From add06adc5d974ac685cb637c48f2455034c8a52f Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期五, 06 二月 2026 13:34:19 +0800
Subject: [PATCH] Merge branch 'dev_new' of http://114.132.189.42:9002/r/product-inventory-APP-before into dev_new

---
 src/pages/procurementManagement/procurementLedger/index.vue         |   72 +
 src/pages.json                                                      |   42 
 src/pages/procurementManagement/procurementLedger/detail.vue        |   47 
 src/pages/safeProduction/safeQualifications/detail.vue              |    2 
 src/pages/safeProduction/accidentReportingRecord/view.vue           |    4 
 src/pages/managementMeetings/meetingBoard/index.vue                 |    8 
 src/pages/sales/receiptPaymentLedger/detail.vue                     |    4 
 src/manifest.json                                                   |  162 +-
 src/pages/safeProduction/safetyTrainingAssessment/index.vue         |  448 ++++++++
 src/pages/sales/receiptPaymentHistory/index.vue                     |    4 
 src/pages/index.vue                                                 |   10 
 src/pages/sales/receiptPayment/index.vue                            |    1 
 src/pages/safeProduction/safetyTrainingAssessment/view.vue          |  171 +++
 src/pages/sales/invoicingRegistration/index.vue                     |    4 
 src/pages/sales/salesAccount/index.vue                              |   85 +
 src/pages/safeProduction/safetyTrainingAssessment/record.vue        |  546 +++++++++
 src/pages/sales/invoiceLedger/index.vue                             |    6 
 src/pages/safeProduction/emergencyPlanReview/detail.vue             |   29 
 src/pages/safeProduction/safetyTrainingAssessment/resultDetail.vue  |  388 +++++++
 src/pages/procurementManagement/paymentLedger/detail.vue            |    6 
 src/pages/safeProduction/safetyTrainingAssessment/fileList.vue      |  567 ++++++++++
 src/api/safeProduction/safetyTrainingAssessment.js                  |  120 ++
 src/pages/safeProduction/accidentReportingRecord/detail.vue         |    7 
 src/pages/sales/invoicingRegistration/view.vue                      |    5 
 src/pages/safeProduction/safeQualifications/index.vue               |    5 
 src/pages/cooperativeOffice/clientVisit/detail.vue                  |   25 
 src/pages/safeProduction/safetyTrainingAssessment/detail.vue        |  430 +++++++
 src/pages/procurementManagement/procurementInvoiceLedger/detail.vue |   76 
 28 files changed, 3,115 insertions(+), 159 deletions(-)

diff --git a/src/api/safeProduction/safetyTrainingAssessment.js b/src/api/safeProduction/safetyTrainingAssessment.js
new file mode 100644
index 0000000..4e493f0
--- /dev/null
+++ b/src/api/safeProduction/safetyTrainingAssessment.js
@@ -0,0 +1,120 @@
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function safeTrainingListPage(query) {
+  return request({
+    url: "/safeTraining/page",
+    method: "get",
+    params: query,
+  });
+}
+
+
+// 鏂板瀹夊叏鍩硅鑰冩牳
+export function safeTrainingAdd(query) {
+    return request({
+        url: '/safeTraining',
+        method: 'post',
+        data: query
+    })
+}
+
+// 淇敼瀹夊叏鍩硅鑰冩牳
+export function safeTrainingUpdate(query) {
+    return request({
+        url: '/safeTraining',
+        method: 'put',
+        data: query
+    })
+}
+
+// 鍒犻櫎瀹夊叏鍩硅鑰冩牳
+export function safeTrainingDel(ids) {
+    return request({
+        url: '/safeTraining/' + ids,
+        method: 'delete',
+        data: ids
+    })
+}
+
+// 瀵煎嚭
+export function safeTrainingExport(query) {
+    return request({
+        url: '/safeTraining/export',
+        method: 'post',
+        data: query,
+        responseType: 'blob'
+    })
+}
+
+// 鏌ヨ闄勪欢鍒楄〃
+export function safeTrainingFileListPage(query) {
+  return request({
+    url: "/safeTrainingFile/listPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 娣诲姞闄勪欢
+export function safeTrainingFileAdd(query) {
+    return request({
+        url: '/safeTrainingFile/add',
+        method: 'post',
+        data: query
+    })
+}
+
+// 鍒犻櫎闄勪欢
+export function safeTrainingFileDel(ids) {
+    return request({
+        url: '/safeTrainingFile/del',
+        method: 'delete',
+        data: ids
+    })
+}
+
+// 绛惧埌
+export function safeTrainingSign(query) {
+    return request({
+        url: '/safeTraining/sign',
+        method: 'post',
+        data: query
+    })
+}
+
+// 鏌ヨ璇︽儏
+export function safeTrainingGet(query) {
+    return request({
+        url: '/safeTraining/getSafeTraining',
+        method: 'get',
+        params: query
+    })
+}
+
+// 鎻愪氦
+export function safeTrainingSave(query) {
+    return request({
+        url: '/safeTraining/saveSafeTraining',
+        method: 'post',
+        data: query
+    })
+}
+
+export function safeTrainingDetailListPage(query) {
+  return request({
+    url: "/safeTrainingDetails/page",
+    method: "get",
+    params: query,
+  });
+}
+
+// 瀵煎嚭
+export function safeTrainingDetailExport(query) {
+    return request({
+        url: '/safeTrainingDetails/export',
+        method: 'post',
+        data: query,
+        responseType: 'blob'
+    })
+}
\ No newline at end of file
diff --git a/src/manifest.json b/src/manifest.json
index 076346e..f1b9741 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -1,35 +1,35 @@
 {
-    "name" : "淇℃伅绠$悊",
-    "appid" : "__UNI__099A590",
-    "description" : "",
-    "versionName" : "1.0.0",
-    "versionCode" : "100",
-    "transformPx" : false,
+    "name": "淇℃伅绠$悊",
+    "appid": "__UNI__099A590",
+    "description": "",
+    "versionName": "1.0.0",
+    "versionCode": "100",
+    "transformPx": false,
     /* 5+App鐗规湁鐩稿叧 */
-    "app-plus" : {
-        "compatible" : {
-            "usingComponents" : true,
-            "ignoreVersion" : true
+    "app-plus": {
+        "compatible": {
+            "usingComponents": true,
+            "ignoreVersion": true
         },
-        "usingComponents" : true,
-        "nvueStyleCompiler" : "uni-app",
-        "compilerVersion" : 3,
-        "splashscreen" : {
-            "alwaysShowBeforeRender" : true,
-            "waiting" : true,
-            "autoclose" : true,
-            "delay" : 0
+        "usingComponents": true,
+        "nvueStyleCompiler": "uni-app",
+        "compilerVersion": 3,
+        "splashscreen": {
+            "alwaysShowBeforeRender": true,
+            "waiting": true,
+            "autoclose": true,
+            "delay": 0
         },
         /* 妯″潡閰嶇疆 */
-        "modules" : {
-            "Camera" : {},
-            "Barcode" : {}
+        "modules": {
+            "Camera": {},
+            "Barcode": {}
         },
         /* 搴旂敤鍙戝竷淇℃伅 */
-        "distribute" : {
+        "distribute": {
             /* android鎵撳寘閰嶇疆 */
-            "android" : {
-                "permissions" : [
+            "android": {
+                "permissions": [
                     "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
                     "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
                     "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
@@ -44,80 +44,90 @@
                     "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
                     "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
                     "<uses-feature android:name=\"android.hardware.camera\"/>",
-                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS\"/>",
+                    "<uses-feature android:name=\"android.hardware.location\"/>",
+                    "<uses-feature android:name=\"android.hardware.location.gps\"/>",
+                    "<uses-feature android:name=\"android.hardware.location.network\"/>"
                 ]
             },
             /* ios鎵撳寘閰嶇疆 */
-            "ios" : {
-                "dSYMs" : false
+            "ios": {
+                "dSYMs": false,
+                "plist": {
+                    "NSLocationWhenInUseUsageDescription": "闇�瑕佽幏鍙栨偍鐨勪綅缃俊鎭潵璁板綍瀹㈡埛鎷滆鍦扮偣",
+                    "NSLocationAlwaysAndWhenInUseUsageDescription": "闇�瑕佽幏鍙栨偍鐨勪綅缃俊鎭潵璁板綍瀹㈡埛鎷滆鍦扮偣"
+                }
             },
             /* SDK閰嶇疆 */
-            "sdkConfigs" : {
-                "push" : {
-                    "unipush" : {
-                        "icons" : {
-                            "small" : {
-                                "ldpi" : "D:/xindao/wenjian/img/logo/app.png"
+            "sdkConfigs": {
+                "push": {
+                    "unipush": {
+                        "icons": {
+                            "small": {
+                                "ldpi": "D:/xindao/wenjian/img/logo/app.png"
                             }
                         }
                     }
                 }
             },
-            "icons" : {
-                "android" : {
-                    "hdpi" : "unpackage/res/icons/72x72.png",
-                    "xhdpi" : "unpackage/res/icons/96x96.png",
-                    "xxhdpi" : "unpackage/res/icons/144x144.png",
-                    "xxxhdpi" : "unpackage/res/icons/192x192.png"
+            "icons": {
+                "android": {
+                    "hdpi": "unpackage/res/icons/72x72.png",
+                    "xhdpi": "unpackage/res/icons/96x96.png",
+                    "xxhdpi": "unpackage/res/icons/144x144.png",
+                    "xxxhdpi": "unpackage/res/icons/192x192.png"
                 },
-                "ios" : {
-                    "appstore" : "unpackage/res/icons/1024x1024.png",
-                    "ipad" : {
-                        "app" : "unpackage/res/icons/76x76.png",
-                        "app@2x" : "unpackage/res/icons/152x152.png",
-                        "notification" : "unpackage/res/icons/20x20.png",
-                        "notification@2x" : "unpackage/res/icons/40x40.png",
-                        "proapp@2x" : "unpackage/res/icons/167x167.png",
-                        "settings" : "unpackage/res/icons/29x29.png",
-                        "settings@2x" : "unpackage/res/icons/58x58.png",
-                        "spotlight" : "unpackage/res/icons/40x40.png",
-                        "spotlight@2x" : "unpackage/res/icons/80x80.png"
+                "ios": {
+                    "appstore": "unpackage/res/icons/1024x1024.png",
+                    "ipad": {
+                        "app": "unpackage/res/icons/76x76.png",
+                        "app@2x": "unpackage/res/icons/152x152.png",
+                        "notification": "unpackage/res/icons/20x20.png",
+                        "notification@2x": "unpackage/res/icons/40x40.png",
+                        "proapp@2x": "unpackage/res/icons/167x167.png",
+                        "settings": "unpackage/res/icons/29x29.png",
+                        "settings@2x": "unpackage/res/icons/58x58.png",
+                        "spotlight": "unpackage/res/icons/40x40.png",
+                        "spotlight@2x": "unpackage/res/icons/80x80.png"
                     },
-                    "iphone" : {
-                        "app@2x" : "unpackage/res/icons/120x120.png",
-                        "app@3x" : "unpackage/res/icons/180x180.png",
-                        "notification@2x" : "unpackage/res/icons/40x40.png",
-                        "notification@3x" : "unpackage/res/icons/60x60.png",
-                        "settings@2x" : "unpackage/res/icons/58x58.png",
-                        "settings@3x" : "unpackage/res/icons/87x87.png",
-                        "spotlight@2x" : "unpackage/res/icons/80x80.png",
-                        "spotlight@3x" : "unpackage/res/icons/120x120.png"
+                    "iphone": {
+                        "app@2x": "unpackage/res/icons/120x120.png",
+                        "app@3x": "unpackage/res/icons/180x180.png",
+                        "notification@2x": "unpackage/res/icons/40x40.png",
+                        "notification@3x": "unpackage/res/icons/60x60.png",
+                        "settings@2x": "unpackage/res/icons/58x58.png",
+                        "settings@3x": "unpackage/res/icons/87x87.png",
+                        "spotlight@2x": "unpackage/res/icons/80x80.png",
+                        "spotlight@3x": "unpackage/res/icons/120x120.png"
                     }
                 }
             }
         }
     },
     /* 蹇簲鐢ㄧ壒鏈夌浉鍏� */
-    "quickapp" : {},
+    "quickapp": {},
     /* 灏忕▼搴忕壒鏈夌浉鍏� */
-    "mp-weixin" : {
-        "appid" : "",
-        "setting" : {
-            "urlCheck" : false
+    "mp-weixin": {
+        "appid": "",
+        "setting": {
+            "urlCheck": false
         },
-        "usingComponents" : true
+        "usingComponents": true
     },
-    "mp-alipay" : {
-        "usingComponents" : true
+    "mp-alipay": {
+        "usingComponents": true
     },
-    "mp-baidu" : {
-        "usingComponents" : true
+    "mp-baidu": {
+        "usingComponents": true
     },
-    "mp-toutiao" : {
-        "usingComponents" : true
+    "mp-toutiao": {
+        "usingComponents": true
     },
-    "uniStatistics" : {
-        "enable" : false
+    "uniStatistics": {
+        "enable": false
     },
-    "vueVersion" : "3"
-}
+    "vueVersion": "3"
+}
\ No newline at end of file
diff --git a/src/pages.json b/src/pages.json
index 2e1cabe..805d0f1 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -814,6 +814,48 @@
         "navigationBarTitleText": "搴旀�ラ妗堣鎯�",
         "navigationStyle": "custom"
       }
+    },
+    {
+      "path": "pages/safeProduction/safetyTrainingAssessment/index",
+      "style": {
+        "navigationBarTitleText": "瀹夊叏鍩硅鑰冩牳",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/safeProduction/safetyTrainingAssessment/detail",
+      "style": {
+        "navigationBarTitleText": "鍩硅璇︽儏",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/safeProduction/safetyTrainingAssessment/view",
+      "style": {
+        "navigationBarTitleText": "鍩硅璇︽儏",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/safeProduction/safetyTrainingAssessment/fileList",
+      "style": {
+        "navigationBarTitleText": "鍩硅闄勪欢",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/safeProduction/safetyTrainingAssessment/resultDetail",
+      "style": {
+        "navigationBarTitleText": "缁撴灉鏄庣粏",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/safeProduction/safetyTrainingAssessment/record",
+      "style": {
+        "navigationBarTitleText": "鍩硅璁板綍",
+        "navigationStyle": "custom"
+      }
     }
   ],
   "subPackages": [
diff --git a/src/pages/cooperativeOffice/clientVisit/detail.vue b/src/pages/cooperativeOffice/clientVisit/detail.vue
index 9c3ce0d..3c54b8e 100644
--- a/src/pages/cooperativeOffice/clientVisit/detail.vue
+++ b/src/pages/cooperativeOffice/clientVisit/detail.vue
@@ -212,9 +212,32 @@
       },
       fail: err => {
         uni.hideLoading();
-        showToast("鑾峰彇浣嶇疆澶辫触锛岃妫�鏌ュ畾浣嶆潈闄�");
         console.error("鑾峰彇浣嶇疆澶辫触:", err);
 
+        // 鏄剧ず閿欒鎻愮ず骞跺紩瀵肩敤鎴锋鏌ユ潈闄�
+        showToast("鑾峰彇浣嶇疆澶辫触锛岃妫�鏌ュ畾浣嶆潈闄�");
+
+        // 寮曞鐢ㄦ埛妫�鏌ユ潈闄愯缃�
+        uni.showModal({
+          title: "浣嶇疆鏉冮檺鎻愮ず",
+          content:
+            "鑾峰彇浣嶇疆澶辫触锛屽彲鑳芥槸鍥犱负浣嶇疆鏉冮檺鏈紑鍚紝璇峰湪璁惧璁剧疆涓鏌ュ苟寮�鍚綅缃潈闄愩��",
+          confirmText: "鐭ラ亾浜�",
+          cancelText: "鍙栨秷",
+          success: res => {
+            if (res.confirm) {
+              // 鍙互灏濊瘯鎵撳紑璁剧疆椤甸潰锛堝鏋滄敮鎸侊級
+              if (uni.openSetting) {
+                uni.openSetting({
+                  success: settingRes => {
+                    console.log("璁剧疆缁撴灉:", settingRes);
+                  },
+                });
+              }
+            }
+          },
+        });
+
         // 澶辫触鏃舵樉绀洪敊璇俊鎭�
         form.value.visitAddress = "浣嶇疆鑾峰彇澶辫触";
       },
diff --git a/src/pages/index.vue b/src/pages/index.vue
index aa08cb3..7f0408c 100644
--- a/src/pages/index.vue
+++ b/src/pages/index.vue
@@ -331,6 +331,10 @@
       icon: "/static/images/icon/guzhangfenxi@2x.png",
       label: "浜嬫晠涓婃姤",
     },
+    {
+      icon: "/static/images/icon/guzhangfenxi@2x.png",
+      label: "瀹夊叏鍩硅",
+    },
   ]);
   // 鍗忓悓鍔炲叕鍔熻兘鏁版嵁
   const collaborationItems = reactive([
@@ -724,6 +728,12 @@
           url: "/pages/safeProduction/accidentReportingRecord/index",
         });
         break;
+      case "瀹夊叏鍩硅":
+        uni.navigateTo({
+          url: "/pages/safeProduction/safetyTrainingAssessment/index",
+        });
+        break;
+
       default:
         uni.showToast({
           title: `鐐瑰嚮浜�${item.label}`,
diff --git a/src/pages/managementMeetings/meetingBoard/index.vue b/src/pages/managementMeetings/meetingBoard/index.vue
index 5b930b4..913ab9a 100644
--- a/src/pages/managementMeetings/meetingBoard/index.vue
+++ b/src/pages/managementMeetings/meetingBoard/index.vue
@@ -8,7 +8,7 @@
     <view class="topbox">
       <view class="boxItem">
         <view class="boxItem-num">
-          {{stats.total}}
+          {{stats.total ? stats.total : 0}}
         </view>
         <view class="boxItem-title">
           鎬讳細璁暟
@@ -16,7 +16,7 @@
       </view>
       <view class="boxItem">
         <view class="boxItem-num">
-          {{stats.underWay}}
+          {{stats.underWay ? stats.underWay : 0}}
         </view>
         <view class="boxItem-title">
           杩涜涓�
@@ -24,7 +24,7 @@
       </view>
       <view class="boxItem">
         <view class="boxItem-num">
-          {{stats.completed}}
+          {{stats.completed ? stats.completed : 0}}
         </view>
         <view class="boxItem-title">
           宸插畬鎴�
@@ -32,7 +32,7 @@
       </view>
       <view class="boxItem">
         <view class="boxItem-num">
-          {{stats.toStart}}
+          {{stats.toStart ? stats.toStart : 0}}
         </view>
         <view class="boxItem-title">
           鍗冲皢寮�濮�
diff --git a/src/pages/procurementManagement/paymentLedger/detail.vue b/src/pages/procurementManagement/paymentLedger/detail.vue
index 33b2856..26f5cf5 100644
--- a/src/pages/procurementManagement/paymentLedger/detail.vue
+++ b/src/pages/procurementManagement/paymentLedger/detail.vue
@@ -54,6 +54,10 @@
             <text class="detail-label">搴斾粯閲戦(鍏�)</text>
             <text class="detail-value danger">{{ formatAmount(item.payableAmount) }}</text>
           </view>
+          <view class="detail-row">
+            <text class="detail-label">鍙戠敓鏃ユ湡</text>
+            <text class="detail-value">{{ item.paymentDate }}</text>
+          </view>
         </view>
       </view>
     </view>
@@ -121,7 +125,7 @@
       return;
     }
     showLoadingToast("鍔犺浇涓�...");
-    paymentRecordList({supplierId: supplierId.value})
+    paymentRecordList({ supplierId: supplierId.value })
       .then(res => {
         tableData.value = res.data;
         closeToast();
diff --git a/src/pages/procurementManagement/procurementInvoiceLedger/detail.vue b/src/pages/procurementManagement/procurementInvoiceLedger/detail.vue
index 179e7e1..3b0bea3 100644
--- a/src/pages/procurementManagement/procurementInvoiceLedger/detail.vue
+++ b/src/pages/procurementManagement/procurementInvoiceLedger/detail.vue
@@ -102,7 +102,7 @@
   const temFutureTickets = ref(0);
   const originalTicketsNum = ref(0); // 淇濆瓨鍘熷鏉ョエ鏁�
 
-  // 琛ㄥ崟鏍¢獙瑙勫垯
+  // 琛ㄥ崟鏍¢獙瑙勫垯 - 浣跨敤绠�鍗曠殑 required 瑙勫垯
   const rules = {
     ticketsNum: [{ required: true, message: "璇疯緭鍏ユ潵绁ㄦ暟", trigger: "blur" }],
     ticketsAmount: [
@@ -265,44 +265,50 @@
 
   // 琛ㄥ崟鎻愪氦
   const onSubmit = async () => {
-    if (!formRef.value) {
-      console.log("琛ㄥ崟寮曠敤涓嶅瓨鍦�");
+    // 鍦ㄩ獙璇佸墠锛岀‘淇濆繀濉瓧娈垫湁鍊�
+    if (!form.value.ticketsNum || form.value.ticketsNum === "" || form.value.ticketsNum === null || form.value.ticketsNum === undefined) {
+      uni.showToast({
+        title: "璇疯緭鍏ユ潵绁ㄦ暟",
+        icon: "none",
+      });
       return;
     }
     
-    try {
-      // 鍏堣皟鐢� validate 鏂规硶
-      const validateResult = formRef.value.validate();
-      
-      // 濡傛灉 validate 杩斿洖 undefined 鎴� null锛岀洿鎺ユ彁浜�
-      if (validateResult === undefined || validateResult === null) {
-        submitForm();
-        return;
-      }
-      
-      // 濡傛灉杩斿洖 Promise锛屼娇鐢� await 鍜� catch
-      if (validateResult && typeof validateResult.then === 'function') {
-        const valid = await validateResult.catch(() => false);
-        if (valid) {
-          // 琛ㄥ崟楠岃瘉閫氳繃锛屾彁浜よ〃鍗�
-          submitForm();
-        } else {
-          // 琛ㄥ崟楠岃瘉澶辫触
-          console.log("琛ㄥ崟楠岃瘉澶辫触");
-        }
-      } else {
-        // 濡傛灉杩斿洖甯冨皵鍊硷紝鐩存帴鍒ゆ柇
-        if (validateResult) {
-          submitForm();
-        } else {
-          console.log("琛ㄥ崟楠岃瘉澶辫触");
-        }
-      }
-    } catch (error) {
-      // 濡傛灉 validate 鏂规硶涓嶅瓨鍦ㄦ垨鎶涘嚭閿欒锛岀洿鎺ユ彁浜�
-      console.log("琛ㄥ崟楠岃瘉澶辫触", error);
-      submitForm();
+    if (!form.value.ticketsAmount || form.value.ticketsAmount === "" || form.value.ticketsAmount === null || form.value.ticketsAmount === undefined) {
+      uni.showToast({
+        title: "璇疯緭鍏ユ湰娆℃潵绁ㄩ噾棰�",
+        icon: "none",
+      });
+      return;
     }
+    
+    // 纭繚瀛楁鏄暟瀛楃被鍨嬶紝骞惰浆鎹负瀛楃涓诧紙鍥犱负琛ㄥ崟鍙兘闇�瑕佸瓧绗︿覆绫诲瀷锛�
+    const ticketsNum = Number(form.value.ticketsNum);
+    const ticketsAmount = Number(form.value.ticketsAmount);
+    
+    // 濡傛灉鏉ョエ鏁颁负0鎴栨潵绁ㄩ噾棰濅负0锛屾彁绀虹敤鎴�
+    if (isNaN(ticketsNum) || ticketsNum <= 0) {
+      uni.showToast({
+        title: "鏉ョエ鏁板繀椤诲ぇ浜�0",
+        icon: "none",
+      });
+      return;
+    }
+    
+    if (isNaN(ticketsAmount) || ticketsAmount <= 0) {
+      uni.showToast({
+        title: "鏈鏉ョエ閲戦蹇呴』澶т簬0",
+        icon: "none",
+      });
+      return;
+    }
+    
+    // 鏇存柊琛ㄥ崟鍊硷紝纭繚鏄湁鏁堢殑鏁板瓧瀛楃涓�
+    form.value.ticketsNum = ticketsNum.toString();
+    form.value.ticketsAmount = ticketsAmount.toString();
+    
+    // 鎵嬪姩楠岃瘉閫氳繃鍚庯紝鐩存帴鎻愪氦锛岃烦杩囪〃鍗曢獙璇侊紙閬垮厤鐪熸満涓婄殑楠岃瘉闂锛�
+    submitForm();
   };
   const purchaseLedgerId = ref("");
   const productModelId = ref({});
diff --git a/src/pages/procurementManagement/procurementLedger/detail.vue b/src/pages/procurementManagement/procurementLedger/detail.vue
index 41df1af..004713b 100644
--- a/src/pages/procurementManagement/procurementLedger/detail.vue
+++ b/src/pages/procurementManagement/procurementLedger/detail.vue
@@ -17,27 +17,30 @@
       </up-form-item>
       <up-form-item label="閿�鍞悎鍚屽彿"
                     prop="salesContractNo"
-                    required
-                    @click="showPicker = true">
+                    required>
         <up-input v-model="form.salesContractNo"
-                  readonly=""
-                  @click="showPicker = true"
+                  readonly
+                  :disabled="isReadOnly"
+                  @click="!isReadOnly && (showPicker = true)"
                   placeholder="鐐瑰嚮閫夋嫨閿�鍞悎鍚屽彿" />
         <template #right>
           <up-icon name="arrow-right"
+                   v-if="!isReadOnly"
                    @click="showPicker = true"></up-icon>
         </template>
       </up-form-item>
       <up-form-item label="渚涘簲鍟嗗悕绉�"
                     prop="supplierName"
                     required
-                    @click="showCustomerPicker = true">
+                    >
         <up-input v-model="form.supplierName"
-                  readonly=""
-                  @click="showCustomerPicker = true"
+                  readonly
+                  :disabled="isReadOnly"
+                  @click="!isReadOnly && (showCustomerPicker = true)"
                   placeholder="鐐瑰嚮閫夋嫨渚涘簲鍟�" />
         <template #right>
           <up-icon name="arrow-right"
+                   v-if="!isReadOnly"
                    @click="showCustomerPicker = true"></up-icon>
         </template>
       </up-form-item>
@@ -45,11 +48,13 @@
                     prop="projectName"
                     required>
         <up-input v-model="form.projectName"
+                  :disabled="isReadOnly"
                   placeholder="璇疯緭鍏ラ」鐩悕绉�" />
       </up-form-item>
       <up-form-item label="浠樻鏂瑰紡"
                     prop="paymentMethod">
         <up-input v-model="form.paymentMethod"
+                  :disabled="isReadOnly"
                   placeholder="璇疯緭鍏ヤ粯娆炬柟寮�" />
       </up-form-item>
       <up-form-item label="绛捐鏃ユ湡"
@@ -57,9 +62,11 @@
                     prop="executionDate">
         <up-input v-model="form.executionDate"
                   placeholder="璇烽�夋嫨"
-                  readonly="" />
+                  readonly
+                  :disabled="isReadOnly" />
         <template #right>
           <up-icon name="arrow-right"
+                   v-if="!isReadOnly"
                    @click="showTimePicker = true"></up-icon>
         </template>
       </up-form-item>
@@ -99,10 +106,12 @@
                   <text class="approver-name">{{ step.nickName }}</text>
                 </view>
                 <view class="delete-approver-btn"
+                      v-if="!isReadOnly"
                       @click="removeApprover(stepIndex)">脳</view>
               </view>
               <view v-else
                     class="add-approver-btn"
+                    v-if="!isReadOnly"
                     @click="addApprover(stepIndex)">
                 <view class="add-circle">+</view>
                 <text class="add-label">閫夋嫨瀹℃壒浜�</text>
@@ -111,11 +120,11 @@
             <view class="step-line"
                   v-if="stepIndex < approverNodes.length - 1"></view>
             <view class="delete-step-btn"
-                  v-if="approverNodes.length > 1"
+                  v-if="approverNodes.length > 1 && !isReadOnly"
                   @click="removeApprovalStep(stepIndex)">鍒犻櫎鑺傜偣</view>
           </view>
         </view>
-        <view class="add-step-btn">
+        <view class="add-step-btn" v-if="!isReadOnly">
           <u-button icon="plus"
                     plain
                     type="primary"
@@ -400,13 +409,17 @@
   const operationType = ref("");
   const editData = ref(null);
   const formRef = ref(null);
-  // 瀹℃壒閫氳繃锛坅pprovalStatus === 3锛夊悗锛岀姝㈢紪杈�/鍒犻櫎浜у搧
+  // 瀹℃壒閫氳繃锛坅pprovalStatus === 3锛夊悗锛屾暣鍗曠姝㈢紪杈戯紙鍚骇鍝併�佸熀鏈俊鎭�佸鎵规祦绋嬶級
   const isApprovalPassed = computed(() => {
     const status = editData.value?.approvalStatus ?? form.value?.approvalStatus;
     return Number(status) === 3;
   });
   const canEditProducts = computed(() => {
     return operationType.value !== "view" && !isApprovalPassed.value;
+  });
+  // 鏄惁鏁翠綋鍙锛氭煡鐪嬫ā寮� 鎴� 宸插鎵归�氳繃
+  const isReadOnly = computed(() => {
+    return operationType.value === "view" || isApprovalPassed.value;
   });
 
   const userStore = useUserStore();
@@ -891,6 +904,14 @@
   };
 
   const onSubmit = () => {
+    // 瀹℃壒閫氳繃鐨勫彴璐︾姝㈠啀娆′慨鏀�
+    if (isApprovalPassed.value) {
+      uni.showToast({
+        title: "宸插鎵归�氳繃鐨勫彴璐︿笉鍏佽淇敼",
+        icon: "none",
+      });
+      return;
+    }
     const hasEmptyApprover = approverNodes.value.some(node => !node.userId);
     if (hasEmptyApprover) {
       uni.showToast({
@@ -1096,6 +1117,7 @@
   };
 
   const addApprover = stepIndex => {
+    if (isReadOnly.value) return;
     // 璺宠浆鍒拌仈绯讳汉閫夋嫨椤甸潰
     uni.setStorageSync("stepIndex", stepIndex);
     uni.navigateTo({
@@ -1104,18 +1126,21 @@
   };
 
   const addApprovalStep = () => {
+    if (isReadOnly.value) return;
     // 娣诲姞鏂扮殑瀹℃壒姝ラ
     approverNodes.value.push({ userId: null, nickName: null });
     console.log(approverNodes.value, "approverNodes.value");
   };
 
   const removeApprover = stepIndex => {
+    if (isReadOnly.value) return;
     // 绉婚櫎瀹℃壒浜�
     approverNodes.value[stepIndex].userId = null;
     approverNodes.value[stepIndex].nickName = null;
   };
 
   const removeApprovalStep = stepIndex => {
+    if (isReadOnly.value) return;
     // 纭繚鑷冲皯淇濈暀涓�涓鎵规楠�
     if (approverNodes.value.length > 1) {
       approverNodes.value.splice(stepIndex, 1);
diff --git a/src/pages/procurementManagement/procurementLedger/index.vue b/src/pages/procurementManagement/procurementLedger/index.vue
index ebcf07a..5d44a09 100644
--- a/src/pages/procurementManagement/procurementLedger/index.vue
+++ b/src/pages/procurementManagement/procurementLedger/index.vue
@@ -76,6 +76,21 @@
                 <text class="detail-value">{{ item.entryDate }}</text>
               </view>
             </view>
+            <!-- 浠呴潪鈥滃鎵归�氳繃鈥濈殑鍙拌处灞曠ず鍒犻櫎鎸夐挳 -->
+            <view
+              class="detail-row"
+              v-if="item.approvalStatus !== 3"
+              style="justify-content: flex-end; margin-top: 8px;"
+            >
+              <up-button
+                type="error"
+                size="small"
+                plain
+                @click.stop="handleDelete(item)"
+              >
+                鍒犻櫎
+              </up-button>
+            </view>
           </view>
         </view>
       </view>
@@ -99,7 +114,7 @@
   import { onShow } from "@dcloudio/uni-app";
   import useUserStore from "@/store/modules/user";
   import PageHeader from "@/components/PageHeader.vue";
-  import { purchaseListPage } from "@/api/procurementManagement/procurementLedger";
+  import { purchaseListPage, delPurchase } from "@/api/procurementManagement/procurementLedger";
   const userStore = useUserStore();
   const approvalStatusText = {
     1: "寰呭鏍�",
@@ -208,6 +223,61 @@
     }
   };
 
+  // 鍒犻櫎鍗曟潯閲囪喘鍙拌处
+  const handleDelete = row => {
+    if (!row || !row.id) {
+      uni.showToast({
+        title: "鏁版嵁鏈夎锛屾棤娉曞垹闄�",
+        icon: "none",
+      });
+      return;
+    }
+
+    uni.showModal({
+      title: "鎻愮ず",
+      content: "閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�",
+      confirmText: "纭",
+      cancelText: "鍙栨秷",
+      success: res => {
+        if (res.confirm) {
+          delPurchase([row.id])
+            .then(result => {
+              // 鎴愬姛锛歝ode === 200
+              if (result && result.code === 200) {
+                uni.showToast({
+                  title: "鍒犻櫎鎴愬姛",
+                  icon: "success",
+                });
+                getList();
+                return;
+              }
+              // 涓氬姟澶辫触锛氫紭鍏堝睍绀哄悗绔繑鍥炵殑 msg锛堝 CG2026... 涓嶅厑璁稿垹闄わ級
+              uni.showToast({
+                title: (result && result.msg) || "鍒犻櫎澶辫触",
+                icon: "none",
+              });
+            })
+            .catch(error => {
+              // 瀵逛簬 request 灏佽杩斿洖鐨� '500' 鎴栧叾浠� code锛岄敊璇彁绀哄凡缁忓湪 request 閲� toast 杩囦簡锛岃繖閲屼笉鍐嶉噸澶嶈鐩�
+              if (error === "500" || typeof error === "number") {
+                return;
+              }
+              // 鍙湁鍦ㄧ湡姝e紓甯告椂锛屾墠鍦ㄨ繖閲屽厹搴曟彁绀�
+              const msg =
+                (error && error.msg) ||
+                (error && error.response && error.response.data && error.response.data.msg) ||
+                (error && error.message) ||
+                "鍒犻櫎澶辫触";
+              uni.showToast({
+                title: msg,
+                icon: "none",
+              });
+            });
+        }
+      },
+    });
+  };
+
   onShow(() => {
     // 椤甸潰鏄剧ず鏃跺埛鏂板垪琛�
     getList();
diff --git a/src/pages/safeProduction/accidentReportingRecord/detail.vue b/src/pages/safeProduction/accidentReportingRecord/detail.vue
index af7e89b..ec9662e 100644
--- a/src/pages/safeProduction/accidentReportingRecord/detail.vue
+++ b/src/pages/safeProduction/accidentReportingRecord/detail.vue
@@ -79,6 +79,12 @@
             <view class="unit">鍏�</view>
           </template>
         </u-form-item>
+        <u-form-item label="浜哄憳鎹熷け鎯呭喌"
+                     prop="personLoss"
+                     border-bottom>
+          <u-input v-model="form.personLoss"
+                   placeholder="璇疯緭鍏ヤ汉鍛樻崯澶辨儏鍐�" />
+        </u-form-item>
         <u-form-item label="浜嬫晠鐩存帴鍘熷洜"
                      prop="accidentCause"
                      border-bottom>
@@ -185,6 +191,7 @@
     accidentGrade: "",
     happenTime: "",
     happenLocation: "",
+    personLoss: "",
     createUserName: "",
     createTime: "",
     assetLoss: "",
diff --git a/src/pages/safeProduction/accidentReportingRecord/view.vue b/src/pages/safeProduction/accidentReportingRecord/view.vue
index c696dc3..ec15918 100644
--- a/src/pages/safeProduction/accidentReportingRecord/view.vue
+++ b/src/pages/safeProduction/accidentReportingRecord/view.vue
@@ -54,6 +54,10 @@
             <view class="info-value">{{ accidentInfo.assetLoss || '-' }}<span v-if="accidentInfo.assetLoss">鍏�</span></view>
           </view>
           <view class="info-row">
+            <view class="info-label">浜哄憳鎹熷け鎯呭喌锛�</view>
+            <view class="info-value">{{ accidentInfo.personLoss || '-' }}</view>
+          </view>
+          <view class="info-row">
             <view class="info-label">涓婃姤鏃堕棿锛�</view>
             <view class="info-value">{{ accidentInfo.createTime || '-' }}</view>
           </view>
diff --git a/src/pages/safeProduction/emergencyPlanReview/detail.vue b/src/pages/safeProduction/emergencyPlanReview/detail.vue
index 6117254..c36316b 100644
--- a/src/pages/safeProduction/emergencyPlanReview/detail.vue
+++ b/src/pages/safeProduction/emergencyPlanReview/detail.vue
@@ -436,10 +436,33 @@
   // 鎻愪氦琛ㄥ崟
   const submitForm = async () => {
     // 楠岃瘉琛ㄥ崟蹇呭~椤�
-    if (!formRef.value) return;
+    if (!form.value.planCode) {
+      showToast("璇疯緭鍏ュ簲鎬ラ妗堢紪鐮�");
+      return;
+    }
 
-    const valid = await formRef.value.validate();
-    if (!valid) {
+    if (!form.value.planName) {
+      showToast("璇疯緭鍏ュ簲鎬ラ妗堝悕绉�");
+      return;
+    }
+
+    if (!form.value.publishTime) {
+      showToast("璇烽�夋嫨鍙戝竷鐢熸晥鏃堕棿");
+      return;
+    }
+
+    if (!form.value.planType) {
+      showToast("璇烽�夋嫨棰勬绫诲瀷");
+      return;
+    }
+
+    if (!form.value.coreResponsorUserId) {
+      showToast("璇烽�夋嫨鏍稿績璐d换浜�");
+      return;
+    }
+
+    if (!form.value.applyScope || form.value.applyScope.length === 0) {
+      showToast("璇烽�夋嫨閫傜敤鑼冨洿");
       return;
     }
 
diff --git a/src/pages/safeProduction/safeQualifications/detail.vue b/src/pages/safeProduction/safeQualifications/detail.vue
index 0d0648b..e821920 100644
--- a/src/pages/safeProduction/safeQualifications/detail.vue
+++ b/src/pages/safeProduction/safeQualifications/detail.vue
@@ -235,7 +235,7 @@
   onLoad(() => {
     // 缂栬緫瑙勭▼璧勮川鏃讹紝浠庢湰鍦板瓨鍌ㄨ幏鍙栨暟鎹�
     const qualification = uni.getStorageSync("safeQualifications");
-    if (qualification) {
+    if (qualification.id) {
       form.value = qualification;
       isEdit.value = true;
 
diff --git a/src/pages/safeProduction/safeQualifications/index.vue b/src/pages/safeProduction/safeQualifications/index.vue
index 0456efd..95b18bc 100644
--- a/src/pages/safeProduction/safeQualifications/index.vue
+++ b/src/pages/safeProduction/safeQualifications/index.vue
@@ -106,12 +106,7 @@
   import { delCustomer } from "@/api/cooperativeOffice/clientVisit";
   import {
     qualificationsListPage,
-    safeCertificationAdd,
-    safeCertificationUpdate,
     safeCertificationDel,
-    fileListPage,
-    safeCertificationFileAdd,
-    safeCertificationFileDel,
   } from "@/api/safeProduction/safeQualifications";
 
   import useUserStore from "@/store/modules/user";
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/detail.vue b/src/pages/safeProduction/safetyTrainingAssessment/detail.vue
new file mode 100644
index 0000000..d55c892
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/detail.vue
@@ -0,0 +1,430 @@
+<template>
+  <view class="danger-investigation-detail">
+    <PageHeader :title="isEdit ? '缂栬緫鍩硅' : '鏂板鍩硅'"
+                @back="goBack" />
+    <u-form @submit="handleSubmit"
+            ref="formRef"
+            label-width="110">
+      <!-- 鍩硅淇℃伅 -->
+      <u-cell-group title="鍩硅淇℃伅">
+        <u-form-item label="璇剧▼缂栧彿"
+                     prop="courseCode"
+                     border-bottom>
+          <u-input v-model="form.courseCode"
+                   placeholder="绯荤粺鑷姩鐢熸垚"
+                   readonly />
+        </u-form-item>
+        <u-form-item label="鍩硅鏃ユ湡"
+                     prop="trainingDate"
+                     required
+                     border-bottom>
+          <u-input v-model="form.trainingDate"
+                   placeholder="璇烽�夋嫨鍩硅鏃ユ湡"
+                   @click="showTrainingDatePicker"
+                   readonly />
+          <template #right>
+            <up-icon name="arrow-right"
+                     @click="showTrainingDatePicker"></up-icon>
+          </template>
+        </u-form-item>
+        <u-form-item label="寮�濮嬫椂闂�"
+                     prop="openingTime"
+                     required
+                     border-bottom>
+          <u-input v-model="form.openingTime"
+                   placeholder="璇烽�夋嫨寮�濮嬫椂闂�"
+                   @click="showOpeningTimePicker"
+                   readonly />
+          <template #right>
+            <up-icon name="arrow-right"
+                     @click="showOpeningTimePicker"></up-icon>
+          </template>
+        </u-form-item>
+        <u-form-item label="缁撴潫鏃堕棿"
+                     prop="endTime"
+                     required
+                     border-bottom>
+          <u-input v-model="form.endTime"
+                   placeholder="璇烽�夋嫨缁撴潫鏃堕棿"
+                   @click="showEndTimePicker"
+                   readonly />
+          <template #right>
+            <up-icon name="arrow-right"
+                     @click="showEndTimePicker"></up-icon>
+          </template>
+        </u-form-item>
+        <u-form-item label="鍩硅鐩爣"
+                     prop="trainingObjectives"
+                     border-bottom>
+          <u-input v-model="form.trainingObjectives"
+                   placeholder="璇疯緭鍏ュ煿璁洰鏍�" />
+        </u-form-item>
+        <u-form-item label="鍙傚姞瀵硅薄"
+                     prop="participants"
+                     border-bottom>
+          <u-input v-model="form.participants"
+                   placeholder="璇疯緭鍏ュ弬鍔犲璞�" />
+        </u-form-item>
+        <u-form-item label="鍩硅鍐呭"
+                     prop="trainingContent"
+                     required
+                     border-bottom>
+          <u-textarea v-model="form.trainingContent"
+                      placeholder="璇疯緭鍏ュ煿璁唴瀹�"
+                      :maxlength="200"
+                      count
+                      :autoHeight="true" />
+        </u-form-item>
+        <u-form-item label="鍩硅璁插笀"
+                     prop="trainingLecturer"
+                     required
+                     border-bottom>
+          <u-input v-model="form.trainingLecturer"
+                   placeholder="璇疯緭鍏ュ煿璁甯�" />
+        </u-form-item>
+        <u-form-item label="椤圭洰瀛﹀垎"
+                     prop="projectCredits"
+                     border-bottom>
+          <u-input v-model="form.projectCredits"
+                   placeholder="璇疯緭鍏ラ」鐩鍒�"
+                   type="number" />
+        </u-form-item>
+        <u-form-item label="鍩硅鏂瑰紡"
+                     prop="trainingMode"
+                     border-bottom>
+          <u-input v-model="trainingModeName"
+                   placeholder="璇烽�夋嫨鍩硅鏂瑰紡"
+                   @click="showTrainingModeSheet"
+                   readonly />
+          <template #right>
+            <up-icon name="arrow-right"
+                     @click="showTrainingModeSheet"></up-icon>
+          </template>
+        </u-form-item>
+        <u-form-item label="鍩硅鍦扮偣"
+                     prop="placeTraining"
+                     border-bottom>
+          <u-input v-model="form.placeTraining"
+                   placeholder="璇疯緭鍏ュ煿璁湴鐐�" />
+        </u-form-item>
+        <u-form-item label="璇炬椂"
+                     prop="classHour"
+                     required
+                     border-bottom>
+          <u-input v-model="form.classHour"
+                   placeholder="璇疯緭鍏ヨ鏃�"
+                   type="number" />
+        </u-form-item>
+      </u-cell-group>
+      <!-- 鎻愪氦鎸夐挳 -->
+      <view class="footer-btns">
+        <u-button class="cancel-btn"
+                  @click="goBack">鍙栨秷</u-button>
+        <u-button class="sign-btn"
+                  type="primary"
+                  @click="handleSubmit"
+                  :loading="loading">{{ isEdit ? '淇濆瓨淇敼' : '鎻愪氦' }}</u-button>
+      </view>
+    </u-form>
+    <!-- 鏃堕棿閫夋嫨鍣� -->
+    <up-datetime-picker :show="trainingDateVisible"
+                        mode="date"
+                        v-model="nowDate"
+                        @confirm="handleTrainingDateConfirm"
+                        @cancel="trainingDateVisible = false"
+                        title="閫夋嫨鍩硅鏃ユ湡" />
+    <u-datetime-picker :show="openingTimeVisible"
+                       mode="time"
+                       @confirm="handleOpeningTimeConfirm"
+                       @cancel="openingTimeVisible = false"
+                       title="閫夋嫨寮�濮嬫椂闂�" />
+    <u-datetime-picker :show="endTimeVisible"
+                       mode="time"
+                       @confirm="handleEndTimeConfirm"
+                       @cancel="endTimeVisible = false"
+                       title="閫夋嫨缁撴潫鏃堕棿" />
+    <!-- 鍩硅鏂瑰紡閫夋嫨鍣� -->
+    <up-action-sheet :show="trainingModeSheetVisible"
+                     :actions="trainingModeOptions"
+                     @select="handleTrainingModeSelect"
+                     @close="trainingModeSheetVisible = false"
+                     title="閫夋嫨鍩硅鏂瑰紡" />
+  </view>
+</template>
+
+<script setup>
+  // 鏇挎崲 toast 鏂规硶
+  defineOptions({ name: "danger-investigation-detail" });
+  const showToast = message => {
+    uni.showToast({ title: message, icon: "none" });
+  };
+
+  import { ref, onMounted } from "vue";
+  import PageHeader from "@/components/PageHeader.vue";
+  import {
+    safeTrainingAdd,
+    safeTrainingUpdate,
+  } from "@/api/safeProduction/safetyTrainingAssessment";
+  import { onLoad } from "@dcloudio/uni-app";
+  import { useDict } from "@/utils/dict";
+  import dayjs from "dayjs";
+
+  // 鑾峰彇瀛楀吀鏁版嵁
+  const { safe_training_methods } = useDict("safe_training_methods");
+
+  // 琛ㄥ崟鏁版嵁
+  const form = ref({
+    courseCode: "", // 璇剧▼缂栧彿
+    trainingDate: "", // 鍩硅鏃ユ湡
+    openingTime: "", // 寮�濮嬫椂闂�
+    endTime: "", // 缁撴潫鏃堕棿
+    trainingObjectives: "", // 鍩硅鐩爣
+    participants: "", // 鍙傚姞瀵硅薄
+    trainingContent: "", // 鍩硅鍐呭
+    trainingLecturer: "", // 鍩硅璁插笀
+    projectCredits: "", // 椤圭洰瀛﹀垎
+    trainingMode: "", // 鍩硅鏂瑰紡
+    placeTraining: "", // 鍩硅鍦扮偣
+    classHour: "", // 璇炬椂
+  });
+
+  // 椤甸潰鐘舵��
+  const loading = ref(false);
+  const formRef = ref(null);
+  const isEdit = ref(false);
+
+  // 鍩硅鏂瑰紡閫夋嫨鍣�
+  const trainingModeSheetVisible = ref(false);
+  const trainingModeName = ref("");
+  const trainingModeOptions = ref([]);
+
+  // 鏃堕棿閫夋嫨鍣�
+  const trainingDateVisible = ref(false);
+  const openingTimeVisible = ref(false);
+  const endTimeVisible = ref(false);
+  const nowDate = ref(new Date());
+
+  const showTrainingDatePicker = () => {
+    trainingDateVisible.value = true;
+  };
+
+  const showOpeningTimePicker = () => {
+    openingTimeVisible.value = true;
+  };
+
+  const showEndTimePicker = () => {
+    endTimeVisible.value = true;
+  };
+
+  const handleTrainingDateConfirm = e => {
+    form.value.trainingDate = dayjs(e.value).format("YYYY-MM-DD");
+    nowDate.value = e.value;
+    trainingDateVisible.value = false;
+  };
+
+  const handleOpeningTimeConfirm = e => {
+    console.log(e);
+    form.value.openingTime = e.value;
+    openingTimeVisible.value = false;
+  };
+
+  const handleEndTimeConfirm = e => {
+    form.value.endTime = e.value;
+    endTimeVisible.value = false;
+  };
+
+  // 鏄剧ず鍩硅鏂瑰紡閫夋嫨鍣�
+  const showTrainingModeSheet = () => {
+    trainingModeSheetVisible.value = true;
+  };
+
+  // 澶勭悊鍩硅鏂瑰紡閫夋嫨
+  const handleTrainingModeSelect = item => {
+    form.value.trainingMode = item.value;
+    trainingModeName.value = item.name;
+    trainingModeSheetVisible.value = false;
+  };
+
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.removeStorageSync("safetyTraining");
+    uni.navigateBack();
+  };
+
+  // 鎻愪氦琛ㄥ崟
+  const handleSubmit = async () => {
+    if (!form.value.trainingDate) {
+      showToast("璇烽�夋嫨鍩硅鏃ユ湡");
+      return;
+    }
+
+    if (!form.value.openingTime) {
+      showToast("璇烽�夋嫨寮�濮嬫椂闂�");
+      return;
+    }
+
+    if (!form.value.endTime) {
+      showToast("璇烽�夋嫨缁撴潫鏃堕棿");
+      return;
+    }
+    if (!form.value.trainingContent) {
+      showToast("璇疯緭鍏ュ煿璁唴瀹�");
+      return;
+    }
+
+    if (!form.value.trainingLecturer) {
+      showToast("璇疯緭鍏ュ煿璁甯�");
+      return;
+    }
+
+    if (!form.value.classHour) {
+      showToast("璇疯緭鍏ヨ鏃�");
+      return;
+    }
+    if (
+      form.value.projectCredits &&
+      (isNaN(Number(form.value.projectCredits)) ||
+        Number(form.value.projectCredits) <= 0)
+    ) {
+      showToast("瀛﹀垎蹇呴』鏄ぇ浜�0鐨勬暟瀛�");
+      return;
+    }
+    form.value.openingTime = form.value.openingTime + ":00";
+    form.value.endTime = form.value.endTime + ":00";
+
+    if (
+      form.value.classHour &&
+      (isNaN(Number(form.value.classHour)) || Number(form.value.classHour) <= 0)
+    ) {
+      showToast("璇炬椂蹇呴』鏄ぇ浜�0鐨勬暟瀛�");
+      return;
+    }
+
+    try {
+      loading.value = true;
+
+      // 浣跨敤瀹夊叏娴呮嫹璐�
+      const source =
+        form.value && typeof form.value === "object" ? form.value : {};
+      const submitData = {};
+      Object.keys(source).forEach(k => {
+        submitData[k] = source[k];
+      });
+
+      if (isEdit.value) {
+        const { code } = await safeTrainingAdd(submitData);
+        if (code === 200) {
+          showToast("淇敼鎴愬姛");
+          setTimeout(() => {
+            goBack();
+          }, 500);
+        } else {
+          loading.value = false;
+          showToast("淇敼澶辫触锛岃閲嶈瘯");
+        }
+      } else {
+        const { code } = await safeTrainingAdd(submitData);
+        if (code === 200) {
+          showToast("鏂板鎴愬姛");
+          setTimeout(() => {
+            goBack();
+          }, 500);
+        } else {
+          loading.value = false;
+          showToast("鏂板澶辫触锛岃閲嶈瘯");
+        }
+      }
+    } catch (e) {
+      loading.value = false;
+      console.error("鎻愪氦澶辫触:", e);
+      showToast("鎻愪氦澶辫触锛岃閲嶈瘯");
+    }
+  };
+
+  onLoad(() => {
+    // 缂栬緫鍩硅鏃讹紝浠庢湰鍦板瓨鍌ㄨ幏鍙栨暟鎹�
+    const safetyTraining = uni.getStorageSync("safetyTraining");
+    if (safetyTraining.id) {
+      form.value = safetyTraining;
+      nowDate.value = dayjs(form.value.trainingDate).toDate();
+      form.value.openingTime = form.value.openingTime
+        ? form.value.openingTime.slice(0, 5)
+        : "";
+      form.value.endTime = form.value.endTime
+        ? form.value.endTime.slice(0, 5)
+        : "";
+      isEdit.value = true;
+    } else {
+      isEdit.value = false;
+      // 榛樿鍩硅鏃ユ湡涓轰粖澶�
+      form.value.trainingDate = dayjs().format("YYYY-MM-DD");
+    }
+  });
+
+  onMounted(() => {
+    // 鍒濆鍖栧煿璁柟寮忛�夐」
+    if (safe_training_methods && Array.isArray(safe_training_methods.value)) {
+      trainingModeOptions.value =
+        safe_training_methods.value.map(item => ({
+          value: item.value,
+          name: item.label,
+        })) || [];
+    } else {
+      trainingModeOptions.value = [];
+    }
+
+    // 璁剧疆宸查�夊�肩殑鏄剧ず鏂囨湰
+    if (form.value.trainingMode) {
+      const modeItem = trainingModeOptions.value.find(
+        item => String(item.value) === String(form.value.trainingMode)
+      );
+      trainingModeName.value = modeItem ? modeItem.name : "";
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @import "@/static/scss/form-common.scss";
+
+  .danger-investigation-detail {
+    min-height: 100vh;
+    background: #f8f9fa;
+    padding-bottom: 100px;
+  }
+
+  .footer-btns {
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: #fff;
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
+    padding: 0.75rem 0;
+    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
+    z-index: 1000;
+  }
+
+  .cancel-btn {
+    font-weight: 400;
+    font-size: 1rem;
+    color: #666;
+    background: #f5f5f5;
+    border: 1px solid #ddd;
+    width: 45%;
+    height: 2.5rem;
+    border-radius: 2.5rem;
+  }
+
+  .sign-btn {
+    font-weight: 500;
+    font-size: 1rem;
+    color: #fff;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border: none;
+    width: 45%;
+    height: 2.5rem;
+    border-radius: 2.5rem;
+  }
+</style>
\ No newline at end of file
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/fileList.vue b/src/pages/safeProduction/safetyTrainingAssessment/fileList.vue
new file mode 100644
index 0000000..9797f21
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/fileList.vue
@@ -0,0 +1,567 @@
+<template>
+  <view class="file-list-page">
+    <!-- 椤甸潰澶撮儴 -->
+    <PageHeader title="闄勪欢绠$悊"
+                @back="goBack" />
+    <!-- 闄勪欢鍒楄〃 -->
+    <view class="file-list-container">
+      <view v-if="fileList.length > 0"
+            class="file-list">
+        <view v-for="(file, index) in fileList"
+              :key="file.id || index"
+              class="file-item">
+          <!-- 鏂囦欢鍥炬爣 -->
+          <!-- <view class="file-icon"
+                :class="getFileIconClass(file.fileType)">
+            <up-icon :name="getFileIcon(file.fileType)"
+                     size="24"
+                     color="#ffffff" />
+          </view> -->
+          <!-- 鏂囦欢淇℃伅 -->
+          <view class="file-info">
+            <text class="file-name">{{ file.name }}</text>
+            <!-- <text class="file-meta">{{ formatFileSize(file.fileSize) }} 路 {{ file.uploadTime || file.createTime }}</text> -->
+          </view>
+          <!-- 鎿嶄綔鎸夐挳 -->
+          <view class="file-actions">
+            <!-- <u-button size="small"
+                      type="primary"
+                      plain
+                      @click="previewFile(file)">棰勮</u-button> -->
+            <u-button size="small"
+                      type="info"
+                      plain
+                      @click="downloadFile(file)">涓嬭浇骞堕瑙�</u-button>
+            <u-button size="small"
+                      type="error"
+                      plain
+                      @click="confirmDelete(file, index)">鍒犻櫎</u-button>
+          </view>
+        </view>
+      </view>
+      <!-- 绌虹姸鎬� -->
+      <view v-else
+            class="empty-state">
+        <up-icon name="document"
+                 size="64"
+                 color="#c0c4cc" />
+        <text class="empty-text">鏆傛棤闄勪欢</text>
+      </view>
+    </view>
+    <!-- <a rel="nofollow"
+       id="downloadLink"
+       href="#"
+       style="display:none;">涓嬭浇鏂囨湰鏂囦欢</a> -->
+    <!-- 涓婁紶鎸夐挳 -->
+    <view class="upload-button"
+          @click="chooseFile">
+      <up-icon name="plus"
+               size="24"
+               color="#ffffff" />
+      <text class="upload-text">涓婁紶闄勪欢</text>
+    </view>
+  </view>
+</template>
+
+<script setup>
+  import { ref, onMounted } from "vue";
+  import PageHeader from "@/components/PageHeader.vue";
+  import config from "@/config";
+  import { getToken } from "@/utils/auth";
+  // import { saveAs } from "file-saver";
+  import {
+    listRuleFiles,
+    delRuleFile,
+  } from "@/api/managementMeetings/rulesRegulationsManagement";
+  import {
+    safeTrainingFileListPage,
+    safeTrainingFileAdd,
+    safeTrainingFileDel,
+  } from "@/api/safeProduction/safetyTrainingAssessment";
+  import { blobValidate } from "@/utils/ruoyi";
+
+  // 闄勪欢鍒楄〃
+  const fileList = ref([]);
+
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
+  // const request = axios.create({
+  //   baseURL: "URL.com",
+  //   adapter: axiosAdapterUniapp,
+  // });
+  // 鑾峰彇鏂囦欢鍥炬爣
+  const getFileIcon = fileType => {
+    const iconMap = {
+      doc: "document",
+      docx: "document",
+      xls: "grid",
+      xlsx: "grid",
+      pdf: "document",
+      ppt: "copy",
+      pptx: "copy",
+      txt: "document",
+      jpg: "image",
+      jpeg: "image",
+      png: "image",
+      gif: "image",
+      zip: "folder",
+      rar: "folder",
+    };
+    return iconMap[fileType.toLowerCase()] || "document";
+  };
+
+  // 鑾峰彇鏂囦欢鍥炬爣鏍峰紡绫�
+  const getFileIconClass = fileType => {
+    const colorMap = {
+      doc: "blue",
+      docx: "blue",
+      xls: "green",
+      xlsx: "green",
+      pdf: "red",
+      ppt: "orange",
+      pptx: "orange",
+      txt: "gray",
+      jpg: "purple",
+      jpeg: "purple",
+      png: "purple",
+      gif: "purple",
+      zip: "yellow",
+      rar: "yellow",
+    };
+    return colorMap[fileType.toLowerCase()] || "gray";
+  };
+
+  // 鏍煎紡鍖栨枃浠跺ぇ灏�
+  const formatFileSize = bytes => {
+    if (bytes === 0) return "0 B";
+    const k = 1024;
+    const sizes = ["B", "KB", "MB", "GB"];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
+  };
+
+  // 閫夋嫨鏂囦欢
+  const chooseFile = () => {
+    uni.chooseImage({
+      count: 9,
+      sizeType: ["original", "compressed"],
+      sourceType: ["album", "camera"],
+      success: res => {
+        console.log(res, "閫夋嫨鍥剧墖鎴愬姛");
+        uploadFiles(res.tempFiles);
+      },
+      fail: err => {
+        console.error("閫夋嫨鍥剧墖澶辫触:", err);
+        showToast("閫夋嫨鏂囦欢澶辫触");
+      },
+    });
+    // uni.chooseFile({
+    //   count: 9,
+    //   extension: [
+    //     ".doc",
+    //     ".docx",
+    //     ".xls",
+    //     ".xlsx",
+    //     ".pdf",
+    //     ".ppt",
+    //     ".pptx",
+    //     ".txt",
+    //     ".jpg",
+    //     ".jpeg",
+    //     ".png",
+    //     ".gif",
+    //     ".zip",
+    //     ".rar",
+    //   ],
+    //   success: res => {
+    //     console.log(res, "閫夋嫨鏂囦欢鎴愬姛");
+    //     uploadFiles(res.tempFiles);
+    //   },
+    //   fail: err => {
+    //     showToast("閫夋嫨鏂囦欢澶辫触");
+    //   },
+    // });
+  };
+
+  // 涓婁紶鏂囦欢
+  const uploadFiles = tempFiles => {
+    console.log(tempFiles, "涓婁紶鏂囦欢1");
+    tempFiles.forEach((tempFile, index) => {
+      // 鏄剧ず涓婁紶涓彁绀�
+      uni.showLoading({
+        title: "涓婁紶涓�...",
+        mask: true,
+      });
+      console.log(tempFile, "涓婁紶鏂囦欢2");
+      // 1. 鐩存帴浣跨敤 uni.uploadFile 涓婁紶鏂囦欢
+      uni.uploadFile({
+        url: config.baseUrl + "/file/upload",
+        filePath: tempFile.path,
+        name: "file",
+        header: {
+          Authorization: "Bearer " + getToken(),
+        },
+        success: uploadRes => {
+          uni.hideLoading();
+          console.log(uploadRes, "涓婁紶鏂囦欢3");
+
+          try {
+            const res = JSON.parse(uploadRes.data);
+            console.log(res, "涓婁紶鏂囦欢4");
+            if (res.code === 200) {
+              // 2. 鎻愬彇鏂囦欢淇℃伅
+              const fileName = tempFile.name
+                ? tempFile.name
+                : tempFile.path.split("/").pop();
+              // const fileType = fileName.split(".").pop();
+              // 3. 鏋勯�犱繚瀛樻枃浠朵俊鎭殑鍙傛暟
+              const saveData = {
+                name: fileName,
+                safeTrainingId: rulesRegulationsManagementId.value,
+                url: res.data.tempPath || "",
+              };
+              console.log(saveData, "淇濆瓨鏂囦欢淇℃伅鍙傛暟");
+              // 4. 璋冪敤 addRuleFile 鎺ュ彛淇濆瓨鏂囦欢淇℃伅
+              safeTrainingFileAdd(saveData)
+                .then(addRes => {
+                  if (addRes.code === 200) {
+                    // 5. 娣诲姞鍒版枃浠跺垪琛�
+                    const newFile = {
+                      ...addRes.data,
+                      uploadTime: new Date().toLocaleString(),
+                    };
+                    // fileList.value.push(newFile);
+                    getFileList();
+                    showToast("涓婁紶鎴愬姛");
+                  } else {
+                    showToast("淇濆瓨鏂囦欢淇℃伅澶辫触");
+                  }
+                })
+                .catch(err => {
+                  console.error("淇濆瓨鏂囦欢淇℃伅澶辫触:", err);
+                  showToast("淇濆瓨鏂囦欢淇℃伅澶辫触");
+                });
+            } else {
+              showToast("鏂囦欢涓婁紶澶辫触");
+            }
+          } catch (e) {
+            console.error("瑙f瀽涓婁紶缁撴灉澶辫触:", e);
+            showToast("涓婁紶澶辫触");
+          }
+        },
+        fail: err => {
+          uni.hideLoading();
+          console.error("涓婁紶澶辫触:", err);
+          showToast("涓婁紶澶辫触");
+        },
+      });
+    });
+  };
+  // 涓嬭浇鏂囦欢
+  const downloadFile = file => {
+    var url =
+      config.baseUrl +
+      "/common/download?fileName=" +
+      encodeURIComponent(file.url) +
+      "&delete=true";
+    console.log(url, "url");
+
+    uni
+      .downloadFile({
+        url: url,
+        responseType: "blob",
+        header: { Authorization: "Bearer " + getToken() },
+      })
+      .then(res => {
+        console.log(res, "涓嬭浇鏂囦欢");
+        let osType = uni.getStorageSync("deviceInfo").osName;
+        let filePath = res.tempFilePath;
+        if (osType === "ios") {
+          uni.openDocument({
+            filePath: filePath,
+            showMenu: true,
+            success: res => {
+              resolve(res);
+            },
+            fail: err => {
+              console.log("uni.openDocument--fail");
+              reject(err);
+            },
+          });
+        } else {
+          uni.saveFile({
+            tempFilePath: filePath,
+            success: fileRes => {
+              uni.showToast({
+                icon: "none",
+                mask: true,
+                title:
+                  "鏂囦欢宸蹭繚瀛橈細Android/data/uni.UNI720216F/apps/__UNI__720216F/" +
+                  fileRes.savedFilePath, //淇濆瓨璺緞
+                duration: 3000,
+              });
+              setTimeout(() => {
+                //鎵撳紑鏂囨。鏌ョ湅
+                uni.openDocument({
+                  filePath: fileRes.savedFilePath,
+                  success: function (res) {
+                    resolve(fileRes);
+                  },
+                });
+              }, 3000);
+            },
+            fail: err => {
+              console.log("uni.save--fail");
+              reject(err);
+            },
+          });
+        }
+        // const isBlob = blobValidate(res.data);
+        // if (isBlob) {
+        //   const blob = new Blob([res.data], { type: "text/plain" });
+        //   const url = URL.createObjectURL(blob);
+        //   const downloadLink = document.getElementById("downloadLink");
+        //   downloadLink.href = url;
+        //   downloadLink.download = file.name;
+        //   downloadLink.click();
+        //   showToast("涓嬭浇鎴愬姛");
+        // } else {
+        //   showToast("涓嬭浇澶辫触");
+        // }
+      })
+      .catch(err => {
+        console.error("涓嬭浇澶辫触:", err);
+        showToast("涓嬭浇澶辫触");
+      });
+  };
+
+  // 纭鍒犻櫎
+  const confirmDelete = (file, index) => {
+    uni.showModal({
+      title: "鍒犻櫎纭",
+      content: `纭畾瑕佸垹闄ら檮浠� "${file.name}" 鍚楋紵`,
+      success: res => {
+        if (res.confirm) {
+          deleteFile(file.id, index);
+        }
+      },
+    });
+  };
+
+  // 鍒犻櫎鏂囦欢
+  const deleteFile = (fileId, index) => {
+    uni.showLoading({
+      title: "鍒犻櫎涓�...",
+      mask: true,
+    });
+
+    safeTrainingFileDel([fileId])
+      .then(res => {
+        uni.hideLoading();
+        if (res.code === 200) {
+          // fileList.value.splice(index, 1);
+          getFileList();
+          showToast("鍒犻櫎鎴愬姛");
+        } else {
+          showToast("鍒犻櫎澶辫触");
+        }
+      })
+      .catch(err => {
+        uni.hideLoading();
+        showToast("鍒犻櫎澶辫触");
+      });
+  };
+
+  // 鏄剧ず鎻愮ず
+  const showToast = message => {
+    uni.showToast({
+      title: message,
+      icon: "none",
+    });
+  };
+  const rulesRegulationsManagementId = ref("");
+  // 椤甸潰鍔犺浇鏃�
+  onMounted(() => {
+    rulesRegulationsManagementId.value = uni.getStorageSync(
+      "safetyTrainingFileId"
+    );
+    // 浠� API 鑾峰彇闄勪欢鍒楄〃
+    getFileList();
+    // 浠庢湰鍦板瓨鍌ㄨ幏鍙� rulesRegulationsManagementId
+  });
+
+  // 鑾峰彇闄勪欢鍒楄〃
+  const getFileList = () => {
+    uni.showLoading({
+      title: "鍔犺浇涓�...",
+      mask: true,
+    });
+
+    safeTrainingFileListPage({
+      safeTrainingId: rulesRegulationsManagementId.value,
+      current: -1,
+      size: -1,
+    })
+      .then(res => {
+        uni.hideLoading();
+        if (res.code === 200) {
+          fileList.value = res.data.records || [];
+        } else {
+          showToast("鑾峰彇闄勪欢鍒楄〃澶辫触");
+        }
+      })
+      .catch(err => {
+        uni.hideLoading();
+        showToast("鑾峰彇闄勪欢鍒楄〃澶辫触");
+      });
+  };
+</script>
+
+<style scoped lang="scss">
+  @import "../../../styles/sales-common.scss";
+
+  .file-list-page {
+    min-height: 100vh;
+    background: #f8f9fa;
+    padding-bottom: 100rpx;
+  }
+
+  .file-list-container {
+    padding: 20rpx;
+  }
+
+  .file-list {
+    background: #ffffff;
+    border-radius: 8rpx;
+    overflow: hidden;
+    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+  }
+
+  .file-item {
+    display: flex;
+    align-items: center;
+    padding: 20rpx;
+    border-bottom: 1rpx solid #f0f0f0;
+
+    &:last-child {
+      border-bottom: none;
+    }
+  }
+
+  .file-icon {
+    width: 56rpx;
+    height: 56rpx;
+    border-radius: 8rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-right: 20rpx;
+
+    &.blue {
+      background: #409eff;
+    }
+
+    &.green {
+      background: #67c23a;
+    }
+
+    &.red {
+      background: #f56c6c;
+    }
+
+    &.orange {
+      background: #e6a23c;
+    }
+
+    &.gray {
+      background: #909399;
+    }
+
+    &.purple {
+      background: #909399;
+    }
+
+    &.yellow {
+      background: #e6a23c;
+    }
+  }
+
+  .file-info {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .file-name {
+    display: block;
+    font-size: 16px;
+    color: #303133;
+    margin-bottom: 8rpx;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .file-meta {
+    display: block;
+    font-size: 12px;
+    color: #909399;
+  }
+
+  .file-actions {
+    display: flex;
+    gap: 12rpx;
+  }
+
+  .empty-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 100rpx 0;
+    background: #ffffff;
+    border-radius: 8rpx;
+    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+  }
+
+  .empty-text {
+    font-size: 14px;
+    color: #909399;
+    margin-top: 20rpx;
+  }
+
+  .upload-button {
+    position: fixed;
+    bottom: 40rpx;
+    right: 40rpx;
+    width: 130rpx;
+    height: 130rpx;
+    border-radius: 50%;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4);
+    z-index: 1000;
+  }
+
+  .upload-text {
+    font-size: 10px;
+    color: #ffffff;
+    margin-top: 4rpx;
+  }
+
+  .upload-progress {
+    padding: 40rpx 0;
+  }
+
+  .upload-progress-text {
+    display: block;
+    text-align: center;
+    margin-top: 20rpx;
+    font-size: 14px;
+    color: #606266;
+  }
+</style>
\ No newline at end of file
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/index.vue b/src/pages/safeProduction/safetyTrainingAssessment/index.vue
new file mode 100644
index 0000000..905f9e8
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/index.vue
@@ -0,0 +1,448 @@
+<template>
+  <view class="sales-accoun">
+    <!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
+    <PageHeader title="瀹夊叏鍩硅鑰冩牳"
+                @back="goBack" />
+    <!-- 鎼滅储鍜岀瓫閫夊尯鍩� -->
+    <view class="search-section">
+      <view class="search-bar">
+        <view @click="selectDate"
+              class="search-input">
+          <view class="search-text">{{ searchKeyword? searchKeyword : '璇烽�夋嫨鍩硅鏃ユ湡' }}</view>
+        </view>
+        <view class="filter-button"
+              @click="clearDate">
+          <u-icon name="close-circle"
+                  size="24"
+                  color="#999"></u-icon>
+        </view>
+      </view>
+      <!-- 鍩硅璁板綍鎸夐挳 -->
+      <view class="record-button">
+        <u-button type="info"
+                  @click="viewTrainingRecord">鍩硅璁板綍</u-button>
+      </view>
+    </view>
+    <!-- 鏍囩椤� -->
+    <view class="tabs-section">
+      <up-tabs v-model="searchForm.state"
+               :list="tabList"
+               itemStyle="width: 33%;height: 80rpx;"
+               @change="tabhandleQuery">
+      </up-tabs>
+    </view>
+    <!-- 鍩硅璁板綍鍒楄〃 -->
+    <view class="ledger-list"
+          v-if="trainingList.length > 0">
+      <view v-for="(item, index) in trainingList"
+            :key="index">
+        <view class="ledger-item">
+          <view class="item-header">
+            <view class="item-left">
+              <view class="document-icon">
+                <up-icon name="file-text"
+                         size="16"
+                         color="#ffffff"></up-icon>
+              </view>
+              <text class="item-id">璇剧▼缂栧彿锛歿{ item.courseCode }}</text>
+            </view>
+          </view>
+          <up-divider></up-divider>
+          <view class="item-details">
+            <view class="detail-row">
+              <text class="detail-label">璇剧▼缂栧彿</text>
+              <text class="detail-value">{{ item.courseCode || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鍩硅鏃ユ湡</text>
+              <text class="detail-value">{{ item.trainingDate || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">寮�濮嬫椂闂�</text>
+              <text class="detail-value">{{ item.openingTime || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">缁撴潫鏃堕棿</text>
+              <text class="detail-value">{{ item.endTime || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鍩硅鐩爣</text>
+              <text class="detail-value">{{ item.trainingObjectives || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鍙傚姞瀵硅薄</text>
+              <text class="detail-value">{{ item.participants || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鍩硅鍐呭</text>
+              <text class="detail-value">{{ item.trainingContent || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鍩硅璁插笀</text>
+              <text class="detail-value">{{ item.trainingLecturer || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">椤圭洰瀛﹀垎</text>
+              <text class="detail-value">{{ item.projectCredits || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鍩硅鏂瑰紡</text>
+              <text class="detail-value">{{ getTrainingModeLabel(item.trainingMode) || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鍩硅鍦扮偣</text>
+              <text class="detail-value">{{ item.placeTraining || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">璇炬椂</text>
+              <text class="detail-value">{{ item.classHour || '-' }}</text>
+            </view>
+          </view>
+          <!-- 鎸夐挳鍖哄煙 -->
+          <view class="action-buttons">
+            <u-button type="primary"
+                      size="small"
+                      class="action-btn"
+                      :disabled="item.state !== 0"
+                      @click="editVisit(item)">
+              缂栬緫
+            </u-button>
+            <u-button type="info"
+                      size="small"
+                      class="action-btn"
+                      @click="viewFileList(item)">
+              闄勪欢
+            </u-button>
+            <u-button type="error"
+                      size="small"
+                      class="action-btn"
+                      @click="deleteVisit(item)">
+              鍒犻櫎
+            </u-button>
+          </view>
+          <view class="action-buttons">
+            <u-button type="warning"
+                      size="small"
+                      class="action-btn"
+                      :disabled="item.state !== 1"
+                      @click="signIn(item)">
+              绛惧埌
+            </u-button>
+            <u-button type="success"
+                      size="small"
+                      class="action-btn"
+                      :disabled="item.state === 0"
+                      @click="viewResultDetail(item)">
+              缁撴灉鏄庣粏
+            </u-button>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view v-else
+          class="no-data">
+      <text>鏆傛棤鍩硅璁板綍</text>
+    </view>
+    <up-datetime-picker :show="trainingDateVisible"
+                        mode="date"
+                        @confirm="handleDateConfirm"
+                        @cancel="handleDateCancel"
+                        title="閫夋嫨鍩硅鏃ユ湡" />
+    <!-- 娴姩鏂板鎸夐挳 -->
+    <view class="fab-button"
+          @click="addVisit">
+      <up-icon name="plus"
+               size="24"
+               color="#ffffff"></up-icon>
+    </view>
+  </view>
+</template>
+
+<script setup>
+  import { ref, onMounted, reactive } from "vue";
+
+  import { onShow } from "@dcloudio/uni-app";
+  import PageHeader from "@/components/PageHeader.vue";
+  import {
+    safeTrainingListPage,
+    safeTrainingDel,
+    safeTrainingSign,
+    safeTrainingGet,
+  } from "@/api/safeProduction/safetyTrainingAssessment";
+  import useUserStore from "@/store/modules/user";
+  import { useDict } from "@/utils/dict";
+  import dayjs from "dayjs";
+  // 鏇挎崲 toast 鏂规硶
+  defineOptions({ name: "safety-training-index" });
+  const showToast = message => {
+    uni.showToast({
+      title: message,
+      icon: "none",
+    });
+  };
+
+  const userStore = useUserStore();
+
+  // 鑾峰彇瀛楀吀鏁版嵁
+  const { safe_training_methods } = useDict("safe_training_methods");
+
+  // 鎼滅储鍏抽敭璇�
+  const searchKeyword = ref("");
+  // 鏃ユ湡閫夋嫨鍣ㄧ姸鎬�
+  const trainingDateVisible = ref(false);
+
+  const tabList = reactive([
+    { name: "鏈紑濮�", value: 0 },
+    { name: "杩涜涓�", value: 1 },
+    { name: "宸茬粨鏉�", value: 2 },
+  ]);
+  // 鎼滅储琛ㄥ崟
+  const searchForm = ref({
+    state: 0, // 榛樿鏄剧ず宸茬粨鏉�
+    trainingDate: "",
+  });
+  const tabhandleQuery = val => {
+    searchForm.value.state = val.value;
+    getList();
+  };
+  const getTrainingModeLabel = mode => {
+    if (!safe_training_methods || !Array.isArray(safe_training_methods.value)) {
+      return mode || "-";
+    }
+    const dictItem = safe_training_methods.value.find(
+      item => String(item.value) === String(mode)
+    );
+    return dictItem ? dictItem.label : mode || "-";
+  };
+
+  // 鍩硅璁板綍鏁版嵁
+  const trainingList = ref([]);
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
+  const viewFileList = item => {
+    uni.setStorageSync("safetyTrainingFileId", item.id);
+    uni.navigateTo({
+      url: "/pages/safeProduction/safetyTrainingAssessment/fileList",
+    });
+  };
+  const currentUserId = ref("");
+  // 绛惧埌鍔熻兘
+  const signIn = item => {
+    uni.showModal({
+      title: "鎻愮ず",
+      content: "纭绛惧埌鍚楋紵",
+      success: function (res) {
+        if (res.confirm) {
+          safeTrainingSign({
+            safeTrainingId: item.id,
+            userId: currentUserId.value,
+          })
+            .then(res => {
+              if (res.code === 200) {
+                uni.showToast({ title: "绛惧埌鎴愬姛", icon: "success" });
+                setTimeout(() => {}, 1000);
+              } else {
+                uni.showToast({ title: res.msg || "绛惧埌澶辫触", icon: "none" });
+              }
+            })
+            .catch(() => {
+              uni.showToast({ title: "绛惧埌澶辫触锛岃閲嶈瘯", icon: "none" });
+            });
+        }
+      },
+    });
+  };
+
+  // 鏌ョ湅缁撴灉鏄庣粏
+  const viewResultDetail = item => {
+    uni.setStorageSync("safetyTrainingResultId", item.id);
+    uni.setStorageSync("safetyTrainingResultNums", item.nums);
+
+    uni.navigateTo({
+      url: "/pages/safeProduction/safetyTrainingAssessment/resultDetail",
+    });
+  };
+
+  // 鏌ョ湅鍩硅璁板綍
+  const viewTrainingRecord = () => {
+    uni.navigateTo({
+      url: "/pages/safeProduction/safetyTrainingAssessment/record",
+    });
+  };
+  // 娓呴櫎鏃ユ湡閫夋嫨
+  const clearDate = () => {
+    searchKeyword.value = "";
+    searchForm.value.trainingDate = "";
+    getList();
+  };
+  // 鏄剧ず鏃ユ湡閫夋嫨鍣�
+  const selectDate = () => {
+    trainingDateVisible.value = true;
+  };
+
+  // 澶勭悊鏃ユ湡閫夋嫨纭
+  const handleDateConfirm = e => {
+    searchKeyword.value = dayjs(e.value).format("YYYY-MM-DD");
+    searchForm.value.trainingDate = dayjs(e.value).format("YYYY-MM-DD");
+    trainingDateVisible.value = false;
+    getList();
+  };
+
+  // 澶勭悊鏃ユ湡閫夋嫨鍙栨秷
+  const handleDateCancel = () => {
+    trainingDateVisible.value = false;
+  };
+
+  // 鏌ヨ鍒楄〃
+  const getList = () => {
+    showLoadingToast("鍔犺浇涓�...");
+    const params = {
+      current: -1,
+      size: -1,
+      trainingDate: searchForm.value.trainingDate,
+      state: searchForm.value.state,
+    };
+    safeTrainingListPage(params)
+      .then(res => {
+        trainingList.value = res.records || res.data?.records || [];
+        closeToast();
+      })
+      .catch(() => {
+        closeToast();
+        showToast("鑾峰彇鏁版嵁澶辫触");
+      });
+  };
+
+  // 鏄剧ず鍔犺浇鎻愮ず
+  const showLoadingToast = message => {
+    uni.showLoading({
+      title: message,
+      mask: true,
+    });
+  };
+
+  // 鍏抽棴鎻愮ず
+  const closeToast = () => {
+    uni.hideLoading();
+  };
+
+  // 鏂板鍩硅
+  const addVisit = () => {
+    uni.setStorageSync("safetyTraining", {});
+    uni.navigateTo({
+      url: "/pages/safeProduction/safetyTrainingAssessment/detail",
+    });
+  };
+  // 缂栬緫鍩硅
+  const editVisit = item => {
+    uni.setStorageSync("safetyTraining", item);
+    uni.navigateTo({
+      url: "/pages/safeProduction/safetyTrainingAssessment/detail",
+    });
+  };
+  // 鍒犻櫎鍩硅
+  const deleteVisit = item => {
+    uni.showModal({
+      title: "鍒犻櫎纭",
+      content: `纭畾瑕佸垹闄よ鍩硅璁板綍鍚楋紵`,
+      success: res => {
+        if (res.confirm) {
+          deleteClientVisit(item.id);
+        }
+      },
+    });
+  };
+  // 鍒犻櫎鍩硅璁板綍
+  const deleteClientVisit = id => {
+    showLoadingToast("鍒犻櫎涓�...");
+    safeTrainingDel([id])
+      .then(() => {
+        closeToast();
+        showToast("鍒犻櫎鎴愬姛");
+        getList();
+      })
+      .catch(() => {
+        closeToast();
+        showToast("鍒犻櫎澶辫触");
+      });
+  };
+  // 鏌ョ湅璇︽儏
+  const viewDetail = item => {
+    uni.setStorageSync("safetyTraining", item);
+    uni.navigateTo({
+      url: "/pages/safeProduction/safetyTrainingAssessment/view",
+    });
+  };
+
+  onMounted(() => {
+    userStore.getInfo().then(res => {
+      currentUserId.value = res.user.userId;
+    });
+    // currentUserId
+    getList();
+  });
+
+  onShow(() => {
+    getList();
+  });
+</script>
+
+<style scoped lang="scss">
+  @import "../../../styles/sales-common.scss";
+
+  // 椤甸潰鐗瑰畾鐨勬牱寮忚鐩�
+  .sales-accoun {
+    min-height: 100vh;
+    background: #f8f9fa;
+    position: relative;
+    padding-bottom: 80px;
+  }
+
+  // 鍩硅璁板綍鎸夐挳
+  .record-button {
+    margin-top: 10px;
+    text-align: center;
+  }
+
+  // 鐗瑰畾鐨勫浘鏍囨牱寮�
+  .document-icon {
+    background: #667eea; // 淇濇寔椤甸潰鐗规湁鐨勮儗鏅壊
+  }
+
+  // 鐗规湁鏍峰紡
+  .visit-status {
+    display: flex;
+    align-items: center;
+  }
+
+  .detail-value {
+    word-break: break-all; // 淇濈暀椤甸潰鐗规湁鐨勬枃鏈崲琛屾牱寮�
+  }
+
+  // 鐗瑰畾鐨勬诞鍔ㄦ寜閽牱寮�
+  .fab-button {
+    background: #667eea; // 淇濇寔椤甸潰鐗规湁鐨勮儗鏅壊
+    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3); // 淇濇寔椤甸潰鐗规湁鐨勯槾褰辨晥鏋�
+  }
+  .action-buttons {
+    gap: 4px;
+  }
+  .action-buttons {
+    padding: 0 0 10rpx 0;
+  }
+
+  .tabs-section {
+    background: #fff;
+    margin-bottom: 1rem;
+    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.05);
+  }
+  .search-text {
+    // font-size: 24rpx;
+    color: #a6a6a6;
+    height: 70rpx;
+    line-height: 70rpx;
+    margin-left: 20rpx;
+  }
+</style>
\ No newline at end of file
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/record.vue b/src/pages/safeProduction/safetyTrainingAssessment/record.vue
new file mode 100644
index 0000000..825b7d6
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/record.vue
@@ -0,0 +1,546 @@
+<template>
+  <view class="training-record">
+    <!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
+    <PageHeader title="鍩硅璁板綍"
+                @back="goBack" />
+    <!-- 鎼滅储鍜岀瓫閫夊尯鍩� -->
+    <view class="search-section">
+      <view class="search-bar">
+        <view class="search-input">
+          <up-input class="search-text"
+                    placeholder="浜哄憳鍚嶇О鎼滅储"
+                    v-model="searchForm.searchText"
+                    @change="searchName"
+                    clearable />
+        </view>
+        <view class="filter-button"
+              @click="searchName">
+          <u-icon name="search"
+                  size="24"
+                  color="#999"></u-icon>
+        </view>
+      </view>
+    </view>
+    <!-- 浜哄憳鍗$墖鍒楄〃 -->
+    <view class="user-card-list"
+          v-if="userList.length > 0">
+      <view v-for="(user, index) in userList"
+            :key="index"
+            class="user-card">
+        <!-- 鍗$墖澶撮儴 -->
+        <view class="card-header"
+              @click="toggleUserCard(index)">
+          <view class="header-left">
+            <text class="user-name">{{ user.nickName }}</text>
+            <text class="user-dept">鎵�灞烇細{{ user.deptNames || '-' }}</text>
+            <text class="user-dept">鑱旂郴鏂瑰紡锛歿{ user.phonenumber || '-' }}</text>
+            <!-- 鍩硅缁熻淇℃伅 -->
+          </view>
+          <u-icon :name="expandedUsers[index] ? 'arrow-up' : 'arrow-down'"
+                  size="20"
+                  color="#999"></u-icon>
+        </view>
+        <!-- 鍗$墖鍐呭锛堟姌鍙犻儴鍒嗭級 -->
+        <view class="card-content"
+              v-if="expandedUsers[index]">
+          <!-- 骞翠唤绛涢�� -->
+          <view class="year-filter-section">
+            <!-- <text class="filter-label">骞翠唤绛涢��</text> -->
+            <view class="year-options">
+              <u-tag v-for="year in yearOptions"
+                     :key="year"
+                     :text="year"
+                     :type="userYearFilters[user.userId] === year.toString() ? 'primary' : 'info'"
+                     @click="() => {
+                       userYearFilters[user.userId] = year.toString();
+                       filterUserCourses(user.userId);
+                     }"
+                     :class="{ active: userYearFilters[user.userId] === year.toString() }"
+                     style="margin-right: 8px; margin-bottom: 8px;"></u-tag>
+            </view>
+          </view>
+          <!-- 鍩硅璇剧▼鍒楄〃 -->
+          <view class="course-list"
+                v-if="userCourses[user.userId] && userCourses[user.userId].length > 0">
+            <view class="user-stats"
+                  v-if="userStats[user.userId]">
+              <text class="stat-item">鍩硅娆℃暟: {{ userStats[user.userId].total }}</text>
+              <text class="stat-item success">鍚堟牸: {{ userStats[user.userId].qualified }}</text>
+              <text class="stat-item danger">涓嶅悎鏍�: {{ userStats[user.userId].unqualified }}</text>
+            </view>
+            <view v-for="(course, courseIndex) in userCourses[user.userId]"
+                  :key="courseIndex">
+              <view class="course-item"
+                    v-if="userYearFilters[user.userId] === '鍏ㄩ儴' || course.trainingDate.includes(userYearFilters[user.userId])">
+                <view class="course-header">
+                  <text class="course-date">{{ course.trainingDate || '-' }}</text>
+                  <u-tag :type="course.examinationResults === '鍚堟牸' ? 'success' : 'error'">
+                    {{ course.examinationResults || '-' }}
+                  </u-tag>
+                </view>
+                <view class="course-info">
+                  <text class="info-label">鍩硅鍐呭锛�</text>
+                  <text class="info-value">{{ course.trainingContent || '-' }}</text>
+                </view>
+                <view class="course-info">
+                  <text class="info-label">鍩硅璇炬椂锛�</text>
+                  <text class="info-value">{{ course.classHour || '-' }}</text>
+                </view>
+              </view>
+            </view>
+            <!-- 绛涢�夊悗鏃犳暟鎹� -->
+            <view v-if="userYearFilters[user.userId] === '鍏ㄩ儴' ? userCourses[user.userId].length === 0 : userCourses[user.userId].filter(c => c.trainingDate.includes(userYearFilters[user.userId])).length === 0"
+                  class="empty-course">
+              <text class="empty-text">{{ userYearFilters[user.userId] === '鍏ㄩ儴' ? '鏆傛棤鍩硅璁板綍' : '璇ュ勾浠芥殏鏃犲煿璁褰�' }}</text>
+            </view>
+          </view>
+          <!-- 绌虹姸鎬� -->
+          <view v-else
+                class="empty-course">
+            <text class="empty-text">鏆傛棤鍩硅璁板綍</text>
+          </view>
+        </view>
+        <!-- 瀵煎嚭鎸夐挳 -->
+        <!-- <view class="course-export">
+          <u-button type="primary"
+                    size="small"
+                    @click="exportUserRecord(user.userId)">瀵煎嚭璁板綍</u-button>
+        </view> -->
+      </view>
+    </view>
+    <view v-else
+          class="empty-state">
+      <up-icon name="people"
+               size="64"
+               color="#c0c4cc"></up-icon>
+      <text class="empty-text">鏆傛棤浜哄憳鏁版嵁</text>
+    </view>
+    <!-- 绌虹姸鎬� -->
+    <!-- 骞翠唤閫夋嫨鍣� -->
+    <up-datetime-picker :show="yearPickerVisible"
+                        mode="year"
+                        @confirm="handleYearConfirm"
+                        @cancel="yearPickerVisible = false"
+                        title="閫夋嫨骞翠唤" />
+  </view>
+</template>
+
+<script setup>
+  import { ref, onMounted, reactive } from "vue";
+  import { onShow } from "@dcloudio/uni-app";
+  import PageHeader from "@/components/PageHeader.vue";
+  import { safeTrainingDetailListPage } from "@/api/safeProduction/safetyTrainingAssessment";
+  import { userListNoPage } from "@/api/system/user.js";
+  import { getToken } from "@/utils/auth";
+  import config from "@/config";
+
+  // 椤甸潰鐘舵��
+  const userList = ref([]);
+  const userCourses = ref({}); // 瀛樺偍姣忎釜鐢ㄦ埛鐨勫煿璁绋�
+  const userStats = ref({}); // 瀛樺偍姣忎釜鐢ㄦ埛鐨勫煿璁粺璁′俊鎭�
+  const expandedUsers = ref([]); // 鎺у埗鐢ㄦ埛鍗$墖灞曞紑鐘舵��
+  const loading = ref(false);
+  const courseLoading = ref({}); // 鎺у埗姣忎釜鐢ㄦ埛鐨勮绋嬪姞杞界姸鎬�
+  const userYearFilters = ref({}); // 瀛樺偍姣忎釜鐢ㄦ埛鐨勫勾浠界瓫閫夋潯浠�
+  const yearOptions = ref([]); // 骞翠唤閫夐」锛堜粖骞村拰杩囧幓涓夊勾锛�
+
+  // 鎼滅储琛ㄥ崟
+  const searchForm = reactive({
+    searchText: "",
+    invoiceDate: "",
+  });
+
+  // 骞翠唤閫夋嫨鍣ㄧ姸鎬�
+  const yearPickerVisible = ref(false);
+
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
+
+  // 鐢熸垚骞翠唤閫夐」
+  const generateYearOptions = () => {
+    const currentYear = new Date().getFullYear();
+    const options = [];
+    // 娣诲姞"鍏ㄩ儴"閫夐」
+    options.push("鍏ㄩ儴");
+    for (let i = 0; i < 4; i++) {
+      options.push(currentYear - i);
+    }
+    yearOptions.value = options;
+  };
+
+  // 鎼滅储浜哄憳鍚嶇О
+  const searchName = () => {
+    getUserList();
+  };
+
+  // 鏄剧ず骞翠唤閫夋嫨鍣�
+  const showYearPicker = () => {
+    yearPickerVisible.value = true;
+  };
+
+  // 澶勭悊骞翠唤閫夋嫨纭
+  const handleYearConfirm = e => {
+    searchForm.invoiceDate = e.value;
+    yearPickerVisible.value = false;
+  };
+
+  // 鎸夊勾浠芥悳绱�
+  const searchDate = () => {
+    // 閬嶅巻鎵�鏈夊睍寮�鐨勭敤鎴峰崱鐗囷紝閲嶆柊鍔犺浇鍩硅璁板綍
+    userList.value.forEach((user, index) => {
+      if (expandedUsers.value[index]) {
+        getUserCourses(user.userId, index);
+      }
+    });
+  };
+
+  // 鑾峰彇浜哄憳鍒楄〃
+  const getUserList = () => {
+    loading.value = true;
+    userListNoPage()
+      .then(res => {
+        loading.value = false;
+        if (res.data && res.data.length > 0) {
+          let users = res.data;
+          // 濡傛灉鏈夋悳绱㈠叧閿瘝锛岃繘琛岀瓫閫�
+          if (searchForm.searchText) {
+            users = users.filter(user =>
+              user.nickName.includes(searchForm.searchText)
+            );
+          }
+          userList.value = users;
+          // 鍒濆鍖栨墍鏈夊崱鐗囦负鏀惰捣鐘舵��
+          expandedUsers.value = new Array(userList.value.length).fill(false);
+        } else {
+          userList.value = [];
+          expandedUsers.value = [];
+        }
+      })
+      .catch(() => {
+        loading.value = false;
+        uni.showToast({ title: "鑾峰彇浜哄憳鍒楄〃澶辫触", icon: "none" });
+      });
+  };
+
+  // 鍒囨崲鐢ㄦ埛鍗$墖灞曞紑鐘舵��
+  const toggleUserCard = index => {
+    const user = userList.value[index];
+    expandedUsers.value[index] = !expandedUsers.value[index];
+
+    // 濡傛灉灞曞紑鍗$墖锛屽姞杞藉煿璁褰�
+    if (expandedUsers.value[index]) {
+      // 鍒濆鍖栧勾浠界瓫閫夋潯浠朵负"鍏ㄩ儴"
+      userYearFilters.value[user.userId] = "鍏ㄩ儴";
+      getUserCourses(user.userId, index);
+    }
+  };
+
+  // 绛涢�夌敤鎴峰煿璁绋�
+  const filterUserCourses = userId => {
+    if (!userYearFilters.value[userId]) return;
+    console.log("userYearFilters", userYearFilters.value);
+    const year = userYearFilters.value[userId];
+    const allCourses = userCourses.value[userId] || [];
+
+    // 绛涢�夋寚瀹氬勾浠界殑鍩硅璁板綍
+    let filteredCourses = allCourses;
+    if (year !== "鍏ㄩ儴") {
+      filteredCourses = allCourses.filter(
+        course => course.trainingDate && course.trainingDate.includes(year)
+      );
+    }
+    console.log("filteredCourses", filteredCourses);
+
+    // 鏇存柊缁熻淇℃伅
+    const total = filteredCourses.length;
+    const qualified = filteredCourses.filter(
+      course => course.examinationResults === "鍚堟牸"
+    ).length;
+    const unqualified = filteredCourses.filter(
+      course => course.examinationResults === "涓嶅悎鏍�"
+    ).length;
+    userStats.value[userId] = {
+      total,
+      qualified,
+      unqualified,
+    };
+  };
+
+  // 鑾峰彇鐢ㄦ埛鍩硅璇剧▼
+  const getUserCourses = (userId, index) => {
+    courseLoading.value[userId] = true;
+
+    const params = {
+      userId,
+      // 濡傛灉鏈夊勾浠界瓫閫夛紝娣诲姞骞翠唤鍙傛暟
+      // 杩欓噷闇�瑕佹牴鎹悗绔帴鍙g殑瀹為檯鍙傛暟鍚嶈繘琛岃皟鏁�
+    };
+
+    safeTrainingDetailListPage(params)
+      .then(res => {
+        courseLoading.value[userId] = false;
+        if (res.data && res.data.records) {
+          let courses = res.data.records;
+          // 濡傛灉鏈夊勾浠界瓫閫夛紝杩涜绛涢��
+          if (searchForm.invoiceDate) {
+            const year = searchForm.invoiceDate.substring(0, 4);
+            courses = courses.filter(
+              course => course.trainingDate && course.trainingDate.includes(year)
+            );
+          }
+          userCourses.value[userId] = courses;
+
+          // 璁$畻鍩硅缁熻淇℃伅
+          const total = courses.length;
+          const qualified = courses.filter(
+            course => course.examinationResults === "鍚堟牸"
+          ).length;
+          const unqualified = courses.filter(
+            course => course.examinationResults === "涓嶅悎鏍�"
+          ).length;
+
+          userStats.value[userId] = {
+            total,
+            qualified,
+            unqualified,
+          };
+        } else {
+          userCourses.value[userId] = [];
+          userStats.value[userId] = {
+            total: 0,
+            qualified: 0,
+            unqualified: 0,
+          };
+        }
+      })
+      .catch(() => {
+        courseLoading.value[userId] = false;
+        uni.showToast({ title: "鑾峰彇鍩硅璁板綍澶辫触", icon: "none" });
+      });
+  };
+
+  // 椤甸潰鍔犺浇
+  onMounted(() => {
+    // 鐢熸垚骞翠唤閫夐」
+    generateYearOptions();
+    getUserList();
+  });
+
+  // 椤甸潰鏄剧ず鏃跺埛鏂�
+  onShow(() => {
+    // 鍙互鍦ㄨ繖閲屾坊鍔犲埛鏂伴�昏緫
+  });
+</script>
+
+<style scoped lang="scss">
+  @import "../../../styles/sales-common.scss";
+  .training-record {
+    min-height: 100vh;
+    background: #f8f9fa;
+    padding-bottom: 20px;
+  }
+
+  // 骞翠唤閫夋嫨鍣�
+  .year-picker {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 10px 15px;
+    background: #f8f9fa;
+    border-radius: 4px;
+    margin-right: 10px;
+  }
+
+  .picker-text {
+    font-size: 14px;
+    color: #333;
+  }
+
+  // 浜哄憳鍗$墖鍒楄〃
+  .user-card-list {
+    padding: 10px;
+  }
+
+  // 浜哄憳鍗$墖
+  .user-card {
+    background: #fff;
+    border-radius: 8px;
+    margin-bottom: 12px;
+    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
+    overflow: hidden;
+    border: 1px solid #e8e8e8;
+  }
+
+  // 鍗$墖澶撮儴
+  .card-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 16px;
+    background: #f5f7fa;
+    border-bottom: 1px solid #e8e8e8;
+    cursor: pointer;
+  }
+
+  .header-left {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .user-name {
+    font-size: 16px;
+    font-weight: 600;
+    color: #333;
+    margin-bottom: 20rpx;
+  }
+
+  .user-dept {
+    font-size: 14px;
+    color: #666;
+    margin-bottom: 8px;
+  }
+
+  // 鍩硅缁熻淇℃伅
+  .user-stats {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 10px;
+    margin-bottom: 8px;
+  }
+
+  .stat-item {
+    font-size: 12px;
+    color: #555;
+    padding: 5px 10px;
+    background: #f0f2f5;
+    border-radius: 4px;
+    border: 1px solid #e0e0e0;
+    font-weight: 500;
+  }
+
+  .stat-item.success {
+    background: #e6f7ff;
+    color: #1890ff;
+    border-color: #91d5ff;
+  }
+
+  .stat-item.danger {
+    background: #fff1f0;
+    color: #ff4d4f;
+    border-color: #ffccc7;
+  }
+
+  // 骞翠唤绛涢�夊尯鍩�
+  .year-filter-section {
+    margin-bottom: 16px;
+    padding-bottom: 12px;
+    border-bottom: 1px solid #e8e8e8;
+  }
+
+  .filter-label {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+    margin-bottom: 8px;
+    display: block;
+  }
+
+  .year-options {
+    display: flex;
+    flex-wrap: wrap;
+  }
+
+  // 鍗$墖鍐呭
+  .card-content {
+    padding: 16px;
+  }
+
+  // 鍩硅璇剧▼鍒楄〃
+  .course-list {
+    margin-bottom: 16px;
+  }
+
+  // 璇剧▼椤�
+  .course-item {
+    background: #fafafa;
+    border-radius: 6px;
+    padding: 14px;
+    margin-bottom: 12px;
+    border: 1px solid #e8e8e8;
+  }
+
+  // 璇剧▼澶撮儴
+  .course-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 12px;
+    padding-bottom: 8px;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  .course-date {
+    font-size: 14px;
+    font-weight: 600;
+    color: #333;
+  }
+
+  // 璇剧▼淇℃伅
+  .course-info {
+    display: flex;
+    margin-bottom: 8px;
+    align-items: flex-start;
+  }
+
+  .info-label {
+    font-size: 14px;
+    color: #666;
+    width: 80px;
+    flex-shrink: 0;
+  }
+
+  .info-value {
+    font-size: 14px;
+    color: #333;
+    flex: 1;
+    line-height: 1.4;
+  }
+
+  // 璇剧▼瀵煎嚭鎸夐挳
+  .course-export {
+    display: flex;
+    justify-content: flex-end;
+  }
+
+  // 绌虹姸鎬�
+  .empty-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 60px 0;
+  }
+
+  .empty-course {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 30px 0;
+    color: #999;
+    font-size: 14px;
+  }
+
+  .empty-text {
+    font-size: 14px;
+    color: #999;
+    margin-top: 16px;
+  }
+  :deep(.u-tag--info) {
+    background-color: #c1c3c8;
+    border-width: 1px;
+    border-color: #c1c3c8;
+  }
+</style>
\ No newline at end of file
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/resultDetail.vue b/src/pages/safeProduction/safetyTrainingAssessment/resultDetail.vue
new file mode 100644
index 0000000..9fd10a1
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/resultDetail.vue
@@ -0,0 +1,388 @@
+<template>
+  <view class="result-detail">
+    <!-- 椤甸潰澶撮儴 -->
+    <PageHeader title="缁撴灉鏄庣粏"
+                @back="goBack" />
+    <!-- 鍐呭鍖哄煙 -->
+    <view class="content">
+      <!-- 璇剧▼璇︽儏 -->
+      <view class="section">
+        <view class="section-title">璇剧▼璇︽儏</view>
+        <view class="info-list">
+          <view class="info-item">
+            <text class="info-label">璇剧▼缂栧彿</text>
+            <text class="info-value">{{ currentTraining.courseCode || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅鍐呭</text>
+            <text class="info-value">{{ currentTraining.trainingContent || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鐘舵��</text>
+            <text class="info-value">
+              <u-tag :type="currentTraining.state === 0 ? 'success' : (currentTraining.state === 1 ? 'warning' : 'info')">
+                {{ currentTraining.state === 0 ? '鏈紑濮�' : (currentTraining.state === 1 ? '杩涜涓�' : '宸茬粨鏉�') }}
+              </u-tag>
+            </text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅璁插笀</text>
+            <text class="info-value">{{ currentTraining.trainingLecturer || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅寮�濮嬫椂闂�</text>
+            <text class="info-value">{{ currentTraining.trainingDate + ' ' + currentTraining.openingTime || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅缁撴潫鏃堕棿</text>
+            <text class="info-value">{{ currentTraining.trainingDate + ' ' + currentTraining.endTime || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅鐩爣</text>
+            <text class="info-value">{{ currentTraining.trainingObjectives || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍙傚姞瀵硅薄</text>
+            <text class="info-value">{{ currentTraining.participants || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅鏂瑰紡</text>
+            <text class="info-value">{{ getTrainingModeLabel(currentTraining.trainingMode) || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅鍦扮偣</text>
+            <text class="info-value">{{ currentTraining.placeTraining || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">璇炬椂</text>
+            <text class="info-value">{{ currentTraining.classHour || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">璇剧▼瀛﹀垎</text>
+            <text class="info-value">{{ currentTraining.projectCredits || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鎶ュ悕浜烘暟</text>
+            <text class="info-value">{{ currentTraining.nums || '-' }}</text>
+          </view>
+        </view>
+      </view>
+      <!-- 璇剧▼璇勪环 -->
+      <view class="section">
+        <view class="section-title">璇剧▼璇勪环</view>
+        <u-form ref="formRef"
+                label-width="90"
+                :model="endform">
+          <u-form-item label="璇勪环浜�">
+            <u-input v-model="endform.assessmentUserName"
+                     disabled
+                     placeholder="璇烽�夋嫨璇勪环浜�" />
+          </u-form-item>
+          <u-form-item label="璇勪环鏃堕棿">
+            <u-input v-model="endform.assessmentDate"
+                     disabled
+                     placeholder="璇烽�夋嫨璇勪环鏃堕棿" />
+          </u-form-item>
+          <u-form-item label="鑰冩牳鏂瑰紡">
+            <u-input v-model="endform.assessmentMethod"
+                     placeholder="璇疯緭鍏ヨ�冩牳鏂瑰紡" />
+          </u-form-item>
+          <u-form-item label="缁煎悎璇勪环">
+            <u-input v-model="endform.comprehensiveAssessment"
+                     placeholder="璇疯緭鍏ユ湰娆¤绋嬬患鍚堣瘎浠�" />
+          </u-form-item>
+          <u-form-item label="鍩硅鎽樿">
+            <u-textarea v-model="endform.trainingAbstract"
+                        :rows="4"
+                        placeholder="璇疯緭鍏ュ煿璁憳瑕�" />
+          </u-form-item>
+        </u-form>
+      </view>
+      <!-- 鑰冩牳鍒楄〃 -->
+      <view class="section">
+        <view class="section-title">鑰冩牳鍒楄〃</view>
+        <view class="assessment-list">
+          <view v-for="(item, index) in endform.safeTrainingDetailsDtoList"
+                :key="index"
+                class="assessment-item">
+            <view class="assessment-info">
+              <view class="info-row">
+                <text class="label">濮撳悕锛�</text>
+                <text class="value">{{ item.nickName || '-' }}</text>
+              </view>
+              <view class="info-row">
+                <text class="label">鐢佃瘽鍙风爜锛�</text>
+                <text class="value">{{ item.phonenumber || '-' }}</text>
+              </view>
+            </view>
+            <view class="assessment-result">
+              <text class="result-label">鑰冩牳缁撴灉锛�</text>
+              <u-radio-group v-model="endform.safeTrainingDetailsDtoList[index].examinationResults"
+                             :key="index"
+                             size="default">
+                <u-radio label="鍚堟牸"
+                         name="鍚堟牸"></u-radio>
+                <u-radio label="涓嶅悎鏍�"
+                         name="涓嶅悎鏍�"></u-radio>
+              </u-radio-group>
+            </view>
+          </view>
+        </view>
+      </view>
+      <!-- 鎻愪氦鎸夐挳 -->
+      <view class="submit-btn">
+        <u-button type="primary"
+                  @click="submitForm"
+                  :loading="loading">鎻愪氦</u-button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+  import { ref, onMounted } from "vue";
+  import PageHeader from "@/components/PageHeader.vue";
+  import { onLoad } from "@dcloudio/uni-app";
+  import { useDict } from "@/utils/dict";
+  import dayjs from "dayjs";
+  import useUserStore from "@/store/modules/user";
+  import {
+    safeTrainingGet,
+    safeTrainingSave,
+  } from "@/api/safeProduction/safetyTrainingAssessment";
+
+  // 鑾峰彇瀛楀吀鏁版嵁
+  const { safe_training_methods } = useDict("safe_training_methods");
+
+  // 椤甸潰鐘舵��
+  const loading = ref(false);
+  const currentTraining = ref({});
+  const endform = ref({
+    assessmentUserId: "",
+    assessmentUserName: "",
+    assessmentMethod: "",
+    assessmentDate: "",
+    comprehensiveAssessment: "",
+    trainingAbstract: "",
+    safeTrainingFileList: [],
+    safeTrainingDetailsDtoList: [],
+  });
+
+  // 鑾峰彇鍩硅鏂瑰紡鏍囩
+  const getTrainingModeLabel = val => {
+    if (!safe_training_methods || !Array.isArray(safe_training_methods.value)) {
+      return val;
+    }
+    const item = safe_training_methods.value.find(
+      i => String(i.value) === String(val)
+    );
+    return item ? item.label : val;
+  };
+
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
+  // 鎻愪氦琛ㄥ崟
+  const submitForm = () => {
+    // 楠岃瘉鑰冩牳缁撴灉
+    for (let i = 0; i < endform.value.safeTrainingDetailsDtoList.length; i++) {
+      const item = endform.value.safeTrainingDetailsDtoList[i];
+      if (!item.examinationResults) {
+        uni.showToast({
+          title: `璇烽�夋嫨${item.nickName}鐨勮�冩牳缁撴灉`,
+          icon: "none",
+        });
+        return;
+      }
+    }
+
+    loading.value = true;
+    safeTrainingSave(endform.value)
+      .then(res => {
+        loading.value = false;
+        if (res.code === 200) {
+          uni.showToast({ title: "鎻愪氦鎴愬姛", icon: "success" });
+          setTimeout(() => {
+            goBack();
+          }, 500);
+        } else {
+          uni.showToast({ title: res.msg || "鎻愪氦澶辫触", icon: "none" });
+        }
+      })
+      .catch(() => {
+        loading.value = false;
+        uni.showToast({ title: "鎻愪氦澶辫触锛岃閲嶈瘯", icon: "none" });
+      });
+  };
+
+  // 椤甸潰鍔犺浇
+  onLoad(() => {
+    const trainingId = uni.getStorageSync("safetyTrainingResultId");
+    const trainingNums = uni.getStorageSync("safetyTrainingResultNums");
+
+    if (trainingId) {
+      getTrainingDetail(trainingId, trainingNums);
+    }
+  });
+  const userStore = useUserStore();
+  const getuserInfo = () => {
+    const userInfo = {
+      id: "",
+      nickName: "",
+    };
+    userStore.getInfo().then(res => {
+      userInfo.id = res.user.userId;
+      userInfo.nickName = res.user.nickName;
+      endform.value.assessmentUserName = res.user.nickName;
+      endform.value.assessmentUserId = res.user.userId;
+    });
+    return userInfo;
+  };
+
+  // 鑾峰彇鍩硅璇︽儏
+  const getTrainingDetail = (id, trainingNums) => {
+    loading.value = true;
+    safeTrainingGet({ id })
+      .then(res => {
+        loading.value = false;
+        if (res.code === 200) {
+          currentTraining.value = res.data;
+          currentTraining.value.nums = trainingNums;
+
+          endform.value = { ...res.data };
+          // 璁剧疆榛樿鍊�
+          if (!endform.value.assessmentUserId) {
+            getuserInfo();
+          }
+
+          endform.value.assessmentDate = endform.value.assessmentDate
+            ? dayjs(endform.value.assessmentDate).format("YYYY-MM-DD")
+            : dayjs().format("YYYY-MM-DD");
+          endform.value.safeTrainingDetailsDtoList =
+            endform.value.safeTrainingDetailsDtoList || [];
+        } else {
+          uni.showToast({ title: "鑾峰彇璇︽儏澶辫触", icon: "none" });
+        }
+      })
+      .catch(() => {
+        loading.value = false;
+        uni.showToast({ title: "鑾峰彇璇︽儏澶辫触", icon: "none" });
+      });
+  };
+</script>
+
+<style scoped lang="scss">
+  .result-detail {
+    min-height: 100vh;
+    background: #f8f9fa;
+  }
+
+  .content {
+    padding: 16px;
+  }
+
+  .section {
+    background: #fff;
+    border-radius: 8px;
+    padding: 16px;
+    margin-bottom: 16px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+  }
+
+  .section-title {
+    font-size: 16px;
+    font-weight: 600;
+    margin-bottom: 16px;
+    color: #333;
+  }
+
+  .info-list {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .info-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    padding-bottom: 12px;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  .info-item:last-child {
+    border-bottom: none;
+    padding-bottom: 0;
+  }
+
+  .info-label {
+    font-size: 14px;
+    color: #666;
+    width: 100px;
+  }
+
+  .info-value {
+    flex: 1;
+    font-size: 14px;
+    color: #333;
+    text-align: right;
+  }
+
+  .assessment-list {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .assessment-item {
+    background: #f8f9fa;
+    border-radius: 8px;
+    padding: 16px;
+    border: 1px solid #e8e8e8;
+  }
+
+  .assessment-info {
+    margin-bottom: 12px;
+  }
+
+  .info-row {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+  }
+
+  .info-row:last-child {
+    margin-bottom: 0;
+  }
+
+  .label {
+    font-size: 14px;
+    color: #666;
+    min-width: 80px;
+  }
+
+  .value {
+    font-size: 14px;
+    color: #333;
+    flex: 1;
+  }
+
+  .assessment-result {
+    display: flex;
+    align-items: center;
+    padding-top: 8px;
+    border-top: 1px solid #e8e8e8;
+  }
+
+  .result-label {
+    font-size: 14px;
+    color: #666;
+    min-width: 80px;
+  }
+
+  .submit-btn {
+    margin-top: 24px;
+    margin-bottom: 32px;
+  }
+</style>
\ No newline at end of file
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/view.vue b/src/pages/safeProduction/safetyTrainingAssessment/view.vue
new file mode 100644
index 0000000..df8b991
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/view.vue
@@ -0,0 +1,171 @@
+<template>
+  <view class="danger-investigation-view">
+    <PageHeader title="鍩硅璇︽儏"
+                @back="goBack" />
+    <!-- 鍐呭瀹瑰櫒 -->
+    <view class="detail-content">
+      <!-- 鍩硅淇℃伅 -->
+      <view class="info-section">
+        <!-- <view class="section-title">鍩硅淇℃伅</view> -->
+        <view class="info-grid">
+          <view class="info-item">
+            <text class="info-label">璇剧▼缂栧彿</text>
+            <text class="info-value">{{ form.courseCode || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅鏃ユ湡</text>
+            <text class="info-value">{{ form.trainingDate || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">寮�濮嬫椂闂�</text>
+            <text class="info-value">{{ form.openingTime || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">缁撴潫鏃堕棿</text>
+            <text class="info-value">{{ form.endTime || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅鐩爣</text>
+            <text class="info-value">{{ form.trainingObjectives || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍙傚姞瀵硅薄</text>
+            <text class="info-value">{{ form.participants || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅鍐呭</text>
+            <text class="info-value">{{ form.trainingContent || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅璁插笀</text>
+            <text class="info-value">{{ form.trainingLecturer || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">椤圭洰瀛﹀垎</text>
+            <text class="info-value">{{ form.projectCredits || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅鏂瑰紡</text>
+            <text class="info-value">{{ getTrainingModeLabel(form.trainingMode) || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍩硅鍦扮偣</text>
+            <text class="info-value">{{ form.placeTraining || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">璇炬椂</text>
+            <text class="info-value">{{ form.classHour || '-' }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+  import { ref, onMounted } from "vue";
+  import PageHeader from "@/components/PageHeader.vue";
+  import { onLoad } from "@dcloudio/uni-app";
+  import { useDict } from "@/utils/dict";
+  // 鏇挎崲 toast 鏂规硶
+  defineOptions({ name: "safety-training-view" });
+  const showToast = message => {
+    uni.showToast({ title: message, icon: "none" });
+  };
+
+  // 鑾峰彇瀛楀吀鏁版嵁
+  const { safe_training_methods } = useDict("safe_training_methods");
+
+  // 鑾峰彇鍩硅鏂瑰紡鏍囩
+  const getTrainingModeLabel = val => {
+    if (!safe_training_methods || !Array.isArray(safe_training_methods.value)) {
+      return val;
+    }
+    const item = safe_training_methods.value.find(
+      i => String(i.value) === String(val)
+    );
+    return item ? item.label : val;
+  };
+
+  // 鍩硅淇℃伅
+  const form = ref({});
+
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
+
+  onLoad(() => {
+    // 浠庢湰鍦板瓨鍌ㄨ幏鍙栧煿璁俊鎭�
+    const safetyTraining = uni.getStorageSync("safetyTraining");
+    if (safetyTraining) {
+      form.value = safetyTraining;
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @import "../../../styles/sales-common.scss";
+
+  .danger-investigation-view {
+    min-height: 100vh;
+    background: #f8f9fa;
+    padding-bottom: 2rem;
+  }
+
+  .detail-content {
+    padding: 20px;
+  }
+
+  .info-section {
+    background: #ffffff;
+    border-radius: 12px;
+    padding: 24px;
+    margin-bottom: 24px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  }
+
+  .section-title {
+    padding: 1rem;
+    font-size: 1rem;
+    font-weight: 500;
+    color: #303133;
+    background: #f5f5f5;
+    border-bottom: 1px solid #e4e7ed;
+  }
+
+  .info-content {
+    padding: 1rem;
+  }
+  .info-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 20px;
+  }
+  .info-item {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+  .info-item:last-child {
+    margin-bottom: 0;
+  }
+
+  .info-label {
+    font-size: 14px;
+    color: #909399;
+  }
+
+  .info-value {
+    font-size: 14px;
+    color: #303133;
+    word-break: break-all;
+  }
+
+  .description-content {
+    padding: 1rem;
+    font-size: 0.875rem;
+    color: #303133;
+    line-height: 1.5;
+  }
+</style>
\ No newline at end of file
diff --git a/src/pages/sales/invoiceLedger/index.vue b/src/pages/sales/invoiceLedger/index.vue
index 04ebbc2..d5d0b17 100644
--- a/src/pages/sales/invoiceLedger/index.vue
+++ b/src/pages/sales/invoiceLedger/index.vue
@@ -8,7 +8,7 @@
       <view class="search-bar">
         <view class="search-input">
           <up-input class="search-text"
-                    placeholder="璇疯緭鍏ュ鎴峰悕绉�/鍚堝悓鍙锋悳绱�"
+                    placeholder="璇疯緭鍏ュ鎴峰悕绉�/閿�鍞悎鍚屽彿鎼滅储"
                     v-model="searchForm.searchText"
                     @change="handleQuery"
                     clearable />
@@ -45,10 +45,6 @@
             <view class="detail-row">
               <text class="detail-label">瀹㈡埛鍚嶇О</text>
               <text class="detail-value">{{ item.customerName }}</text>
-            </view>
-            <view class="detail-row">
-              <text class="detail-label">瀹㈡埛鍚堝悓鍙�</text>
-              <text class="detail-value">{{ item.customerContractNo }}</text>
             </view>
             <view class="detail-row">
               <text class="detail-label">椤圭洰</text>
diff --git a/src/pages/sales/invoicingRegistration/index.vue b/src/pages/sales/invoicingRegistration/index.vue
index b30d1ad..7d5bcbb 100644
--- a/src/pages/sales/invoicingRegistration/index.vue
+++ b/src/pages/sales/invoicingRegistration/index.vue
@@ -44,10 +44,6 @@
               <text class="detail-value">{{ item.customerName }}</text>
             </view>
             <view class="detail-row">
-              <text class="detail-label">瀹㈡埛鍚堝悓鍙�</text>
-              <text class="detail-value">{{ item.customerContractNo }}</text>
-            </view>
-            <view class="detail-row">
               <text class="detail-label">涓氬姟鍛�</text>
               <text class="detail-value">{{ item.salesman }}</text>
             </view>
diff --git a/src/pages/sales/invoicingRegistration/view.vue b/src/pages/sales/invoicingRegistration/view.vue
index 85f1001..72828ff 100644
--- a/src/pages/sales/invoicingRegistration/view.vue
+++ b/src/pages/sales/invoicingRegistration/view.vue
@@ -12,10 +12,6 @@
           <text class="info-value">{{ form.salesContractNo }}</text>
         </view>
         <view class="info-item">
-          <text class="info-label">瀹㈡埛鍚堝悓鍙�</text>
-          <text class="info-value highlight">{{ form.customerContractNo }}</text>
-        </view>
-        <view class="info-item">
           <text class="info-label">瀹㈡埛鍚嶇О</text>
           <text class="info-value">{{ form.customerName }}</text>
         </view>
@@ -123,7 +119,6 @@
 const form = ref({
   id: '',
   salesContractNo: '',
-  customerContractNo: '',
   customerId: '',
   customerName: '',
   projectName: '',
diff --git a/src/pages/sales/receiptPayment/index.vue b/src/pages/sales/receiptPayment/index.vue
index 1f222f6..ffd154a 100644
--- a/src/pages/sales/receiptPayment/index.vue
+++ b/src/pages/sales/receiptPayment/index.vue
@@ -141,7 +141,6 @@
   const searchForm = ref({
     customerName: "",
     status: true,
-    customerContractNo: "",
     projectName: "",
   });
   // 鑾峰彇鏍囩鏍峰紡绫�
diff --git a/src/pages/sales/receiptPaymentHistory/index.vue b/src/pages/sales/receiptPaymentHistory/index.vue
index 1242c2b..0d148b5 100644
--- a/src/pages/sales/receiptPaymentHistory/index.vue
+++ b/src/pages/sales/receiptPaymentHistory/index.vue
@@ -56,10 +56,6 @@
           <up-divider></up-divider>
           <view class="item-details">
             <view class="detail-row">
-              <text class="detail-label">瀹㈡埛鍚堝悓鍙�</text>
-              <text class="detail-value">{{ item.customerContractNo }}</text>
-            </view>
-            <view class="detail-row">
               <text class="detail-label">瀹㈡埛鍚嶇О</text>
               <text class="detail-value">{{ item.customerName }}</text>
             </view>
diff --git a/src/pages/sales/receiptPaymentLedger/detail.vue b/src/pages/sales/receiptPaymentLedger/detail.vue
index 5786eea..b494332 100644
--- a/src/pages/sales/receiptPaymentLedger/detail.vue
+++ b/src/pages/sales/receiptPaymentLedger/detail.vue
@@ -54,6 +54,10 @@
             <text class="detail-label">搴旀敹閲戦(鍏�)</text>
             <text class="detail-value danger">{{ formatAmount(item.unReceiptPaymentAmount) }}</text>
           </view>
+          <view class="detail-row">
+            <text class="detail-label">鍙戠敓鏃ユ湡</text>
+            <text class="detail-value">{{ item.receiptPaymentDate }}</text>
+          </view>
         </view>
       </view>
     </view>
diff --git a/src/pages/sales/salesAccount/index.vue b/src/pages/sales/salesAccount/index.vue
index 8e3fdf6..250447d 100644
--- a/src/pages/sales/salesAccount/index.vue
+++ b/src/pages/sales/salesAccount/index.vue
@@ -79,12 +79,21 @@
               </view>
             </view>
             <up-divider></up-divider>
-            <u-button class="detail-button"
+            <view class="detail-buttons">
+              <u-button class="detail-button"
                       size="small"
                       type="primary"
                       @click="openOut(item)">
               鍙戣揣鐘舵��
             </u-button>
+            <u-button class="detail-button"
+                      size="small"
+                      type="error"
+                      plain
+                      @click.stop="handleDelete(item)">
+              鍒犻櫎
+            </u-button>
+            </view>
           </view>
         </view>
       </view>
@@ -106,7 +115,11 @@
 <script setup>
   import { ref } from "vue";
   import { onShow } from "@dcloudio/uni-app";
-  import { ledgerListPage } from "@/api/salesManagement/salesLedger";
+  import {
+    ledgerListPage,
+    delLedger,
+    productList,
+  } from "@/api/salesManagement/salesLedger";
   import useUserStore from "@/store/modules/user";
   import PageHeader from "@/components/PageHeader.vue";
   const userStore = useUserStore();
@@ -125,6 +138,20 @@
 
   // 閿�鍞彴璐︽暟鎹�
   const ledgerList = ref([]);
+
+  // 鍒ゆ柇鏄惁瀛樺湪宸插彂璐�/鍙戣揣瀹屾垚鐨勪骇鍝�
+  const hasShippedProducts = products => {
+    if (!products || products.length === 0) return false;
+    return products.some(p => {
+      const statusStr = (p.shippingStatus ?? "").toString();
+      // 鍖呭惈鈥滃彂璐р�濇垨鏈夊彂璐ф棩鏈�/杞︾墝鍙疯涓哄凡鍙戣揣
+      return (
+        statusStr.includes("鍙戣揣") ||
+        !!p.shippingDate ||
+        !!p.shippingCarNumber
+      );
+    });
+  };
 
   // 杩斿洖涓婁竴椤�
   const goBack = () => {
@@ -151,6 +178,55 @@
     uni.setStorageSync("outData", JSON.stringify(item));
     uni.navigateTo({
       url: "/pages/sales/salesAccount/out",
+    });
+  };
+
+  // 鍒犻櫎鍗曟潯閿�鍞彴璐�
+  const handleDelete = async row => {
+    if (!row || !row.id) return;
+
+    // 鑾峰彇浜у搧鍒楄〃锛岀敤浜庡垽鏂槸鍚﹀凡鍙戣揣
+    let products = row.children && row.children.length > 0 ? row.children : null;
+    if (!products) {
+      try {
+        const res = await productList({ salesLedgerId: row.id, type: 1 });
+        products = res.data || res.records || [];
+      } catch (e) {
+        products = [];
+      }
+    }
+
+    if (hasShippedProducts(products)) {
+      uni.showToast({
+        title: "宸插彂璐�/鍙戣揣瀹屾垚鐨勯攢鍞鍗曚笉鑳藉垹闄�",
+        icon: "none",
+      });
+      return;
+    }
+
+    uni.showModal({
+      title: "鍒犻櫎纭",
+      content: "閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�",
+      success: async res => {
+        if (res.confirm) {
+          try {
+            showLoadingToast("澶勭悊涓�...");
+            await delLedger([row.id]);
+            closeToast();
+            uni.showToast({
+              title: "鍒犻櫎鎴愬姛",
+              icon: "success",
+            });
+            getList();
+          } catch (e) {
+            closeToast();
+            uni.showToast({
+              title: "鍒犻櫎澶辫触锛岃閲嶈瘯",
+              icon: "none",
+            });
+          }
+        }
+      },
     });
   };
   // 澶勭悊鍙拌处淇℃伅鎿嶄綔锛堟煡鐪�/缂栬緫/鏂板锛�
@@ -209,4 +285,9 @@
 
 <style scoped lang="scss">
   @import "@/styles/sales-common.scss";
+  .detail-buttons {
+    display: flex;
+    gap: 10px;
+    justify-content: space-between;
+  }
 </style>

--
Gitblit v1.9.3