| | |
| | | <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="资料名称"> |
| | |
| | | </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> |
| | |
| | | </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> |
| | | |
| | |
| | | <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> |
| | |
| | | </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> |
| | | |
| | |
| | | <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: "培训管理", |
| | |
| | | |
| | | 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> |