From b8da78824e4c67632abb65302f01ccf74d5a1096 Mon Sep 17 00:00:00 2001
From: zouyu <2723363702@qq.com>
Date: 星期二, 26 五月 2026 18:45:51 +0800
Subject: [PATCH] 浪潮对接:芯导-安环管理系统,配置调整

---
 src/views/safetyManagement/trainingManage/index.vue |  892 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 861 insertions(+), 31 deletions(-)

diff --git a/src/views/safetyManagement/trainingManage/index.vue b/src/views/safetyManagement/trainingManage/index.vue
index 3c37b2f..873f862 100644
--- a/src/views/safetyManagement/trainingManage/index.vue
+++ b/src/views/safetyManagement/trainingManage/index.vue
@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-tabs v-model="activeTab" type="border-card">
+    <el-tabs v-model="activeTab" type="border-card" @tab-change="handleTabChange">
       <el-tab-pane label="鍩硅璧勬枡" name="materials">
         <el-form :model="materialFilters" :inline="true">
           <el-form-item label="璧勬枡鍚嶇О">
@@ -8,10 +8,17 @@
           </el-form-item>
           <el-form-item label="璧勬枡绫诲瀷">
             <el-select v-model="materialFilters.type" placeholder="璇烽�夋嫨" clearable style="width: 150px">
-              <el-option label="鍒跺害" value="system" />
-              <el-option label="璇句欢" value="courseware" />
-              <el-option label="瑙嗛" value="video" />
-              <el-option label="妗堜緥" value="case" />
+              <el-option label="PDF" value="PDF" />
+              <el-option label="鍒跺害" value="鍒跺害" />
+              <el-option label="璇句欢" value="璇句欢" />
+              <el-option label="瑙嗛" value="瑙嗛" />
+              <el-option label="妗堜緥" value="妗堜緥" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="鐘舵��">
+            <el-select v-model="materialFilters.status" placeholder="璇烽�夋嫨" clearable style="width: 150px">
+              <el-option label="鍚敤" :value="1" />
+              <el-option label="鍋滅敤" :value="0" />
             </el-select>
           </el-form-item>
           <el-form-item>
@@ -21,9 +28,9 @@
         </el-form>
         <div class="table_list">
           <div class="actions">
-            <el-button type="primary" @click="uploadMaterial" icon="Upload">涓婁紶璧勬枡</el-button>
+            <el-button type="primary" @click="openMaterialDialog" icon="Upload">涓婁紶璧勬枡</el-button>
           </div>
-          <PIMTable :column="materialColumns" :tableData="materialList" :page="materialPage" @pagination="changeMaterialPage" />
+          <PIMTable :column="materialColumns" :tableData="materialList" :page="materialPage" @pagination="changeMaterialPage" :tableLoading="materialLoading" />
         </div>
       </el-tab-pane>
 
@@ -35,6 +42,20 @@
           <el-form-item label="宀椾綅">
             <el-input v-model="planFilters.post" placeholder="璇疯緭鍏ュ矖浣�" clearable style="width: 200px" />
           </el-form-item>
+          <el-form-item label="鍩硅绛夌骇">
+            <el-select v-model="planFilters.level" placeholder="璇烽�夋嫨" clearable style="width: 150px">
+              <el-option label="涓�绾�" value="涓�绾�" />
+              <el-option label="浜岀骇" value="浜岀骇" />
+              <el-option label="涓夌骇" value="涓夌骇" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="鐘舵��">
+            <el-select v-model="planFilters.status" placeholder="璇烽�夋嫨" clearable style="width: 150px">
+              <el-option label="寰呮墽琛�" :value="0" />
+              <el-option label="鎵ц涓�" :value="1" />
+              <el-option label="宸插畬鎴�" :value="2" />
+            </el-select>
+          </el-form-item>
           <el-form-item>
             <el-button type="primary" @click="getPlanData">鎼滅储</el-button>
             <el-button @click="resetPlanFilters">閲嶇疆</el-button>
@@ -42,9 +63,9 @@
         </el-form>
         <div class="table_list">
           <div class="actions">
-            <el-button type="primary" @click="addPlan" icon="Plus">鍒跺畾璁″垝</el-button>
+            <el-button type="primary" @click="openPlanDialog" icon="Plus">鍒跺畾璁″垝</el-button>
           </div>
-          <PIMTable :column="planColumns" :tableData="planList" :page="planPage" @pagination="changePlanPage" />
+          <PIMTable :column="planColumns" :tableData="planList" :page="planPage" @pagination="changePlanPage" :tableLoading="planLoading" />
         </div>
       </el-tab-pane>
 
@@ -53,22 +74,232 @@
           <el-form-item label="鍛樺伐濮撳悕">
             <el-input v-model="recordFilters.employeeName" placeholder="璇疯緭鍏ュ憳宸ュ鍚�" clearable style="width: 200px" />
           </el-form-item>
+          <el-form-item label="鐘舵��">
+            <el-select v-model="recordFilters.status" placeholder="璇烽�夋嫨" clearable style="width: 150px">
+              <el-option label="宸插畬鎴�" :value="1" />
+              <el-option label="鏈畬鎴�" :value="0" />
+            </el-select>
+          </el-form-item>
           <el-form-item>
             <el-button type="primary" @click="getRecordData">鎼滅储</el-button>
             <el-button @click="resetRecordFilters">閲嶇疆</el-button>
+            <el-button type="primary" @click="openRecordDialog" icon="Plus">鏂板</el-button>
+            <el-button type="success" @click="exportRecords" icon="Download">瀵煎嚭</el-button>
           </el-form-item>
         </el-form>
         <div class="table_list">
-          <PIMTable :column="recordColumns" :tableData="recordList" :page="recordPage" @pagination="changeRecordPage" />
+          <PIMTable :column="recordColumns" :tableData="recordList" :page="recordPage" @pagination="changeRecordPage" :tableLoading="recordLoading" />
         </div>
       </el-tab-pane>
     </el-tabs>
+
+    <!-- 鍩硅璧勬枡寮圭獥 -->
+    <el-dialog :title="materialDialog.title" v-model="materialDialog.visible" width="600px" append-to-body>
+      <el-form ref="materialFormRef" :model="materialForm" :rules="materialRules" label-width="100px">
+        <el-form-item label="璧勬枡鍚嶇О" prop="name">
+          <el-input v-model="materialForm.name" placeholder="璇疯緭鍏ヨ祫鏂欏悕绉�" />
+        </el-form-item>
+        <el-form-item label="璧勬枡绫诲瀷" prop="type">
+          <el-select v-model="materialForm.type" placeholder="璇烽�夋嫨璧勬枡绫诲瀷" style="width: 100%">
+            <el-option label="PDF" value="PDF" />
+            <el-option label="鍒跺害" value="鍒跺害" />
+            <el-option label="璇句欢" value="璇句欢" />
+            <el-option label="瑙嗛" value="瑙嗛" />
+            <el-option label="妗堜緥" value="妗堜緥" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="鏂囦欢涓婁紶" prop="fileUrl" v-if="!materialForm.id">
+          <el-upload
+            ref="uploadRef"
+            action="/dev-api/common/upload"
+            :headers="uploadHeaders"
+            :on-success="handleUploadSuccess"
+            :on-error="handleUploadError"
+            :before-upload="beforeUpload"
+            :limit="1"
+          >
+            <el-button type="primary">閫夋嫨鏂囦欢</el-button>
+            <template #tip>
+              <div class="el-upload__tip">鏀寔 PDF銆乄ord銆佽棰戠瓑鏍煎紡</div>
+            </template>
+          </el-upload>
+        </el-form-item>
+        <el-form-item label="鏂囦欢澶у皬" prop="fileSize" v-if="materialForm.fileSize">
+          <el-input v-model="materialForm.fileSize" disabled />
+        </el-form-item>
+        <el-form-item label="鐘舵��" prop="status">
+          <el-radio-group v-model="materialForm.status">
+            <el-radio :value="1">鍚敤</el-radio>
+            <el-radio :value="0">鍋滅敤</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input v-model="materialForm.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="materialDialog.visible = false">鍙� 娑�</el-button>
+        <el-button type="primary" @click="submitMaterialForm" :loading="materialDialog.loading">纭� 瀹�</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 鍩硅璁″垝寮圭獥 -->
+    <el-dialog :title="planDialog.title" v-model="planDialog.visible" width="700px" append-to-body>
+      <el-form ref="planFormRef" :model="planForm" :rules="planRules" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="璁″垝骞村害" prop="year">
+              <el-date-picker v-model="planForm.year" type="year" placeholder="閫夋嫨骞村害" value-format="YYYY" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="閫傜敤宀椾綅" prop="post">
+              <el-input v-model="planForm.post" placeholder="璇疯緭鍏ラ�傜敤宀椾綅" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="鍩硅绛夌骇" prop="level">
+              <el-select v-model="planForm.level" placeholder="璇烽�夋嫨鍩硅绛夌骇" style="width: 100%">
+                <el-option label="涓�绾�" value="涓�绾�" />
+                <el-option label="浜岀骇" value="浜岀骇" />
+                <el-option label="涓夌骇" value="涓夌骇" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="璁″垝璇炬椂" prop="hours">
+              <el-input-number v-model="planForm.hours" :min="1" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="鍩硅鍐呭" prop="content">
+          <el-input v-model="planForm.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ煿璁唴瀹�" />
+        </el-form-item>
+        <el-form-item label="鍩硅璧勬枡" prop="materialIds">
+          <el-select v-model="planForm.materialIds" multiple placeholder="璇烽�夋嫨鍩硅璧勬枡" style="width: 100%">
+            <el-option v-for="item in materialOptions" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="鐘舵��" prop="status">
+          <el-radio-group v-model="planForm.status">
+            <el-radio :value="0">寰呮墽琛�</el-radio>
+            <el-radio :value="1">鎵ц涓�</el-radio>
+            <el-radio :value="2">宸插畬鎴�</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input v-model="planForm.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="planDialog.visible = false">鍙� 娑�</el-button>
+        <el-button type="primary" @click="submitPlanForm" :loading="planDialog.loading">纭� 瀹�</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 瀹屾垚璁板綍寮圭獥 -->
+    <el-dialog :title="recordDialog.title" v-model="recordDialog.visible" width="700px" append-to-body>
+      <el-form ref="recordFormRef" :model="recordForm" :rules="recordRules" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="鍩硅璁″垝" prop="planId">
+              <el-select v-model="recordForm.planId" placeholder="璇烽�夋嫨鍩硅璁″垝" style="width: 100%" @change="handlePlanChange">
+                <el-option v-for="item in planOptions" :key="item.id" :label="item.content" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍛樺伐濮撳悕" prop="employeeId">
+              <el-select v-model="recordForm.employeeId" placeholder="璇烽�夋嫨鍛樺伐" style="width: 100%" @change="handleEmployeeChange">
+                <el-option v-for="item in employeeOptions" :key="item.userId" :label="item.userName" :value="item.userId" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="瀹屾垚鏃堕棿" prop="completeTime">
+              <el-date-picker v-model="recordForm.completeTime" type="datetime" placeholder="閫夋嫨瀹屾垚鏃堕棿" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="瀛︿範鏃堕暱" prop="duration">
+              <el-input-number v-model="recordForm.duration" :min="0" :precision="1" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="鍩硅鏂瑰紡" prop="method">
+              <el-select v-model="recordForm.method" placeholder="璇烽�夋嫨鍩硅鏂瑰紡" style="width: 100%">
+                <el-option label="绾夸笅鎺堣" value="绾夸笅鎺堣" />
+                <el-option label="绾夸笂瀛︿範" value="绾夸笂瀛︿範" />
+                <el-option label="瀹炴搷婕旂粌" value="瀹炴搷婕旂粌" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鑰冩牳鍒嗘暟" prop="score">
+              <el-input-number v-model="recordForm.score" :min="0" :max="100" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="鑰冩牳缁撴灉" prop="result">
+              <el-select v-model="recordForm.result" placeholder="璇烽�夋嫨鑰冩牳缁撴灉" style="width: 100%">
+                <el-option label="閫氳繃" value="閫氳繃" />
+                <el-option label="鏈�氳繃" value="鏈�氳繃" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鐘舵��" prop="status">
+              <el-radio-group v-model="recordForm.status">
+                <el-radio :value="1">宸插畬鎴�</el-radio>
+                <el-radio :value="0">鏈畬鎴�</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input v-model="recordForm.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="recordDialog.visible = false">鍙� 娑�</el-button>
+        <el-button type="primary" @click="submitRecordForm" :loading="recordDialog.loading">纭� 瀹�</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, reactive } from "vue";
+import { ref, reactive, onMounted } from "vue";
 import PIMTable from "@/components/PIMTable/PIMTable.vue";
+import {
+  getMaterialList,
+  uploadMaterial as uploadMaterialApi,
+  updateMaterial,
+  deleteMaterial,
+  getMaterialDetail,
+  getPlanList,
+  addPlan as addPlanApi,
+  updatePlan,
+  deletePlan,
+  getPlanDetail,
+  getRecordList,
+  addRecord as addRecordApi,
+  updateRecord,
+  deleteRecord,
+  getRecordDetail,
+  exportRecord
+} from "@/api/safetyManagement/trainingManage.js";
+import { listUser } from "@/api/system/user";
+import { getToken } from "@/utils/auth";
+import { ElMessage, ElMessageBox } from "element-plus";
 
 defineOptions({
   name: "鍩硅绠$悊",
@@ -76,54 +307,653 @@
 
 const activeTab = ref("materials");
 
-// 鍩硅璧勬枡
-const materialFilters = reactive({ name: "", type: "" });
+// ==================== 鍩硅璧勬枡 ====================
+const materialFilters = reactive({ name: "", type: "", status: null });
 const materialList = ref([]);
 const materialPage = reactive({ current: 1, size: 10, total: 0 });
+const materialLoading = ref(false);
 const materialColumns = [
   { label: "璧勬枡鍚嶇О", prop: "name", align: "center" },
-  { label: "绫诲瀷", prop: "type", align: "center" },
+  {
+    label: "绫诲瀷",
+    prop: "type",
+    align: "center",
+    dataType: "tag",
+    formatType: () => "primary",
+    formatData: (val) => val || '-'
+  },
   { label: "涓婁紶浜�", prop: "uploader", align: "center" },
   { label: "涓婁紶鏃堕棿", prop: "uploadTime", align: "center" },
   { label: "鏂囦欢澶у皬", prop: "fileSize", align: "center" },
+  {
+    label: "鐘舵��",
+    prop: "status",
+    align: "center",
+    dataType: "tag",
+    formatType: (val) => (val === 1 ? "success" : "info"),
+    formatData: (val) => (val === 1 ? "鍚敤" : "鍋滅敤")
+  },
+  {
+    label: "鎿嶄綔",
+    prop: "action",
+    align: "center",
+    dataType: "action",
+    operation: [
+      { name: "缂栬緫", type: "text", clickFun: (row) => handleEditMaterial(row) },
+      { name: "涓嬭浇", type: "text", clickFun: (row) => handleDownload(row) },
+      { name: "鍒犻櫎", type: "text", clickFun: (row) => handleDeleteMaterial(row) }
+    ]
+  }
 ];
 
-// 鍩硅璁″垝
-const planFilters = reactive({ year: "", post: "" });
+const materialDialog = reactive({ visible: false, title: "", loading: false });
+const materialFormRef = ref(null);
+const uploadRef = ref(null);
+const materialForm = reactive({
+  id: null,
+  name: "",
+  type: "",
+  fileUrl: "",
+  fileSize: "",
+  status: 1,
+  remark: ""
+});
+const materialRules = {
+  name: [{ required: true, message: "璇疯緭鍏ヨ祫鏂欏悕绉�", trigger: "blur" }],
+  type: [{ required: true, message: "璇烽�夋嫨璧勬枡绫诲瀷", trigger: "change" }]
+};
+
+const uploadHeaders = reactive({
+  Authorization: "Bearer " + getToken()
+});
+
+// ==================== 鍩硅璁″垝 ====================
+const planFilters = reactive({ year: "", post: "", level: "", status: null });
 const planList = ref([]);
 const planPage = reactive({ current: 1, size: 10, total: 0 });
+const planLoading = ref(false);
 const planColumns = [
   { label: "璁″垝骞村害", prop: "year", align: "center" },
   { label: "宀椾綅", prop: "post", align: "center" },
-  { label: "灞傜骇", prop: "level", align: "center" },
+  { label: "鍩硅绛夌骇", prop: "level", align: "center" },
   { label: "鍩硅鍐呭", prop: "content", align: "center" },
   { label: "璁″垝璇炬椂", prop: "hours", align: "center" },
+  {
+    label: "鐘舵��",
+    prop: "status",
+    align: "center",
+    dataType: "tag",
+    formatType: (val) => {
+      if (val === 0) return 'info';
+      if (val === 1) return 'warning';
+      return 'success';
+    },
+    formatData: (val) => {
+      const statusMap = { 0: '寰呮墽琛�', 1: '鎵ц涓�', 2: '宸插畬鎴�' };
+      return statusMap[val] || val;
+    }
+  },
+  {
+    label: "鎿嶄綔",
+    prop: "action",
+    align: "center",
+    dataType: "action",
+    operation: [
+      { name: "缂栬緫", type: "text", clickFun: (row) => handleEditPlan(row) },
+      { name: "鍒犻櫎", type: "text", clickFun: (row) => handleDeletePlan(row) }
+    ]
+  }
 ];
 
-// 瀹屾垚璁板綍
-const recordFilters = reactive({ employeeName: "" });
+const planDialog = reactive({ visible: false, title: "", loading: false });
+const planFormRef = ref(null);
+const planForm = reactive({
+  id: null,
+  year: "",
+  post: "",
+  level: "",
+  content: "",
+  hours: 1,
+  materialIds: [],
+  status: 0,
+  remark: ""
+});
+const planRules = {
+  year: [{ required: true, message: "璇烽�夋嫨璁″垝骞村害", trigger: "change" }],
+  post: [{ required: true, message: "璇疯緭鍏ラ�傜敤宀椾綅", trigger: "blur" }],
+  level: [{ required: true, message: "璇烽�夋嫨鍩硅绛夌骇", trigger: "change" }],
+  content: [{ required: true, message: "璇疯緭鍏ュ煿璁唴瀹�", trigger: "blur" }],
+  hours: [{ required: true, message: "璇疯緭鍏ヨ鍒掕鏃�", trigger: "blur" }]
+};
+const materialOptions = ref([]);
+
+// ==================== 瀹屾垚璁板綍 ====================
+const recordFilters = reactive({ employeeName: "", status: null });
 const recordList = ref([]);
 const recordPage = reactive({ current: 1, size: 10, total: 0 });
+const recordLoading = ref(false);
 const recordColumns = [
   { label: "鍛樺伐濮撳悕", prop: "employeeName", align: "center" },
   { label: "鍩硅鍐呭", prop: "content", align: "center" },
   { label: "瀹屾垚鏃堕棿", prop: "completeTime", align: "center" },
-  { label: "鑰冩牳缁撴灉", prop: "result", align: "center" },
+  { label: "瀛︿範鏃堕暱", prop: "duration", align: "center" },
+  { label: "鍩硅鏂瑰紡", prop: "method", align: "center" },
+  { label: "鍒嗘暟", prop: "score", align: "center" },
+  {
+    label: "鑰冩牳缁撴灉",
+    prop: "result",
+    align: "center",
+    dataType: "tag",
+    formatType: (val) => {
+      if (val === '浼樼' || val === '閫氳繃') return 'success';
+      if (val === '鑹ソ') return 'primary';
+      if (val === '鍚堟牸') return 'warning';
+      return 'danger';
+    },
+    formatData: (val) => val || '-'
+  },
+  {
+    label: "鎿嶄綔",
+    prop: "action",
+    align: "center",
+    dataType: "action",
+    operation: [
+      { name: "缂栬緫", type: "text", clickFun: (row) => handleEditRecord(row) },
+      { name: "鍒犻櫎", type: "text", clickFun: (row) => handleDeleteRecord(row) }
+    ]
+  }
 ];
 
-const getMaterialData = () => {};
-const resetMaterialFilters = () => { materialFilters.name = ""; materialFilters.type = ""; };
-const uploadMaterial = () => {};
-const changeMaterialPage = ({ page, limit }) => { materialPage.current = page; materialPage.size = limit; };
+const recordDialog = reactive({ visible: false, title: "", loading: false });
+const recordFormRef = ref(null);
+const recordForm = reactive({
+  id: null,
+  planId: null,
+  employeeId: null,
+  employeeName: "",
+  content: "",
+  completeTime: "",
+  duration: 0,
+  method: "",
+  score: 0,
+  result: "",
+  status: 1,
+  remark: ""
+});
+const recordRules = {
+  planId: [{ required: true, message: "璇烽�夋嫨鍩硅璁″垝", trigger: "change" }],
+  employeeId: [{ required: true, message: "璇烽�夋嫨鍛樺伐", trigger: "change" }],
+  completeTime: [{ required: true, message: "璇烽�夋嫨瀹屾垚鏃堕棿", trigger: "change" }]
+};
+const planOptions = ref([]);
+const employeeOptions = ref([]);
 
-const getPlanData = () => {};
-const resetPlanFilters = () => { planFilters.year = ""; planFilters.post = ""; };
-const addPlan = () => {};
-const changePlanPage = ({ page, limit }) => { planPage.current = page; planPage.size = limit; };
+// ==================== 閫氱敤鏂规硶 ====================
+const loadData = () => {
+  switch (activeTab.value) {
+    case 'materials':
+      getMaterialData();
+      break;
+    case 'plans':
+      getPlanData();
+      break;
+    case 'records':
+      getRecordData();
+      break;
+  }
+};
 
-const getRecordData = () => {};
-const resetRecordFilters = () => { recordFilters.employeeName = ""; };
-const changeRecordPage = ({ page, limit }) => { recordPage.current = page; recordPage.size = limit; };
+const handleTabChange = () => {
+  loadData();
+};
+
+// ==================== 鍩硅璧勬枡鏂规硶 ====================
+const getMaterialData = async () => {
+  materialLoading.value = true;
+  try {
+    const res = await getMaterialList({
+      pageNum: materialPage.current,
+      pageSize: materialPage.size,
+      ...materialFilters
+    });
+    if (res.code === 200) {
+      materialList.value = res.data.records || res.data.rows || [];
+      materialPage.total = res.data.total || 0;
+    }
+  } catch (error) {
+    ElMessage.error('鑾峰彇鍩硅璧勬枡澶辫触');
+  } finally {
+    materialLoading.value = false;
+  }
+};
+
+const resetMaterialFilters = () => {
+  materialFilters.name = "";
+  materialFilters.type = "";
+  materialFilters.status = null;
+  materialPage.current = 1;
+  getMaterialData();
+};
+
+const changeMaterialPage = ({ page, limit }) => {
+  materialPage.current = page;
+  materialPage.size = limit;
+  getMaterialData();
+};
+
+const resetMaterialForm = () => {
+  materialForm.id = null;
+  materialForm.name = "";
+  materialForm.type = "";
+  materialForm.fileUrl = "";
+  materialForm.fileSize = "";
+  materialForm.status = 1;
+  materialForm.remark = "";
+  if (uploadRef.value) {
+    uploadRef.value.clearFiles();
+  }
+};
+
+const openMaterialDialog = () => {
+  resetMaterialForm();
+  materialDialog.title = "涓婁紶鍩硅璧勬枡";
+  materialDialog.visible = true;
+};
+
+const handleEditMaterial = async (row) => {
+  resetMaterialForm();
+  try {
+    const res = await getMaterialDetail(row.id);
+    if (res.code === 200) {
+      Object.assign(materialForm, res.data);
+      materialDialog.title = "缂栬緫鍩硅璧勬枡";
+      materialDialog.visible = true;
+    }
+  } catch (error) {
+    ElMessage.error('鑾峰彇璧勬枡璇︽儏澶辫触');
+  }
+};
+
+const handleUploadSuccess = (response) => {
+  if (response.code === 200) {
+    materialForm.fileUrl = response.url;
+    materialForm.fileSize = formatFileSize(response.file?.size || 0);
+    ElMessage.success('鏂囦欢涓婁紶鎴愬姛');
+  } else {
+    ElMessage.error(response.msg || '涓婁紶澶辫触');
+  }
+};
+
+const handleUploadError = () => {
+  ElMessage.error('鏂囦欢涓婁紶澶辫触');
+};
+
+const beforeUpload = (file) => {
+  const maxSize = 50 * 1024 * 1024; // 50MB
+  if (file.size > maxSize) {
+    ElMessage.error('鏂囦欢澶у皬涓嶈兘瓒呰繃 50MB');
+    return false;
+  }
+  return true;
+};
+
+const formatFileSize = (size) => {
+  if (size < 1024) return size + ' B';
+  if (size < 1024 * 1024) return (size / 1024).toFixed(2) + ' KB';
+  if (size < 1024 * 1024 * 1024) return (size / (1024 * 1024)).toFixed(2) + ' MB';
+  return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
+};
+
+const submitMaterialForm = async () => {
+  const valid = await materialFormRef.value.validate().catch(() => false);
+  if (!valid) return;
+  
+  if (!materialForm.id && !materialForm.fileUrl) {
+    ElMessage.error('璇蜂笂浼犳枃浠�');
+    return;
+  }
+  
+  materialDialog.loading = true;
+  try {
+    const api = materialForm.id ? updateMaterial : uploadMaterialApi;
+    const res = await api(materialForm);
+    if (res.code === 200) {
+      ElMessage.success(materialForm.id ? '淇敼鎴愬姛' : '涓婁紶鎴愬姛');
+      materialDialog.visible = false;
+      getMaterialData();
+    }
+  } catch (error) {
+    ElMessage.error(materialForm.id ? '淇敼澶辫触' : '涓婁紶澶辫触');
+  } finally {
+    materialDialog.loading = false;
+  }
+};
+
+const handleDownload = (row) => {
+  if (row.fileUrl) {
+    // 澶勭悊URL鏍煎紡闂
+    let url = row.fileUrl;
+    // 淇绫讳技 http://host:portupload/ 鐨勯敊璇牸寮�
+    url = url.replace(/^(http:\/\/[^/]+:\d+)(upload\/)/, '$1/$2');
+    // 濡傛灉涓嶆槸瀹屾暣URL锛屾坊鍔犲墠缂�
+    if (!url.startsWith('http')) {
+      url = '/dev-api/' + url.replace(/^\//, '');
+    }
+    window.open(url, '_blank');
+  } else {
+    ElMessage.warning('鏂囦欢鍦板潃涓嶅瓨鍦�');
+  }
+};
+
+const handleDeleteMaterial = (row) => {
+  ElMessageBox.confirm(`纭鍒犻櫎璧勬枡 "${row.name}" 鍚楋紵`, "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning"
+  }).then(async () => {
+    try {
+      const res = await deleteMaterial(row.id);
+      if (res.code === 200) {
+        ElMessage.success("鍒犻櫎鎴愬姛");
+        getMaterialData();
+      }
+    } catch (error) {
+      ElMessage.error("鍒犻櫎澶辫触");
+    }
+  });
+};
+
+// ==================== 鍩硅璁″垝鏂规硶 ====================
+const getPlanData = async () => {
+  planLoading.value = true;
+  try {
+    const res = await getPlanList({
+      pageNum: planPage.current,
+      pageSize: planPage.size,
+      ...planFilters
+    });
+    if (res.code === 200) {
+      planList.value = res.data.records || res.data.rows || [];
+      planPage.total = res.data.total || 0;
+    }
+  } catch (error) {
+    ElMessage.error('鑾峰彇鍩硅璁″垝澶辫触');
+  } finally {
+    planLoading.value = false;
+  }
+};
+
+const resetPlanFilters = () => {
+  planFilters.year = "";
+  planFilters.post = "";
+  planFilters.level = "";
+  planFilters.status = null;
+  planPage.current = 1;
+  getPlanData();
+};
+
+const changePlanPage = ({ page, limit }) => {
+  planPage.current = page;
+  planPage.size = limit;
+  getPlanData();
+};
+
+const loadMaterialOptions = async () => {
+  try {
+    const res = await getMaterialList({ pageNum: 1, pageSize: 1000 });
+    if (res.code === 200) {
+      materialOptions.value = res.data.records || res.data.rows || [];
+    }
+  } catch (error) {
+    console.error('鍔犺浇鍩硅璧勬枡閫夐」澶辫触', error);
+  }
+};
+
+const resetPlanForm = () => {
+  planForm.id = null;
+  planForm.year = "";
+  planForm.post = "";
+  planForm.level = "";
+  planForm.content = "";
+  planForm.hours = 1;
+  planForm.materialIds = [];
+  planForm.status = 0;
+  planForm.remark = "";
+};
+
+const openPlanDialog = () => {
+  resetPlanForm();
+  loadMaterialOptions();
+  planDialog.title = "鍒跺畾鍩硅璁″垝";
+  planDialog.visible = true;
+};
+
+const handleEditPlan = async (row) => {
+  resetPlanForm();
+  loadMaterialOptions();
+  try {
+    const res = await getPlanDetail(row.id);
+    if (res.code === 200) {
+      Object.assign(planForm, res.data);
+      // 灏� materialIds 瀛楃涓茶浆鎹负鏁扮粍
+      if (planForm.materialIds && typeof planForm.materialIds === 'string') {
+        planForm.materialIds = planForm.materialIds.split(',').map(id => parseInt(id));
+      }
+      planDialog.title = "缂栬緫鍩硅璁″垝";
+      planDialog.visible = true;
+    }
+  } catch (error) {
+    ElMessage.error('鑾峰彇璁″垝璇︽儏澶辫触');
+  }
+};
+
+const submitPlanForm = async () => {
+  const valid = await planFormRef.value.validate().catch(() => false);
+  if (!valid) return;
+  
+  planDialog.loading = true;
+  try {
+    const submitData = { ...planForm };
+    // 灏� materialIds 鏁扮粍杞崲涓洪�楀彿鍒嗛殧鐨勫瓧绗︿覆
+    if (Array.isArray(submitData.materialIds)) {
+      submitData.materialIds = submitData.materialIds.join(',');
+    }
+    const api = planForm.id ? updatePlan : addPlanApi;
+    const res = await api(submitData);
+    if (res.code === 200) {
+      ElMessage.success(planForm.id ? '淇敼鎴愬姛' : '鏂板鎴愬姛');
+      planDialog.visible = false;
+      getPlanData();
+    }
+  } catch (error) {
+    ElMessage.error(planForm.id ? '淇敼澶辫触' : '鏂板澶辫触');
+  } finally {
+    planDialog.loading = false;
+  }
+};
+
+const handleDeletePlan = (row) => {
+  ElMessageBox.confirm(`纭鍒犻櫎璇ュ煿璁鍒掑悧锛焋, "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning"
+  }).then(async () => {
+    try {
+      const res = await deletePlan(row.id);
+      if (res.code === 200) {
+        ElMessage.success("鍒犻櫎鎴愬姛");
+        getPlanData();
+      }
+    } catch (error) {
+      ElMessage.error("鍒犻櫎澶辫触");
+    }
+  });
+};
+
+// ==================== 瀹屾垚璁板綍鏂规硶 ====================
+const getRecordData = async () => {
+  recordLoading.value = true;
+  try {
+    const res = await getRecordList({
+      pageNum: recordPage.current,
+      pageSize: recordPage.size,
+      ...recordFilters
+    });
+    if (res.code === 200) {
+      recordList.value = res.data.records || res.data.rows || [];
+      recordPage.total = res.data.total || 0;
+    }
+  } catch (error) {
+    ElMessage.error('鑾峰彇瀹屾垚璁板綍澶辫触');
+  } finally {
+    recordLoading.value = false;
+  }
+};
+
+const resetRecordFilters = () => {
+  recordFilters.employeeName = "";
+  recordFilters.status = null;
+  recordPage.current = 1;
+  getRecordData();
+};
+
+const changeRecordPage = ({ page, limit }) => {
+  recordPage.current = page;
+  recordPage.size = limit;
+  getRecordData();
+};
+
+const exportRecords = async () => {
+  try {
+    const res = await exportRecord(recordFilters);
+    // 澶勭悊鏂囦欢涓嬭浇
+    const blob = new Blob([res]);
+    const link = document.createElement('a');
+    link.href = URL.createObjectURL(blob);
+    link.download = '鍩硅瀹屾垚璁板綍.xlsx';
+    link.click();
+    ElMessage.success('瀵煎嚭鎴愬姛');
+  } catch (error) {
+    ElMessage.error('瀵煎嚭澶辫触');
+  }
+};
+
+// 瀹屾垚璁板綍琛ㄥ崟鏂规硶
+const resetRecordForm = () => {
+  recordForm.id = null;
+  recordForm.planId = null;
+  recordForm.employeeId = null;
+  recordForm.employeeName = "";
+  recordForm.content = "";
+  recordForm.completeTime = "";
+  recordForm.duration = 0;
+  recordForm.method = "";
+  recordForm.score = 0;
+  recordForm.result = "";
+  recordForm.status = 1;
+  recordForm.remark = "";
+};
+
+// 鍔犺浇鍩硅璁″垝閫夐」
+const loadPlanOptions = async () => {
+  try {
+    const res = await getPlanList({ pageNum: 1, pageSize: 1000 });
+    if (res.code === 200) {
+      planOptions.value = res.data.records || res.data.rows || [];
+    }
+  } catch (error) {
+    console.error('鍔犺浇鍩硅璁″垝澶辫触', error);
+  }
+};
+
+// 鍔犺浇鍛樺伐閫夐」锛堜粠鐢ㄦ埛绠$悊鑾峰彇锛�
+const loadEmployeeOptions = async () => {
+  try {
+    const res = await listUser({ pageNum: 1, pageSize: 1000 });
+    // 鐢ㄦ埛绠$悊鎺ュ彛鐩存帴杩斿洖 rows锛屾病鏈� code 瀛楁
+    employeeOptions.value = res.rows || [];
+  } catch (error) {
+    console.error('鍔犺浇鍛樺伐鍒楄〃澶辫触', error);
+  }
+};
+
+const handlePlanChange = (val) => {
+  const selectedPlan = planOptions.value.find(item => item.id === val);
+  if (selectedPlan) {
+    recordForm.content = selectedPlan.content;
+  }
+};
+
+const handleEmployeeChange = (val) => {
+  const selectedEmployee = employeeOptions.value.find(item => item.userId === val);
+  if (selectedEmployee) {
+    recordForm.employeeName = selectedEmployee.userName;
+  }
+};
+
+const openRecordDialog = () => {
+  resetRecordForm();
+  loadPlanOptions();
+  loadEmployeeOptions();
+  recordDialog.title = "鏂板瀹屾垚璁板綍";
+  recordDialog.visible = true;
+};
+
+const handleEditRecord = async (row) => {
+  resetRecordForm();
+  try {
+    const res = await getRecordDetail(row.id);
+    if (res.code === 200) {
+      Object.assign(recordForm, res.data);
+      recordDialog.title = "缂栬緫瀹屾垚璁板綍";
+      recordDialog.visible = true;
+    }
+  } catch (error) {
+    ElMessage.error('鑾峰彇璁板綍璇︽儏澶辫触');
+  }
+};
+
+const submitRecordForm = async () => {
+  const valid = await recordFormRef.value.validate().catch(() => false);
+  if (!valid) return;
+
+  recordDialog.loading = true;
+  try {
+    const api = recordForm.id ? updateRecord : addRecordApi;
+    const res = await api(recordForm);
+    if (res.code === 200) {
+      ElMessage.success(recordForm.id ? '淇敼鎴愬姛' : '鏂板鎴愬姛');
+      recordDialog.visible = false;
+      getRecordData();
+    }
+  } catch (error) {
+    ElMessage.error(recordForm.id ? '淇敼澶辫触' : '鏂板澶辫触');
+  } finally {
+    recordDialog.loading = false;
+  }
+};
+
+const handleDeleteRecord = (row) => {
+  ElMessageBox.confirm(`纭鍒犻櫎璇ュ畬鎴愯褰曞悧锛焋, "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning"
+  }).then(async () => {
+    try {
+      const res = await deleteRecord(row.id);
+      if (res.code === 200) {
+        ElMessage.success("鍒犻櫎鎴愬姛");
+        getRecordData();
+      }
+    } catch (error) {
+      ElMessage.error("鍒犻櫎澶辫触");
+    }
+  });
+};
+
+onMounted(() => {
+  getMaterialData();
+});
 </script>
 
 <style lang="scss" scoped>

--
Gitblit v1.9.3