gaoluyang
6 天以前 19edc651c0e4718e9ccc645d7417146953fbbd70
浪潮
1.安环管理页面开发联调
已添加4个文件
已修改4个文件
2760 ■■■■■ 文件已修改
src/api/safetyManagement/basicInfo.js 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/safetyManagement/employeeLearning.js 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/safetyManagement/inspectionReport.js 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/safetyManagement/trainingManage.js 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/safetyManagement/basicInfo/index.vue 931 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/safetyManagement/employeeLearning/index.vue 175 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/safetyManagement/inspectionReport/index.vue 293 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/safetyManagement/trainingManage/index.vue 892 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/safetyManagement/basicInfo.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,203 @@
import request from '@/utils/request'
// ==================== äººå‘˜æ¡£æ¡ˆæŽ¥å£ ====================
export function getPersonnelList(query) {
  return request({
    url: '/safety/personnel/list',
    method: 'get',
    params: query
  })
}
export function addPersonnel(data) {
  return request({
    url: '/safety/personnel/add',
    method: 'post',
    data: data
  })
}
export function updatePersonnel(data) {
  return request({
    url: '/safety/personnel/update',
    method: 'put',
    data: data
  })
}
export function deletePersonnel(id) {
  return request({
    url: '/safety/personnel/delete/' + id,
    method: 'delete'
  })
}
export function getPersonnelDetail(id) {
  return request({
    url: '/safety/personnel/detail/' + id,
    method: 'get'
  })
}
// ==================== è®¾å¤‡è®¾æ–½æŽ¥å£ ====================
export function getEquipmentList(query) {
  return request({
    url: '/safety/equipment/list',
    method: 'get',
    params: query
  })
}
export function addEquipment(data) {
  return request({
    url: '/safety/equipment/add',
    method: 'post',
    data: data
  })
}
export function updateEquipment(data) {
  return request({
    url: '/safety/equipment/update',
    method: 'put',
    data: data
  })
}
export function deleteEquipment(id) {
  return request({
    url: '/safety/equipment/delete/' + id,
    method: 'delete'
  })
}
export function getEquipmentDetail(id) {
  return request({
    url: '/safety/equipment/detail/' + id,
    method: 'get'
  })
}
// ==================== ä½œä¸šåŒºåŸŸæŽ¥å£ ====================
export function getWorkAreaList(query) {
  return request({
    url: '/safety/workArea/list',
    method: 'get',
    params: query
  })
}
export function addWorkArea(data) {
  return request({
    url: '/safety/workArea/add',
    method: 'post',
    data: data
  })
}
export function updateWorkArea(data) {
  return request({
    url: '/safety/workArea/update',
    method: 'put',
    data: data
  })
}
export function deleteWorkArea(id) {
  return request({
    url: '/safety/workArea/delete/' + id,
    method: 'delete'
  })
}
export function getWorkAreaDetail(id) {
  return request({
    url: '/safety/workArea/detail/' + id,
    method: 'get'
  })
}
export function getAllWorkArea() {
  return request({
    url: '/safety/workArea/all',
    method: 'get'
  })
}
// ==================== å²—位风险接口 ====================
export function getRiskList(query) {
  return request({
    url: '/safety/risk/list',
    method: 'get',
    params: query
  })
}
export function addRisk(data) {
  return request({
    url: '/safety/risk/add',
    method: 'post',
    data: data
  })
}
export function updateRisk(data) {
  return request({
    url: '/safety/risk/update',
    method: 'put',
    data: data
  })
}
export function deleteRisk(id) {
  return request({
    url: '/safety/risk/delete/' + id,
    method: 'delete'
  })
}
export function getRiskDetail(id) {
  return request({
    url: '/safety/risk/detail/' + id,
    method: 'get'
  })
}
// ==================== åº”急资源接口 ====================
export function getEmergencyList(query) {
  return request({
    url: '/safety/emergency/list',
    method: 'get',
    params: query
  })
}
export function addEmergency(data) {
  return request({
    url: '/safety/emergency/add',
    method: 'post',
    data: data
  })
}
export function updateEmergency(data) {
  return request({
    url: '/safety/emergency/update',
    method: 'put',
    data: data
  })
}
export function deleteEmergency(id) {
  return request({
    url: '/safety/emergency/delete/' + id,
    method: 'delete'
  })
}
export function getEmergencyDetail(id) {
  return request({
    url: '/safety/emergency/detail/' + id,
    method: 'get'
  })
}
src/api/safetyManagement/employeeLearning.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
import request from '@/utils/request'
// ==================== å­¦ä¹ è®°å½•接口 ====================
export function getLearningRecordList(query) {
  return request({
    url: '/safety/learning/record/list',
    method: 'get',
    params: query
  })
}
export function getLearningStatistics() {
  return request({
    url: '/safety/learning/record/statistics',
    method: 'get'
  })
}
// ==================== è€ƒæ ¸æŠ¥å‘ŠæŽ¥å£ ====================
export function getAssessmentReportList(query) {
  return request({
    url: '/safety/learning/report/list',
    method: 'get',
    params: query
  })
}
export function getAssessmentReportDetail(id) {
  return request({
    url: '/safety/learning/report/detail/' + id,
    method: 'get'
  })
}
// ==================== æµ‹è¯„任务接口 ====================
export function getAssessmentTaskList(query) {
  return request({
    url: '/safety/learning/assessment/list',
    method: 'get',
    params: query
  })
}
export function startAssessment(data) {
  return request({
    url: '/safety/learning/assessment/start',
    method: 'post',
    data: data
  })
}
export function submitAssessment(data) {
  return request({
    url: '/safety/learning/assessment/submit',
    method: 'post',
    data: data
  })
}
src/api/safetyManagement/inspectionReport.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
import request from '@/utils/request'
// ==================== å·¡æ£€è®°å½•接口 ====================
export function getInspectionRecordList(query) {
  return request({
    url: '/safety/inspection/record/list',
    method: 'get',
    params: query
  })
}
export function addInspectionRecord(data) {
  return request({
    url: '/safety/inspection/record/add',
    method: 'post',
    data: data
  })
}
export function updateInspectionRecord(data) {
  return request({
    url: '/safety/inspection/record/update',
    method: 'put',
    data: data
  })
}
export function deleteInspectionRecord(id) {
  return request({
    url: '/safety/inspection/record/delete/' + id,
    method: 'delete'
  })
}
// åŒæ­¥å·¡æ£€æ•°æ®ï¼ˆæ¨¡æ‹Ÿå­ç³»ç»Ÿï¼‰
export function syncInspectionData() {
  return request({
    url: '/safety/inspection/record/sync',
    method: 'post'
  })
}
// ==================== ç»Ÿè®¡æŠ¥è¡¨æŽ¥å£ ====================
export function getTodayStatistics() {
  return request({
    url: '/safety/inspection/statistics/today',
    method: 'get'
  })
}
export function getTrendStatistics(params) {
  return request({
    url: '/safety/inspection/statistics/trend',
    method: 'get',
    params: params
  })
}
export function getTypeStatistics(params) {
  return request({
    url: '/safety/inspection/statistics/type',
    method: 'get',
    params: params
  })
}
export function getInspectorStatistics(params) {
  return request({
    url: '/safety/inspection/statistics/inspector',
    method: 'get',
    params: params
  })
}
src/api/safetyManagement/trainingManage.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,135 @@
import request from '@/utils/request'
// ==================== åŸ¹è®­èµ„料接口 ====================
export function getMaterialList(query) {
  return request({
    url: '/safety/training/material/list',
    method: 'get',
    params: query
  })
}
export function uploadMaterial(data) {
  return request({
    url: '/safety/training/material/upload',
    method: 'post',
    data: data
  })
}
export function updateMaterial(data) {
  return request({
    url: '/safety/training/material/update',
    method: 'put',
    data: data
  })
}
export function deleteMaterial(id) {
  return request({
    url: '/safety/training/material/delete/' + id,
    method: 'delete'
  })
}
export function getMaterialDetail(id) {
  return request({
    url: '/safety/training/material/detail/' + id,
    method: 'get'
  })
}
// ==================== åŸ¹è®­è®¡åˆ’接口 ====================
export function getPlanList(query) {
  return request({
    url: '/safety/training/plan/list',
    method: 'get',
    params: query
  })
}
export function addPlan(data) {
  return request({
    url: '/safety/training/plan/add',
    method: 'post',
    data: data
  })
}
export function updatePlan(data) {
  return request({
    url: '/safety/training/plan/update',
    method: 'put',
    data: data
  })
}
export function deletePlan(id) {
  return request({
    url: '/safety/training/plan/delete/' + id,
    method: 'delete'
  })
}
export function getPlanDetail(id) {
  return request({
    url: '/safety/training/plan/detail/' + id,
    method: 'get'
  })
}
// ==================== å®Œæˆè®°å½•接口 ====================
export function getRecordList(query) {
  return request({
    url: '/safety/training/record/list',
    method: 'get',
    params: query
  })
}
export function addRecord(data) {
  return request({
    url: '/safety/training/record/add',
    method: 'post',
    data: data
  })
}
export function updateRecord(data) {
  return request({
    url: '/safety/training/record/update',
    method: 'put',
    data: data
  })
}
export function deleteRecord(id) {
  return request({
    url: '/safety/training/record/delete/' + id,
    method: 'delete'
  })
}
export function getRecordDetail(id) {
  return request({
    url: '/safety/training/record/detail/' + id,
    method: 'get'
  })
}
export function exportRecord(query) {
  return request({
    url: '/safety/training/record/export',
    method: 'get',
    params: query,
    responseType: 'blob'
  })
}
// èŽ·å–å­¦ä¹ ç»Ÿè®¡ï¼ˆç”¨äºŽå‘˜å·¥å­¦ä¹ é¡µé¢ï¼‰
export function getLearningStatistics() {
  return request({
    url: '/safety/training/record/statistics',
    method: 'get'
  })
}
src/views/safetyManagement/basicInfo/index.vue
@@ -18,7 +18,7 @@
          <div class="actions">
            <el-button type="primary" @click="addPersonnel" icon="Plus">新增</el-button>
          </div>
          <PIMTable :column="personnelColumns" :tableData="personnelList" :page="personnelPage" @pagination="changePersonnelPage" />
          <PIMTable :column="personnelColumns" :tableData="personnelList" :page="personnelPage" @pagination="changePersonnelPage" :tableLoading="personnelLoading" />
        </div>
      </el-tab-pane>
@@ -28,7 +28,7 @@
            <el-input v-model="equipmentFilters.name" placeholder="请输入设备名称" clearable style="width: 200px" />
          </el-form-item>
          <el-form-item label="所属区域">
            <el-input v-model="equipmentFilters.area" placeholder="请输入所属区域" clearable style="width: 200px" />
            <el-input v-model="equipmentFilters.areaName" placeholder="请输入所属区域" clearable style="width: 200px" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="getEquipmentData">搜索</el-button>
@@ -39,7 +39,7 @@
          <div class="actions">
            <el-button type="primary" @click="addEquipment" icon="Plus">新增</el-button>
          </div>
          <PIMTable :column="equipmentColumns" :tableData="equipmentList" :page="equipmentPage" @pagination="changeEquipmentPage" />
          <PIMTable :column="equipmentColumns" :tableData="equipmentList" :page="equipmentPage" @pagination="changeEquipmentPage" :tableLoading="equipmentLoading" />
        </div>
      </el-tab-pane>
@@ -57,7 +57,7 @@
          <div class="actions">
            <el-button type="primary" @click="addArea" icon="Plus">新增</el-button>
          </div>
          <PIMTable :column="areaColumns" :tableData="areaList" :page="areaPage" @pagination="changeAreaPage" />
          <PIMTable :column="areaColumns" :tableData="areaList" :page="areaPage" @pagination="changeAreaPage" :tableLoading="areaLoading" />
        </div>
      </el-tab-pane>
@@ -75,7 +75,7 @@
          <div class="actions">
            <el-button type="primary" @click="addRisk" icon="Plus">新增</el-button>
          </div>
          <PIMTable :column="riskColumns" :tableData="riskList" :page="riskPage" @pagination="changeRiskPage" />
          <PIMTable :column="riskColumns" :tableData="riskList" :page="riskPage" @pagination="changeRiskPage" :tableLoading="riskLoading" />
        </div>
      </el-tab-pane>
@@ -93,16 +93,197 @@
          <div class="actions">
            <el-button type="primary" @click="addEmergency" icon="Plus">新增</el-button>
          </div>
          <PIMTable :column="emergencyColumns" :tableData="emergencyList" :page="emergencyPage" @pagination="changeEmergencyPage" />
          <PIMTable :column="emergencyColumns" :tableData="emergencyList" :page="emergencyPage" @pagination="changeEmergencyPage" :tableLoading="emergencyLoading" />
        </div>
      </el-tab-pane>
    </el-tabs>
    <!-- äººå‘˜æ¡£æ¡ˆå¼¹çª— -->
    <el-dialog :title="personnelDialog.title" v-model="personnelDialog.visible" width="600px" append-to-body>
      <el-form ref="personnelFormRef" :model="personnelForm" :rules="personnelRules" label-width="100px">
        <el-form-item label="姓名" prop="name">
          <el-input v-model="personnelForm.name" placeholder="请输入姓名" />
        </el-form-item>
        <el-form-item label="部门" prop="dept">
          <el-input v-model="personnelForm.dept" placeholder="请输入部门" />
        </el-form-item>
        <el-form-item label="岗位" prop="post">
          <el-input v-model="personnelForm.post" placeholder="请输入岗位" />
        </el-form-item>
        <el-form-item label="联系方式" prop="phone">
          <el-input v-model="personnelForm.phone" placeholder="请输入联系方式" />
        </el-form-item>
        <el-form-item label="入职日期" prop="entryDate">
          <el-date-picker v-model="personnelForm.entryDate" type="date" placeholder="选择入职日期" value-format="YYYY-MM-DD" style="width: 100%" />
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="personnelForm.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="personnelForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="personnelDialog.visible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="submitPersonnelForm" :loading="personnelDialog.loading">ç¡® å®š</el-button>
      </template>
    </el-dialog>
    <!-- è®¾å¤‡è®¾æ–½å¼¹çª— -->
    <el-dialog :title="equipmentDialog.title" v-model="equipmentDialog.visible" width="600px" append-to-body>
      <el-form ref="equipmentFormRef" :model="equipmentForm" :rules="equipmentRules" label-width="100px">
        <el-form-item label="设备名称" prop="name">
          <el-input v-model="equipmentForm.name" placeholder="请输入设备名称" />
        </el-form-item>
        <el-form-item label="规格型号" prop="model">
          <el-input v-model="equipmentForm.model" placeholder="请输入规格型号" />
        </el-form-item>
        <el-form-item label="所属区域" prop="areaName">
          <el-input v-model="equipmentForm.areaName" placeholder="请输入所属区域" />
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="equipmentForm.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="equipmentForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="equipmentDialog.visible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="submitEquipmentForm" :loading="equipmentDialog.loading">ç¡® å®š</el-button>
      </template>
    </el-dialog>
    <!-- ä½œä¸šåŒºåŸŸå¼¹çª— -->
    <el-dialog :title="areaDialog.title" v-model="areaDialog.visible" width="600px" append-to-body>
      <el-form ref="areaFormRef" :model="areaForm" :rules="areaRules" label-width="100px">
        <el-form-item label="区域名称" prop="name">
          <el-input v-model="areaForm.name" placeholder="请输入区域名称" />
        </el-form-item>
        <el-form-item label="位置" prop="location">
          <el-input v-model="areaForm.location" placeholder="请输入位置" />
        </el-form-item>
        <el-form-item label="负责人" prop="manager">
          <el-input v-model="areaForm.manager" placeholder="请输入负责人" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="areaForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="areaDialog.visible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="submitAreaForm" :loading="areaDialog.loading">ç¡® å®š</el-button>
      </template>
    </el-dialog>
    <!-- å²—位风险弹窗 -->
    <el-dialog :title="riskDialog.title" v-model="riskDialog.visible" width="600px" append-to-body>
      <el-form ref="riskFormRef" :model="riskForm" :rules="riskRules" label-width="100px">
        <el-form-item label="风险类型" prop="type">
          <el-input v-model="riskForm.type" placeholder="请输入风险类型" />
        </el-form-item>
        <el-form-item label="风险等级" prop="level">
          <el-select v-model="riskForm.level" placeholder="请选择风险等级" style="width: 100%">
            <el-option label="高" value="high" />
            <el-option label="中" value="medium" />
            <el-option label="低" value="low" />
          </el-select>
        </el-form-item>
        <el-form-item label="关联岗位" prop="post">
          <el-input v-model="riskForm.post" placeholder="请输入关联岗位" />
        </el-form-item>
        <el-form-item label="风险描述" prop="description">
          <el-input v-model="riskForm.description" type="textarea" :rows="3" placeholder="请输入风险描述" />
        </el-form-item>
        <el-form-item label="控制措施" prop="controlMeasures">
          <el-input v-model="riskForm.controlMeasures" type="textarea" :rows="3" placeholder="请输入控制措施" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="riskForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="riskDialog.visible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="submitRiskForm" :loading="riskDialog.loading">ç¡® å®š</el-button>
      </template>
    </el-dialog>
    <!-- åº”急资源弹窗 -->
    <el-dialog :title="emergencyDialog.title" v-model="emergencyDialog.visible" width="600px" append-to-body>
      <el-form ref="emergencyFormRef" :model="emergencyForm" :rules="emergencyRules" label-width="100px">
        <el-form-item label="资源名称" prop="name">
          <el-input v-model="emergencyForm.name" placeholder="请输入资源名称" />
        </el-form-item>
        <el-form-item label="资源类型" prop="type">
          <el-input v-model="emergencyForm.type" placeholder="请输入资源类型" />
        </el-form-item>
        <el-form-item label="数量" prop="quantity">
          <el-input-number v-model="emergencyForm.quantity" :min="0" style="width: 100%" />
        </el-form-item>
        <el-form-item label="所属区域" prop="areaName">
          <el-input v-model="emergencyForm.areaName" placeholder="请输入所属区域" />
        </el-form-item>
        <el-form-item label="存放位置" prop="location">
          <el-input v-model="emergencyForm.location" placeholder="请输入存放位置" />
        </el-form-item>
        <el-form-item label="管理人" prop="manager">
          <el-input v-model="emergencyForm.manager" placeholder="请输入管理人" />
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="emergencyForm.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="emergencyForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="emergencyDialog.visible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="submitEmergencyForm" :loading="emergencyDialog.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 {
  getPersonnelList,
  addPersonnel as addPersonnelApi,
  updatePersonnel,
  deletePersonnel,
  getPersonnelDetail,
  getEquipmentList,
  addEquipment as addEquipmentApi,
  updateEquipment,
  deleteEquipment,
  getEquipmentDetail,
  getWorkAreaList,
  addWorkArea as addWorkAreaApi,
  updateWorkArea,
  deleteWorkArea,
  getWorkAreaDetail,
  getRiskList,
  addRisk as addRiskApi,
  updateRisk,
  deleteRisk,
  getRiskDetail,
  getEmergencyList,
  addEmergency as addEmergencyApi,
  updateEmergency,
  deleteEmergency,
  getEmergencyDetail
} from "@/api/safetyManagement/basicInfo.js";
import { ElMessage, ElMessageBox } from "element-plus";
defineOptions({
  name: "基础信息管理",
@@ -110,88 +291,762 @@
const activeTab = ref("personnel");
// äººå‘˜æ¡£æ¡ˆ
// ==================== äººå‘˜æ¡£æ¡ˆ ====================
const personnelFilters = reactive({ name: "", dept: "" });
const personnelList = ref([]);
const personnelPage = reactive({ current: 1, size: 10, total: 0 });
const personnelLoading = ref(false);
const personnelColumns = [
  { label: "姓名", prop: "name", align: "center" },
  { label: "部门", prop: "dept", align: "center" },
  { label: "岗位", prop: "post", align: "center" },
  { label: "联系方式", prop: "phone", align: "center" },
  { label: "入职日期", prop: "entryDate", 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) => handleEditPersonnel(row) },
      { name: "删除", type: "text", clickFun: (row) => handleDeletePersonnel(row) }
    ]
  }
];
// è®¾å¤‡è®¾æ–½
const equipmentFilters = reactive({ name: "", area: "" });
const personnelDialog = reactive({ visible: false, title: "", loading: false });
const personnelFormRef = ref(null);
const personnelForm = reactive({
  id: null,
  name: "",
  dept: "",
  post: "",
  phone: "",
  entryDate: "",
  status: 1,
  remark: ""
});
const personnelRules = {
  name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
  dept: [{ required: true, message: "请输入部门", trigger: "blur" }]
};
// ==================== è®¾å¤‡è®¾æ–½ ====================
const equipmentFilters = reactive({ name: "", areaName: "" });
const equipmentList = ref([]);
const equipmentPage = reactive({ current: 1, size: 10, total: 0 });
const equipmentLoading = ref(false);
const equipmentColumns = [
  { label: "设备名称", prop: "name", align: "center" },
  { label: "规格型号", prop: "model", align: "center" },
  { label: "所属区域", prop: "area", align: "center" },
  { label: "状态", prop: "status", align: "center" },
  { label: "所属区域", prop: "areaName", align: "center" },
  {
    label: "状态",
    prop: "status",
    align: "center",
    dataType: "tag",
    formatType: (val) => (val === 1 ? "success" : "danger"),
    formatData: (val) => (val === 1 ? "正常" : "停用")
  },
  {
    label: "操作",
    prop: "action",
    align: "center",
    dataType: "action",
    operation: [
      { name: "编辑", type: "text", clickFun: (row) => handleEditEquipment(row) },
      { name: "删除", type: "text", clickFun: (row) => handleDeleteEquipment(row) }
    ]
  }
];
// ä½œä¸šåŒºåŸŸ
const equipmentDialog = reactive({ visible: false, title: "", loading: false });
const equipmentFormRef = ref(null);
const equipmentForm = reactive({
  id: null,
  name: "",
  model: "",
  areaName: "",
  status: 1,
  remark: ""
});
const equipmentRules = {
  name: [{ required: true, message: "请输入设备名称", trigger: "blur" }]
};
// ==================== ä½œä¸šåŒºåŸŸ ====================
const areaFilters = reactive({ name: "" });
const areaList = ref([]);
const areaPage = reactive({ current: 1, size: 10, total: 0 });
const areaLoading = ref(false);
const areaColumns = [
  { label: "区域名称", prop: "name", align: "center" },
  { label: "位置", prop: "location", align: "center" },
  { label: "负责人", prop: "manager", align: "center" },
  {
    label: "操作",
    prop: "action",
    align: "center",
    dataType: "action",
    operation: [
      { name: "编辑", type: "text", clickFun: (row) => handleEditArea(row) },
      { name: "删除", type: "text", clickFun: (row) => handleDeleteArea(row) }
    ]
  }
];
// å²—位风险
const areaDialog = reactive({ visible: false, title: "", loading: false });
const areaFormRef = ref(null);
const areaForm = reactive({
  id: null,
  name: "",
  location: "",
  manager: "",
  remark: ""
});
const areaRules = {
  name: [{ required: true, message: "请输入区域名称", trigger: "blur" }]
};
// ==================== å²—位风险 ====================
const riskFilters = reactive({ type: "" });
const riskList = ref([]);
const riskPage = reactive({ current: 1, size: 10, total: 0 });
const riskLoading = ref(false);
const riskColumns = [
  { label: "风险类型", prop: "type", align: "center" },
  { label: "风险等级", prop: "level", align: "center" },
  {
    label: "风险等级",
    prop: "level",
    align: "center",
    dataType: "tag",
    formatType: (val) => {
      if (val === 'high') return 'danger';
      if (val === 'medium') return 'warning';
      return 'success';
    },
    formatData: (val) => {
      const map = { high: '高', medium: '中', low: '低' };
      return map[val] || val;
    }
  },
  { label: "描述", prop: "description", align: "center" },
  {
    label: "操作",
    prop: "action",
    align: "center",
    dataType: "action",
    operation: [
      { name: "编辑", type: "text", clickFun: (row) => handleEditRisk(row) },
      { name: "删除", type: "text", clickFun: (row) => handleDeleteRisk(row) }
    ]
  }
];
// åº”急资源
const riskDialog = reactive({ visible: false, title: "", loading: false });
const riskFormRef = ref(null);
const riskForm = reactive({
  id: null,
  type: "",
  level: "",
  description: "",
  post: "",
  controlMeasures: "",
  remark: ""
});
const riskRules = {
  type: [{ required: true, message: "请输入风险类型", trigger: "blur" }],
  level: [{ required: true, message: "请选择风险等级", trigger: "change" }]
};
// ==================== åº”急资源 ====================
const emergencyFilters = reactive({ name: "" });
const emergencyList = ref([]);
const emergencyPage = reactive({ current: 1, size: 10, total: 0 });
const emergencyLoading = ref(false);
const emergencyColumns = [
  { label: "资源名称", prop: "name", align: "center" },
  { label: "类型", prop: "type", align: "center" },
  { label: "数量", prop: "quantity", align: "center" },
  { label: "存放位置", prop: "location", align: "center" },
  {
    label: "状态",
    prop: "status",
    align: "center",
    dataType: "tag",
    formatType: (val) => (val === 1 ? "success" : "danger"),
    formatData: (val) => (val === 1 ? "正常" : "缺失")
  },
  {
    label: "操作",
    prop: "action",
    align: "center",
    dataType: "action",
    operation: [
      { name: "编辑", type: "text", clickFun: (row) => handleEditEmergency(row) },
      { name: "删除", type: "text", clickFun: (row) => handleDeleteEmergency(row) }
    ]
  }
];
const handleTabChange = () => {
  // åˆ‡æ¢tab时加载对应数据
const emergencyDialog = reactive({ visible: false, title: "", loading: false });
const emergencyFormRef = ref(null);
const emergencyForm = reactive({
  id: null,
  name: "",
  type: "",
  quantity: 0,
  areaName: "",
  location: "",
  manager: "",
  status: 1,
  remark: ""
});
const emergencyRules = {
  name: [{ required: true, message: "请输入资源名称", trigger: "blur" }],
  type: [{ required: true, message: "请输入资源类型", trigger: "blur" }]
};
const getPersonnelData = () => {};
const resetPersonnelFilters = () => { personnelFilters.name = ""; personnelFilters.dept = ""; };
const addPersonnel = () => {};
const changePersonnelPage = ({ page, limit }) => { personnelPage.current = page; personnelPage.size = limit; };
// ==================== é€šç”¨æ–¹æ³• ====================
const loadData = () => {
  switch (activeTab.value) {
    case 'personnel':
      getPersonnelData();
      break;
    case 'equipment':
      getEquipmentData();
      break;
    case 'workArea':
      getAreaData();
      break;
    case 'risk':
      getRiskData();
      break;
    case 'emergency':
      getEmergencyData();
      break;
  }
};
const getEquipmentData = () => {};
const resetEquipmentFilters = () => { equipmentFilters.name = ""; equipmentFilters.area = ""; };
const addEquipment = () => {};
const changeEquipmentPage = ({ page, limit }) => { equipmentPage.current = page; equipmentPage.size = limit; };
const handleTabChange = () => {
  loadData();
};
const getAreaData = () => {};
const resetAreaFilters = () => { areaFilters.name = ""; };
const addArea = () => {};
const changeAreaPage = ({ page, limit }) => { areaPage.current = page; areaPage.size = limit; };
const getRiskData = () => {};
const resetRiskFilters = () => { riskFilters.type = ""; };
const addRisk = () => {};
const changeRiskPage = ({ page, limit }) => { riskPage.current = page; riskPage.size = limit; };
const getEmergencyData = () => {};
const resetEmergencyFilters = () => { emergencyFilters.name = ""; };
const addEmergency = () => {};
const changeEmergencyPage = ({ page, limit }) => { emergencyPage.current = page; emergencyPage.size = limit; };
// ==================== äººå‘˜æ¡£æ¡ˆæ–¹æ³• ====================
const getPersonnelData = async () => {
  personnelLoading.value = true;
  try {
    const res = await getPersonnelList({
      pageNum: personnelPage.current,
      pageSize: personnelPage.size,
      ...personnelFilters
    });
    if (res.code === 200) {
      personnelList.value = res.data.rows || res.data.records || [];
      personnelPage.total = res.data.total || 0;
    }
  } catch (error) {
    ElMessage.error('获取人员档案失败');
  } finally {
    personnelLoading.value = false;
  }
};
const resetPersonnelFilters = () => {
  personnelFilters.name = "";
  personnelFilters.dept = "";
  personnelPage.current = 1;
  getPersonnelData();
};
const changePersonnelPage = ({ page, limit }) => {
  personnelPage.current = page;
  personnelPage.size = limit;
  getPersonnelData();
};
const resetPersonnelForm = () => {
  personnelForm.id = null;
  personnelForm.name = "";
  personnelForm.dept = "";
  personnelForm.post = "";
  personnelForm.phone = "";
  personnelForm.entryDate = "";
  personnelForm.status = 1;
  personnelForm.remark = "";
};
const addPersonnel = () => {
  resetPersonnelForm();
  personnelDialog.title = "新增人员档案";
  personnelDialog.visible = true;
};
const handleEditPersonnel = async (row) => {
  resetPersonnelForm();
  try {
    const res = await getPersonnelDetail(row.id);
    if (res.code === 200) {
      Object.assign(personnelForm, res.data);
      personnelDialog.title = "编辑人员档案";
      personnelDialog.visible = true;
    }
  } catch (error) {
    ElMessage.error('获取人员档案详情失败');
  }
};
const handleDeletePersonnel = (row) => {
  ElMessageBox.confirm(`确认删除人员 "${row.name}" å—?`, "提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning"
  }).then(async () => {
    try {
      const res = await deletePersonnel(row.id);
      if (res.code === 200) {
        ElMessage.success("删除成功");
        getPersonnelData();
      }
    } catch (error) {
      ElMessage.error("删除失败");
    }
  });
};
const submitPersonnelForm = async () => {
  const valid = await personnelFormRef.value.validate().catch(() => false);
  if (!valid) return;
  personnelDialog.loading = true;
  try {
    const api = personnelForm.id ? updatePersonnel : addPersonnelApi;
    const res = await api(personnelForm);
    if (res.code === 200) {
      ElMessage.success(personnelForm.id ? "修改成功" : "新增成功");
      personnelDialog.visible = false;
      getPersonnelData();
    }
  } catch (error) {
    ElMessage.error(personnelForm.id ? "修改失败" : "新增失败");
  } finally {
    personnelDialog.loading = false;
  }
};
// ==================== è®¾å¤‡è®¾æ–½æ–¹æ³• ====================
const getEquipmentData = async () => {
  equipmentLoading.value = true;
  try {
    const res = await getEquipmentList({
      pageNum: equipmentPage.current,
      pageSize: equipmentPage.size,
      ...equipmentFilters
    });
    if (res.code === 200) {
      equipmentList.value = res.data.rows || res.data.records || [];
      equipmentPage.total = res.data.total || 0;
    }
  } catch (error) {
    ElMessage.error('获取设备设施失败');
  } finally {
    equipmentLoading.value = false;
  }
};
const resetEquipmentFilters = () => {
  equipmentFilters.name = "";
  equipmentFilters.areaName = "";
  equipmentPage.current = 1;
  getEquipmentData();
};
const changeEquipmentPage = ({ page, limit }) => {
  equipmentPage.current = page;
  equipmentPage.size = limit;
  getEquipmentData();
};
const resetEquipmentForm = () => {
  equipmentForm.id = null;
  equipmentForm.name = "";
  equipmentForm.model = "";
  equipmentForm.areaName = "";
  equipmentForm.status = 1;
  equipmentForm.remark = "";
};
const addEquipment = () => {
  resetEquipmentForm();
  equipmentDialog.title = "新增设备设施";
  equipmentDialog.visible = true;
};
const handleEditEquipment = async (row) => {
  resetEquipmentForm();
  try {
    const res = await getEquipmentDetail(row.id);
    if (res.code === 200) {
      Object.assign(equipmentForm, res.data);
      equipmentDialog.title = "编辑设备设施";
      equipmentDialog.visible = true;
    }
  } catch (error) {
    ElMessage.error('获取设备详情失败');
  }
};
const handleDeleteEquipment = (row) => {
  ElMessageBox.confirm(`确认删除设备 "${row.name}" å—?`, "提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning"
  }).then(async () => {
    try {
      const res = await deleteEquipment(row.id);
      if (res.code === 200) {
        ElMessage.success("删除成功");
        getEquipmentData();
      }
    } catch (error) {
      ElMessage.error("删除失败");
    }
  });
};
const submitEquipmentForm = async () => {
  const valid = await equipmentFormRef.value.validate().catch(() => false);
  if (!valid) return;
  equipmentDialog.loading = true;
  try {
    const api = equipmentForm.id ? updateEquipment : addEquipmentApi;
    const res = await api(equipmentForm);
    if (res.code === 200) {
      ElMessage.success(equipmentForm.id ? "修改成功" : "新增成功");
      equipmentDialog.visible = false;
      getEquipmentData();
    }
  } catch (error) {
    ElMessage.error(equipmentForm.id ? "修改失败" : "新增失败");
  } finally {
    equipmentDialog.loading = false;
  }
};
// ==================== ä½œä¸šåŒºåŸŸæ–¹æ³• ====================
const getAreaData = async () => {
  areaLoading.value = true;
  try {
    const res = await getWorkAreaList({
      pageNum: areaPage.current,
      pageSize: areaPage.size,
      ...areaFilters
    });
    if (res.code === 200) {
      areaList.value = res.data.rows || res.data.records || [];
      areaPage.total = res.data.total || 0;
    }
  } catch (error) {
    ElMessage.error('获取作业区域失败');
  } finally {
    areaLoading.value = false;
  }
};
const resetAreaFilters = () => {
  areaFilters.name = "";
  areaPage.current = 1;
  getAreaData();
};
const changeAreaPage = ({ page, limit }) => {
  areaPage.current = page;
  areaPage.size = limit;
  getAreaData();
};
const resetAreaForm = () => {
  areaForm.id = null;
  areaForm.name = "";
  areaForm.location = "";
  areaForm.manager = "";
  areaForm.remark = "";
};
const addArea = () => {
  resetAreaForm();
  areaDialog.title = "新增作业区域";
  areaDialog.visible = true;
};
const handleEditArea = async (row) => {
  resetAreaForm();
  try {
    const res = await getWorkAreaDetail(row.id);
    if (res.code === 200) {
      Object.assign(areaForm, res.data);
      areaDialog.title = "编辑作业区域";
      areaDialog.visible = true;
    }
  } catch (error) {
    ElMessage.error('获取作业区域详情失败');
  }
};
const handleDeleteArea = (row) => {
  ElMessageBox.confirm(`确认删除区域 "${row.name}" å—?`, "提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning"
  }).then(async () => {
    try {
      const res = await deleteWorkArea(row.id);
      if (res.code === 200) {
        ElMessage.success("删除成功");
        getAreaData();
      }
    } catch (error) {
      ElMessage.error("删除失败");
    }
  });
};
const submitAreaForm = async () => {
  const valid = await areaFormRef.value.validate().catch(() => false);
  if (!valid) return;
  areaDialog.loading = true;
  try {
    const api = areaForm.id ? updateWorkArea : addWorkAreaApi;
    const res = await api(areaForm);
    if (res.code === 200) {
      ElMessage.success(areaForm.id ? "修改成功" : "新增成功");
      areaDialog.visible = false;
      getAreaData();
    }
  } catch (error) {
    ElMessage.error(areaForm.id ? "修改失败" : "新增失败");
  } finally {
    areaDialog.loading = false;
  }
};
// ==================== å²—位风险方法 ====================
const getRiskData = async () => {
  riskLoading.value = true;
  try {
    const res = await getRiskList({
      pageNum: riskPage.current,
      pageSize: riskPage.size,
      ...riskFilters
    });
    if (res.code === 200) {
      riskList.value = res.data.rows || res.data.records || [];
      riskPage.total = res.data.total || 0;
    }
  } catch (error) {
    ElMessage.error('获取岗位风险失败');
  } finally {
    riskLoading.value = false;
  }
};
const resetRiskFilters = () => {
  riskFilters.type = "";
  riskPage.current = 1;
  getRiskData();
};
const changeRiskPage = ({ page, limit }) => {
  riskPage.current = page;
  riskPage.size = limit;
  getRiskData();
};
const resetRiskForm = () => {
  riskForm.id = null;
  riskForm.type = "";
  riskForm.level = "";
  riskForm.description = "";
  riskForm.post = "";
  riskForm.controlMeasures = "";
  riskForm.remark = "";
};
const addRisk = () => {
  resetRiskForm();
  riskDialog.title = "新增岗位风险";
  riskDialog.visible = true;
};
const handleEditRisk = async (row) => {
  resetRiskForm();
  try {
    const res = await getRiskDetail(row.id);
    if (res.code === 200) {
      Object.assign(riskForm, res.data);
      riskDialog.title = "编辑岗位风险";
      riskDialog.visible = true;
    }
  } catch (error) {
    ElMessage.error('获取岗位风险详情失败');
  }
};
const handleDeleteRisk = (row) => {
  ElMessageBox.confirm(`确认删除风险 "${row.type}" å—?`, "提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning"
  }).then(async () => {
    try {
      const res = await deleteRisk(row.id);
      if (res.code === 200) {
        ElMessage.success("删除成功");
        getRiskData();
      }
    } catch (error) {
      ElMessage.error("删除失败");
    }
  });
};
const submitRiskForm = async () => {
  const valid = await riskFormRef.value.validate().catch(() => false);
  if (!valid) return;
  riskDialog.loading = true;
  try {
    const api = riskForm.id ? updateRisk : addRiskApi;
    const res = await api(riskForm);
    if (res.code === 200) {
      ElMessage.success(riskForm.id ? "修改成功" : "新增成功");
      riskDialog.visible = false;
      getRiskData();
    }
  } catch (error) {
    ElMessage.error(riskForm.id ? "修改失败" : "新增失败");
  } finally {
    riskDialog.loading = false;
  }
};
// ==================== åº”急资源方法 ====================
const getEmergencyData = async () => {
  emergencyLoading.value = true;
  try {
    const res = await getEmergencyList({
      pageNum: emergencyPage.current,
      pageSize: emergencyPage.size,
      ...emergencyFilters
    });
    if (res.code === 200) {
      emergencyList.value = res.data.rows || res.data.records || [];
      emergencyPage.total = res.data.total || 0;
    }
  } catch (error) {
    ElMessage.error('获取应急资源失败');
  } finally {
    emergencyLoading.value = false;
  }
};
const resetEmergencyFilters = () => {
  emergencyFilters.name = "";
  emergencyPage.current = 1;
  getEmergencyData();
};
const changeEmergencyPage = ({ page, limit }) => {
  emergencyPage.current = page;
  emergencyPage.size = limit;
  getEmergencyData();
};
const resetEmergencyForm = () => {
  emergencyForm.id = null;
  emergencyForm.name = "";
  emergencyForm.type = "";
  emergencyForm.quantity = 0;
  emergencyForm.areaName = "";
  emergencyForm.location = "";
  emergencyForm.manager = "";
  emergencyForm.status = 1;
  emergencyForm.remark = "";
};
const addEmergency = () => {
  resetEmergencyForm();
  emergencyDialog.title = "新增应急资源";
  emergencyDialog.visible = true;
};
const handleEditEmergency = async (row) => {
  resetEmergencyForm();
  try {
    const res = await getEmergencyDetail(row.id);
    if (res.code === 200) {
      Object.assign(emergencyForm, res.data);
      emergencyDialog.title = "编辑应急资源";
      emergencyDialog.visible = true;
    }
  } catch (error) {
    ElMessage.error('获取应急资源详情失败');
  }
};
const handleDeleteEmergency = (row) => {
  ElMessageBox.confirm(`确认删除资源 "${row.name}" å—?`, "提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning"
  }).then(async () => {
    try {
      const res = await deleteEmergency(row.id);
      if (res.code === 200) {
        ElMessage.success("删除成功");
        getEmergencyData();
      }
    } catch (error) {
      ElMessage.error("删除失败");
    }
  });
};
const submitEmergencyForm = async () => {
  const valid = await emergencyFormRef.value.validate().catch(() => false);
  if (!valid) return;
  emergencyDialog.loading = true;
  try {
    const api = emergencyForm.id ? updateEmergency : addEmergencyApi;
    const res = await api(emergencyForm);
    if (res.code === 200) {
      ElMessage.success(emergencyForm.id ? "修改成功" : "新增成功");
      emergencyDialog.visible = false;
      getEmergencyData();
    }
  } catch (error) {
    ElMessage.error(emergencyForm.id ? "修改失败" : "新增失败");
  } finally {
    emergencyDialog.loading = false;
  }
};
onMounted(() => {
  getPersonnelData();
});
</script>
<style lang="scss" scoped>
src/views/safetyManagement/employeeLearning/index.vue
@@ -11,103 +11,128 @@
          <el-divider />
          <div class="stat-item">
            <span class="label">已完成培训</span>
            <span class="value">{{ userInfo.completedTrainings }} æ¬¡</span>
            <span class="value">{{ statistics.completedTrainings }} æ¬¡</span>
          </div>
          <div class="stat-item">
            <span class="label">待参加培训</span>
            <span class="value">{{ userInfo.pendingTrainings }} æ¬¡</span>
            <span class="value">{{ statistics.pendingTrainings }} æ¬¡</span>
          </div>
          <div class="stat-item">
            <span class="label">平均考核分数</span>
            <span class="value">{{ userInfo.avgScore }} åˆ†</span>
            <span class="value">{{ statistics.avgScore }} åˆ†</span>
          </div>
        </el-card>
      </el-col>
      <el-col :span="18">
        <el-tabs v-model="activeTab" type="border-card">
          <el-tab-pane label="我的学习记录" name="records">
            <PIMTable :column="recordColumns" :tableData="recordList" :page="recordPage" @pagination="changeRecordPage" />
          </el-tab-pane>
          <el-tab-pane label="考核报告" name="reports">
            <PIMTable :column="reportColumns" :tableData="reportList" :page="reportPage" @pagination="changeReportPage" />
          </el-tab-pane>
          <el-tab-pane label="在线测评" name="assessment">
            <div class="assessment-list">
              <el-card v-for="item in assessmentList" :key="item.id" class="assessment-card">
                <div class="assessment-header">
                  <h4>{{ item.title }}</h4>
                  <el-tag :type="item.status === '待测评' ? 'warning' : 'success'">{{ item.status }}</el-tag>
                </div>
                <p class="assessment-desc">{{ item.description }}</p>
                <div class="assessment-footer">
                  <span>截止时间:{{ item.deadline }}</span>
                  <el-button type="primary" size="small" :disabled="item.status !== '待测评'" @click="startAssessment(item)">
                    {{ item.status === '待测评' ? '开始测评' : '已完成' }}
                  </el-button>
                </div>
              </el-card>
            </div>
          </el-tab-pane>
        </el-tabs>
        <el-card>
          <template #header>
            <span>我的学习记录</span>
          </template>
          <PIMTable :column="recordColumns" :tableData="recordList" :page="recordPage" @pagination="changeRecordPage" :tableLoading="recordLoading" />
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
<script setup>
import { ref, reactive } from "vue";
import { ref, reactive, onMounted, computed } from "vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import useUserStore from "@/store/modules/user";
import {
  getRecordList
} from "@/api/safetyManagement/trainingManage.js";
import { ElMessage } from "element-plus";
defineOptions({
  name: "员工学习",
});
const activeTab = ref("records");
const userStore = useUserStore();
const userInfo = reactive({
  name: "张三",
  dept: "生产部",
  post: "操作工",
  avatar: "",
  completedTrainings: 12,
  pendingTrainings: 3,
  avgScore: 85,
const userInfo = computed(() => ({
  name: userStore.name || "-",
  dept: userStore.currentFactoryName || "-",
  post: userStore.roleName || "-",
  avatar: userStore.avatar || "",
}));
const statistics = reactive({
  completedTrainings: 0,
  pendingTrainings: 0,
  avgScore: 0,
});
// å­¦ä¹ è®°å½•
// å­¦ä¹ è®°å½•(来自培训完成记录)
const recordList = ref([]);
const recordPage = reactive({ current: 1, size: 10, total: 0 });
const recordLoading = ref(false);
const recordColumns = [
  { label: "培训内容", prop: "content", align: "center" },
  { label: "培训时间", prop: "trainingTime", align: "center" },
  { label: "培训时长", prop: "duration", align: "center" },
  { label: "完成时间", prop: "completeTime", align: "center" },
  { label: "学习时长", prop: "duration", align: "center" },
  { label: "培训方式", prop: "method", align: "center" },
  { label: "完成状态", prop: "status", align: "center" },
  { label: "分数", prop: "score", align: "center" },
  {
    label: "考核结果",
    prop: "result",
    align: "center",
    dataType: "tag",
    formatType: (val) => {
      if (val === '通过') return 'success';
      if (val === '未通过') return 'danger';
      return 'info';
    },
    formatData: (val) => val || '-'
  }
];
// è€ƒæ ¸æŠ¥å‘Š
const reportList = ref([]);
const reportPage = reactive({ current: 1, size: 10, total: 0 });
const reportColumns = [
  { label: "考核名称", prop: "name", align: "center" },
  { label: "考核时间", prop: "assessTime", align: "center" },
  { label: "得分", prop: "score", align: "center" },
  { label: "等级", prop: "grade", align: "center" },
  { label: "是否合格", prop: "qualified", align: "center" },
];
// åœ¨çº¿æµ‹è¯„
const assessmentList = ref([
  { id: 1, title: "安全操作规程测评", description: "考核员工对安全操作规程的掌握程度", deadline: "2026-05-30", status: "待测评" },
  { id: 2, title: "应急处置能力测评", description: "考核员工应急处置能力", deadline: "2026-06-15", status: "已完成" },
]);
const changeRecordPage = ({ page, limit }) => { recordPage.current = page; recordPage.size = limit; };
const changeReportPage = ({ page, limit }) => { reportPage.current = page; reportPage.size = limit; };
const startAssessment = (item) => {};
// è®¡ç®—统计数据
const calculateStatistics = (records) => {
  const completedTrainings = records.filter(r => r.status === 1).length;
  const pendingTrainings = records.filter(r => r.status === 0).length;
  const scores = records.filter(r => r.score > 0).map(r => r.score);
  const avgScore = scores.length > 0 ? (scores.reduce((a, b) => a + b, 0) / scores.length).toFixed(1) : 0;
  statistics.completedTrainings = completedTrainings;
  statistics.pendingTrainings = pendingTrainings;
  statistics.avgScore = avgScore;
};
// å­¦ä¹ è®°å½•方法(查询当前用户的完成记录)
const getRecordData = async () => {
  recordLoading.value = true;
  try {
    const res = await getRecordList({
      pageNum: recordPage.current,
      pageSize: recordPage.size,
      employeeId: userStore.userId // åªæŸ¥è¯¢å½“前用户的数据
    });
    if (res.code === 200) {
      recordList.value = res.data.rows || res.data.records || [];
      recordPage.total = res.data.total || 0;
      // æ ¹æ®æŸ¥è¯¢åˆ°çš„æ•°æ®è®¡ç®—统计
      calculateStatistics(recordList.value);
    }
  } catch (error) {
    ElMessage.error('获取学习记录失败');
  } finally {
    recordLoading.value = false;
  }
};
const changeRecordPage = ({ page, limit }) => {
  recordPage.current = page;
  recordPage.size = limit;
  getRecordData();
};
onMounted(() => {
  getRecordData();
});
</script>
<style lang="scss" scoped>
@@ -136,30 +161,4 @@
  }
}
.assessment-list {
  .assessment-card {
    margin-bottom: 15px;
    .assessment-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      h4 {
        margin: 0;
      }
    }
    .assessment-desc {
      color: #666;
      margin: 10px 0;
    }
    .assessment-footer {
      display: flex;
      justify-content: space-between;
      align-items: center;
      span {
        color: #999;
        font-size: 12px;
      }
    }
  }
}
</style>
src/views/safetyManagement/inspectionReport/index.vue
@@ -79,112 +79,305 @@
        </el-form-item>
        <el-form-item label="状态">
          <el-select v-model="filters.status" placeholder="请选择" clearable style="width: 150px">
            <el-option label="正常" value="normal" />
            <el-option label="异常" value="abnormal" />
            <el-option label="漏检" value="missed" />
            <el-option label="未执行" value="unexecuted" />
            <el-option label="正常" :value="0" />
            <el-option label="异常" :value="1" />
            <el-option label="漏检" :value="2" />
            <el-option label="未执行" :value="3" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="getData">搜索</el-button>
          <el-button @click="resetFilters">重置</el-button>
          <el-button type="success" @click="handleSyncData" icon="Refresh">同步数据</el-button>
        </el-form-item>
      </el-form>
      <PIMTable :column="columns" :tableData="dataList" :page="pagination" @pagination="changePage" />
      <PIMTable :column="columns" :tableData="dataList" :page="pagination" @pagination="changePage" :tableLoading="tableLoading" />
    </el-card>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { ref, reactive, onMounted, nextTick } from "vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import * as echarts from "echarts";
import {
  getInspectionRecordList,
  getTodayStatistics,
  getTrendStatistics,
  getTypeStatistics,
  syncInspectionData
} from "@/api/safetyManagement/inspectionReport.js";
import { ElMessage } from "element-plus";
import dayjs from "dayjs";
defineOptions({
  name: "巡检数据报表",
});
const todayStats = reactive({
  completionRate: 95,
  inspectorCount: 8,
  abnormalCount: 2,
  missedCount: 1,
  completionRate: 0,
  inspectorCount: 0,
  abnormalCount: 0,
  missedCount: 0,
});
const filters = reactive({
  dateRange: [],
  inspector: "",
  status: "",
  status: null,
});
const dataList = ref([]);
const pagination = reactive({ current: 1, size: 10, total: 0 });
const tableLoading = ref(false);
const columns = [
  { label: "巡检时间", prop: "inspectionTime", align: "center" },
  { label: "巡检人员", prop: "inspector", align: "center" },
  { label: "巡检区域", prop: "area", align: "center" },
  { label: "巡检类型", prop: "type", align: "center" },
  { label: "状态", prop: "status", align: "center" },
  {
    label: "巡检类型",
    prop: "type",
    align: "center",
    formatData: (val) => {
      const typeMap = {
        'daily': '日常巡检',
        'equipment': '设备巡检',
        'fire': '消防巡检',
        'night': '夜间巡检',
        'safety': '安全巡检',
        'environment': '环境巡检'
      };
      return typeMap[val] || val;
    }
  },
  {
    label: "状态",
    prop: "status",
    align: "center",
    dataType: "tag",
    formatType: (val) => {
      if (val === 0) return 'success';
      if (val === 1) return 'danger';
      if (val === 2) return 'warning';
      return 'info';
    },
    formatData: (val) => {
      const statusMap = { 0: '正常', 1: '异常', 2: '漏检', 3: '未执行' };
      return statusMap[val] || val;
    }
  },
  { label: "异常情况", prop: "abnormalDesc", align: "center" },
];
const trendChartRef = ref(null);
const typeChartRef = ref(null);
let trendChart = null;
let typeChart = null;
onMounted(() => {
  initTrendChart();
  initTypeChart();
  getTodayStats();
  getData();
  nextTick(() => {
    initTrendChart();
    initTypeChart();
  });
});
const initTrendChart = () => {
  const chart = echarts.init(trendChartRef.value);
  const option = {
    xAxis: {
      type: "category",
      data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
    },
    yAxis: { type: "value" },
    series: [
      { name: "已完成", type: "line", data: [120, 132, 101, 134, 90, 230, 210], smooth: true },
      { name: "未完成", type: "line", data: [20, 12, 21, 14, 30, 20, 10], smooth: true },
    ],
    legend: { data: ["已完成", "未完成"], bottom: 0 },
    grid: { left: "3%", right: "4%", bottom: "10%", containLabel: true },
  };
  chart.setOption(option);
// èŽ·å–ä»Šæ—¥ç»Ÿè®¡
const getTodayStats = async () => {
  try {
    const res = await getTodayStatistics();
    if (res.code === 200) {
      Object.assign(todayStats, res.data);
    }
  } catch (error) {
    console.error('获取今日统计失败', error);
  }
};
const initTypeChart = () => {
  const chart = echarts.init(typeChartRef.value);
  const option = {
    series: [
      {
        type: "pie",
        radius: ["40%", "70%"],
        data: [
          { value: 335, name: "设备巡检" },
          { value: 310, name: "安全巡检" },
          { value: 234, name: "环境巡检" },
          { value: 135, name: "消防巡检" },
        ],
// åˆå§‹åŒ–趋势图表
const initTrendChart = async () => {
  if (!trendChartRef.value) return;
  trendChart = echarts.init(trendChartRef.value);
  try {
    const endDate = dayjs().format('YYYY-MM-DD');
    const startDate = dayjs().subtract(6, 'day').format('YYYY-MM-DD');
    const res = await getTrendStatistics({ startDate, endDate });
    let dates = [];
    let completedData = [];
    let abnormalData = [];
    let missedData = [];
    let unexecutedData = [];
    if (res.code === 200 && Array.isArray(res.data)) {
      // æŒ‰æ—¥æœŸæŽ’序
      const sortedData = res.data.sort((a, b) => a.statDate.localeCompare(b.statDate));
      dates = sortedData.map(item => dayjs(item.statDate).format('MM-DD'));
      completedData = sortedData.map(item => item.completedCount || 0);
      abnormalData = sortedData.map(item => item.abnormalCount || 0);
      missedData = sortedData.map(item => item.missedCount || 0);
      unexecutedData = sortedData.map(item => item.unexecutedCount || 0);
    } else {
      // ä½¿ç”¨é»˜è®¤æ•°æ®
      for (let i = 6; i >= 0; i--) {
        dates.push(dayjs().subtract(i, 'day').format('MM-DD'));
      }
      completedData = [2, 1, 2, 2, 1, 4, 3];
      abnormalData = [1, 0, 0, 1, 0, 1, 0];
      missedData = [0, 0, 1, 0, 0, 1, 0];
      unexecutedData = [0, 1, 0, 0, 1, 0, 1];
    }
    const option = {
      tooltip: { trigger: 'axis' },
      xAxis: {
        type: "category",
        data: dates,
      },
    ],
    legend: { orient: "vertical", left: "left" },
  };
  chart.setOption(option);
      yAxis: { type: "value" },
      series: [
        { name: "已完成", type: "line", data: completedData, smooth: true },
        { name: "异常", type: "line", data: abnormalData, smooth: true },
        { name: "漏检", type: "line", data: missedData, smooth: true },
        { name: "未执行", type: "line", data: unexecutedData, smooth: true },
      ],
      legend: { data: ["已完成", "异常", "漏检", "未执行"], bottom: 0 },
      grid: { left: "3%", right: "4%", bottom: "15%", containLabel: true },
    };
    trendChart.setOption(option);
  } catch (error) {
    console.error('获取趋势数据失败', error);
  }
};
const getData = () => {};
// ç±»åž‹æ˜ å°„
const typeMap = {
  'daily': '日常巡检',
  'equipment': '设备巡检',
  'fire': '消防巡检',
  'night': '夜间巡检',
  'safety': '安全巡检',
  'environment': '环境巡检'
};
// åˆå§‹åŒ–类型图表
const initTypeChart = async () => {
  if (!typeChartRef.value) return;
  typeChart = echarts.init(typeChartRef.value);
  try {
    const endDate = dayjs().format('YYYY-MM-DD');
    const startDate = dayjs().subtract(30, 'day').format('YYYY-MM-DD');
    const res = await getTypeStatistics({ startDate, endDate });
    let pieData = [];
    if (res.code === 200 && Array.isArray(res.data)) {
      pieData = res.data.map(item => ({
        value: item.count || 0,
        name: typeMap[item.type] || item.type
      }));
    } else {
      // ä½¿ç”¨é»˜è®¤æ•°æ®
      pieData = [
        { value: 8, name: "日常巡检" },
        { value: 4, name: "设备巡检" },
        { value: 3, name: "消防巡检" },
        { value: 3, name: "夜间巡检" },
      ];
    }
    const option = {
      tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
      series: [
        {
          type: "pie",
          radius: ["40%", "70%"],
          data: pieData,
          label: {
            formatter: '{b}\n{c}次 ({d}%)'
          }
        },
      ],
      legend: { orient: "vertical", left: "left" },
    };
    typeChart.setOption(option);
  } catch (error) {
    console.error('获取类型统计失败', error);
  }
};
// èŽ·å–å·¡æ£€è®°å½•åˆ—è¡¨
const getData = async () => {
  tableLoading.value = true;
  try {
    const params = {
      pageNum: pagination.current,
      pageSize: pagination.size,
      inspector: filters.inspector,
      status: filters.status,
    };
    if (filters.dateRange && filters.dateRange.length === 2) {
      params.startDate = filters.dateRange[0];
      params.endDate = filters.dateRange[1];
    }
    const res = await getInspectionRecordList(params);
    if (res.code === 200) {
      dataList.value = res.data.rows || res.data.records || [];
      pagination.total = res.data.total || 0;
    }
  } catch (error) {
    ElMessage.error('获取巡检记录失败');
  } finally {
    tableLoading.value = false;
  }
};
const resetFilters = () => {
  filters.dateRange = [];
  filters.inspector = "";
  filters.status = "";
  filters.status = null;
  pagination.current = 1;
  getData();
};
const changePage = ({ page, limit }) => {
  pagination.current = page;
  pagination.size = limit;
  getData();
};
// åŒæ­¥å·¡æ£€æ•°æ®
const handleSyncData = async () => {
  try {
    const res = await syncInspectionData();
    if (res.code === 200) {
      ElMessage.success(`同步成功,新增 ${res.data?.count || 0} æ¡è®°å½•`);
      // åˆ·æ–°åˆ—表和统计
      getData();
      getTodayStats();
      initTrendChart();
      initTypeChart();
    } else {
      ElMessage.warning(res.msg || '同步失败');
    }
  } catch (error) {
    ElMessage.error(error.response?.data?.msg || '同步失败');
  }
};
// çª—口大小改变时重新渲染图表
window.addEventListener('resize', () => {
  trendChart && trendChart.resize();
  typeChart && typeChart.resize();
});
</script>
<style lang="scss" scoped>
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、Word、视频等格式</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>