Files
education/frontend/node_modules/.cache/vue-loader/3504c1f90031fc06bbaed8b123cbeddd.json
T

1 line
25 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"remainingRequest":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\vue-loader\\lib\\index.js??vue-loader-options!C:\\Users\\Admin\\Desktop\\education\\frontend\\src\\views\\classHour\\jointTraining\\index.vue?vue&type=style&index=0&id=8a0ed46c&scoped=true&lang=scss","dependencies":[{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\src\\views\\classHour\\jointTraining\\index.vue","mtime":1787640892006},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\css-loader\\dist\\cjs.js","mtime":1787309792478},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\vue-loader\\lib\\loaders\\stylePostLoader.js","mtime":1787309794547},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\postcss-loader\\src\\index.js","mtime":1787309793084},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\sass-loader\\dist\\cjs.js","mtime":1787309791955},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\cache-loader\\dist\\cjs.js","mtime":1787309791955},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\vue-loader\\lib\\index.js","mtime":1787309793790}],"contextDependencies":[],"result":[{"type":"Buffer","data":"base64:CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg0KLmFwcC1jb250YWluZXIgew0KICBwYWRkaW5nOiAyMHB4Ow0KfQ0KDQouam9pbnQtdHJhaW5pbmctcGFnZSB7DQogIC5saXN0LXRpdGxlIHsNCiAgICBmb250LXNpemU6IDE2cHg7DQogICAgZm9udC13ZWlnaHQ6IDYwMDsNCiAgICBjb2xvcjogIzMwMzEzMzsNCiAgICBtYXJnaW4tYm90dG9tOiAxMnB4Ow0KICB9DQoNCiAgLy8gPT09PT09PT09PT09PT09PT09PT0gMS4g5p+l6K+i5p2h5Lu25Yy65Z+fID09PT09PT09PT09PT09PT09PT09DQogIC5zZWFyY2gtY2FyZCB7DQogICAgbWFyZ2luLWJvdHRvbTogMTZweDsNCg0KICAgIC5zZWFyY2gtZm9ybSB7DQogICAgICAudy1mdWxsIHsNCiAgICAgICAgd2lkdGg6IDEwMCU7DQogICAgICB9DQoNCiAgICAgIC5zZWFyY2gtYWN0aW9ucyB7DQogICAgICAgIGRpc3BsYXk6IGZsZXg7DQogICAgICAgIGp1c3RpZnktY29udGVudDogZmxleC1lbmQ7DQogICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7DQogICAgICAgIGhlaWdodDogNDBweDsNCiAgICAgIH0NCiAgICB9DQogIH0NCg0KICAvLyA9PT09PT09PT09PT09PT09PT09PSAyLiDmk43kvZzljLrln58gPT09PT09PT09PT09PT09PT09PT0NCiAgLnVwbG9hZC1jYXJkIHsNCiAgICBtYXJnaW4tYm90dG9tOiAxNnB4Ow0KDQogICAgLnVwbG9hZC1yb3cgew0KICAgICAgZGlzcGxheTogZmxleDsNCiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7DQogICAgICBmbGV4LXdyYXA6IHdyYXA7DQogICAgICBnYXA6IDEycHg7DQoNCiAgICAgIC51cGxvYWQtbGVmdCB7DQogICAgICAgIGRpc3BsYXk6IGZsZXg7DQogICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7DQogICAgICAgIGdhcDogMTJweDsNCiAgICAgICAgZmxleC13cmFwOiB3cmFwOw0KDQogICAgICAgIC5maWxlLW5hbWUgew0KICAgICAgICAgIGZvbnQtc2l6ZTogMTJweDsNCiAgICAgICAgICBjb2xvcjogIzkwOTM5OTsNCg0KICAgICAgICAgICYuaGFzLWZpbGUgew0KICAgICAgICAgICAgY29sb3I6ICMzMDMxMzM7DQogICAgICAgICAgfQ0KICAgICAgICB9DQogICAgICB9DQogICAgfQ0KICB9DQoNCiAgLy8gPT09PT09PT09PT09PT09PT09PT0gMy4g5pWw5o2u6KGo5qC8ID09PT09PT09PT09PT09PT09PT09DQogIC50YWJsZS1jYXJkIHsNCiAgICBtYXJnaW4tYm90dG9tOiAxNnB4Ow0KDQogICAgLnRhYmxlLXRpdGxlIHsNCiAgICAgIGZvbnQtc2l6ZTogMTRweDsNCiAgICAgIGZvbnQtd2VpZ2h0OiA2MDA7DQogICAgICBjb2xvcjogIzMwMzEzMzsNCiAgICAgIG1hcmdpbi1ib3R0b206IDEycHg7DQogICAgfQ0KDQogICAgLnBhZ2luYXRpb24gew0KICAgICAgZGlzcGxheTogZmxleDsNCiAgICAgIGp1c3RpZnktY29udGVudDogZmxleC1lbmQ7DQogICAgICBtYXJnaW4tdG9wOiAxMnB4Ow0KICAgIH0NCg0KICAgIC50ZXh0LWRhbmdlciB7DQogICAgICBjb2xvcjogI2Y1NmM2YzsNCiAgICB9DQogIH0NCg0KICAvLyA9PT09PT09PT09PT09PT09PT09PSA0LiDmlrDlop4v5L+u5pS56KGo5Y2VID09PT09PT09PT09PT09PT09PT09DQogIC5hZGQtZm9ybSB7DQogICAgbWF4LWhlaWdodDogNjJ2aDsNCiAgICBvdmVyZmxvdy15OiBhdXRvOw0KICAgIHBhZGRpbmctcmlnaHQ6IDRweDsNCiAgfQ0KfQ0K"},{"version":3,"sources":["index.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2bA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA","file":"index.vue","sourceRoot":"src/views/classHour/jointTraining","sourcesContent":["<template>\r\n <div class=\"app-container joint-training-page\">\r\n <div class=\"list-title\">联教联训管理</div>\r\n\r\n <!-- ==================== 1. 查询条件区域 ==================== -->\r\n <el-card shadow=\"never\" class=\"search-card\">\r\n <el-form :model=\"searchForm\" label-width=\"40px\" class=\"search-form\">\r\n <el-row :gutter=\"6\">\r\n <el-col :span=\"4\">\r\n <el-form-item label=\"名称\">\r\n <el-input v-model=\"searchForm.mc\" placeholder=\"请输入名称\" clearable @keyup.enter.native=\"handleQuery\" />\r\n </el-form-item>\r\n </el-col>\r\n <el-col :span=\"2\">\r\n <div class=\"search-actions\">\r\n <el-button type=\"primary\" @click=\"handleQuery\">查询</el-button>\r\n </div>\r\n </el-col>\r\n </el-row>\r\n </el-form>\r\n </el-card>\r\n\r\n <!-- ==================== 2. 操作区域 ==================== -->\r\n <el-card shadow=\"never\" class=\"upload-card\">\r\n <div class=\"upload-row\">\r\n <div class=\"upload-left\">\r\n <el-button type=\"primary\" @click=\"handleAdd\">新增联教联训</el-button>\r\n <el-button icon=\"el-icon-download\" @click=\"handleExport\">导出 Excel</el-button>\r\n <el-button icon=\"el-icon-download\" @click=\"handleDownloadTemplate\">【联教联训数据文件模板】下载</el-button>\r\n <el-button icon=\"el-icon-folder-opened\" @click=\"handleChooseFile\">选择文件</el-button>\r\n <span class=\"file-name\" :class=\"{ 'has-file': selectedFile }\">{{ fileName }}</span>\r\n <input ref=\"fileInputRef\" type=\"file\" accept=\".xls,.xlsx\" style=\"display: none\" @change=\"handleFileChange\" />\r\n <el-button type=\"primary\" icon=\"el-icon-upload2\" :loading=\"uploading\" @click=\"handleUpload\">上传数据</el-button>\r\n </div>\r\n </div>\r\n </el-card>\r\n\r\n <!-- ==================== 3. 数据表格 ==================== -->\r\n <el-card shadow=\"never\" class=\"table-card\">\r\n <div class=\"table-title\">联教联训记录列表</div>\r\n <el-table v-loading=\"tableLoading\" :data=\"tableData\" border stripe style=\"width: 100%\">\r\n <el-table-column type=\"index\" label=\"序号\" width=\"55\" align=\"center\" />\r\n <el-table-column prop=\"bh\" label=\"编号\" width=\"150\" align=\"center\" show-overflow-tooltip />\r\n <el-table-column prop=\"mc\" label=\"名称\" min-width=\"140\" show-overflow-tooltip />\r\n <el-table-column prop=\"xz\" label=\"性质\" width=\"80\" align=\"center\" show-overflow-tooltip />\r\n <el-table-column prop=\"yj\" label=\"依据\" width=\"110\" align=\"center\" show-overflow-tooltip />\r\n <el-table-column prop=\"ksrq\" label=\"开始日期\" width=\"110\" align=\"center\" :formatter=\"fmtDateTime\" />\r\n <el-table-column prop=\"jsrq\" label=\"结束日期\" width=\"110\" align=\"center\" :formatter=\"fmtDateTime\" />\r\n <el-table-column prop=\"ts\" label=\"天数\" width=\"70\" align=\"center\" />\r\n <el-table-column prop=\"jbksl\" label=\"基本课时量\" width=\"90\" align=\"center\" />\r\n <el-table-column prop=\"kslx\" label=\"课时类型\" width=\"80\" align=\"center\" show-overflow-tooltip />\r\n <el-table-column prop=\"xmlx\" label=\"项目类型\" width=\"80\" align=\"center\" show-overflow-tooltip />\r\n <el-table-column prop=\"sm\" label=\"说明\" min-width=\"120\" show-overflow-tooltip />\r\n <el-table-column prop=\"bz\" label=\"备注\" min-width=\"120\" show-overflow-tooltip />\r\n <el-table-column label=\"操作\" width=\"160\" align=\"center\" fixed=\"right\">\r\n <template slot-scope=\"{ row }\">\r\n <el-button type=\"text\" size=\"small\" @click=\"handleDetail(row)\">详情</el-button>\r\n <el-button type=\"text\" size=\"small\" @click=\"handleEdit(row)\">修改</el-button>\r\n <el-button type=\"text\" size=\"small\" class=\"text-danger\" @click=\"handleDelete(row)\">删除</el-button>\r\n </template>\r\n </el-table-column>\r\n </el-table>\r\n\r\n <el-pagination\r\n :current-page=\"pageNum\"\r\n :page-size=\"pageSize\"\r\n :total=\"total\"\r\n :page-sizes=\"[10, 20, 50, 100]\"\r\n layout=\"total, sizes, prev, pager, next, jumper\"\r\n class=\"pagination\"\r\n @current-change=\"handlePageChange\"\r\n @size-change=\"handleSizeChange\"\r\n />\r\n </el-card>\r\n\r\n <!-- ==================== 4. 新增/修改对话框 ==================== -->\r\n <el-dialog\r\n :visible=\"dialogVisible\"\r\n :title=\"dialogTitle\"\r\n width=\"720px\"\r\n :close-on-click-modal=\"false\"\r\n @update:visible=\"val => dialogVisible = val\"\r\n >\r\n <el-form ref=\"formRef\" :model=\"form\" :rules=\"rules\" label-width=\"110px\" class=\"add-form\">\r\n <el-row :gutter=\"16\">\r\n <el-col :span=\"12\">\r\n <el-form-item label=\"编号\">\r\n <el-input v-model=\"form.bh\" placeholder=\"留空则自动生成\" clearable />\r\n </el-form-item>\r\n </el-col>\r\n <el-col :span=\"12\">\r\n <el-form-item label=\"名称\" prop=\"mc\">\r\n <el-input v-model=\"form.mc\" placeholder=\"请输入名称\" clearable />\r\n </el-form-item>\r\n </el-col>\r\n </el-row>\r\n <el-row :gutter=\"16\">\r\n <el-col :span=\"12\">\r\n <el-form-item label=\"性质\">\r\n <el-input v-model=\"form.xz\" placeholder=\"请输入性质\" clearable />\r\n </el-form-item>\r\n </el-col>\r\n <el-col :span=\"12\">\r\n <el-form-item label=\"依据\">\r\n <el-input v-model=\"form.yj\" placeholder=\"请输入依据\" clearable />\r\n </el-form-item>\r\n </el-col>\r\n </el-row>\r\n <el-row :gutter=\"16\">\r\n <el-col :span=\"12\">\r\n <el-form-item label=\"开始日期\">\r\n <el-date-picker v-model=\"form.ksrq\" type=\"datetime\" value-format=\"yyyy-MM-dd HH:mm:ss\" placeholder=\"请选择开始日期\" clearable class=\"w-full\" />\r\n </el-form-item>\r\n </el-col>\r\n <el-col :span=\"12\">\r\n <el-form-item label=\"结束日期\">\r\n <el-date-picker v-model=\"form.jsrq\" type=\"datetime\" value-format=\"yyyy-MM-dd HH:mm:ss\" placeholder=\"请选择结束日期\" clearable class=\"w-full\" />\r\n </el-form-item>\r\n </el-col>\r\n </el-row>\r\n <el-row :gutter=\"16\">\r\n <el-col :span=\"12\">\r\n <el-form-item label=\"天数\">\r\n <el-input-number v-model=\"form.ts\" :min=\"0\" :precision=\"1\" :step=\"1\" class=\"w-full\" />\r\n </el-form-item>\r\n </el-col>\r\n <el-col :span=\"12\">\r\n <el-form-item label=\"基本课时量\">\r\n <el-input-number v-model=\"form.jbksl\" :min=\"0\" :precision=\"1\" :step=\"0.5\" class=\"w-full\" />\r\n </el-form-item>\r\n </el-col>\r\n </el-row>\r\n <el-row :gutter=\"16\">\r\n <el-col :span=\"12\">\r\n <el-form-item label=\"课时类型\">\r\n <el-input v-model=\"form.kslx\" placeholder=\"请输入课时类型\" clearable />\r\n </el-form-item>\r\n </el-col>\r\n <el-col :span=\"12\">\r\n <el-form-item label=\"项目类型\">\r\n <el-input v-model=\"form.xmlx\" placeholder=\"请输入项目类型\" clearable />\r\n </el-form-item>\r\n </el-col>\r\n </el-row>\r\n <el-form-item label=\"说明\">\r\n <el-input v-model=\"form.sm\" type=\"textarea\" :rows=\"2\" placeholder=\"请输入说明\" />\r\n </el-form-item>\r\n <el-form-item label=\"备注\">\r\n <el-input v-model=\"form.bz\" type=\"textarea\" :rows=\"2\" placeholder=\"请输入备注\" />\r\n </el-form-item>\r\n </el-form>\r\n <div slot=\"footer\">\r\n <el-button @click=\"dialogVisible = false\">取消</el-button>\r\n <el-button type=\"primary\" :loading=\"saving\" @click=\"handleSubmit\">确定</el-button>\r\n </div>\r\n </el-dialog>\r\n\r\n <!-- ==================== 5. 详情对话框 ==================== -->\r\n <el-dialog :visible=\"detailVisible\" title=\"联教联训详情\" width=\"640px\" @update:visible=\"val => detailVisible = val\">\r\n <el-descriptions :column=\"2\" border>\r\n <el-descriptions-item label=\"编号\">{{ detailData.bh }}</el-descriptions-item>\r\n <el-descriptions-item label=\"名称\">{{ detailData.mc }}</el-descriptions-item>\r\n <el-descriptions-item label=\"性质\">{{ fmtEmpty(detailData.xz) }}</el-descriptions-item>\r\n <el-descriptions-item label=\"依据\">{{ fmtEmpty(detailData.yj) }}</el-descriptions-item>\r\n <el-descriptions-item label=\"开始日期\">{{ fmtDateTime(null, null, detailData.ksrq) }}</el-descriptions-item>\r\n <el-descriptions-item label=\"结束日期\">{{ fmtDateTime(null, null, detailData.jsrq) }}</el-descriptions-item>\r\n <el-descriptions-item label=\"天数\">{{ fmtValue(detailData.ts) }}</el-descriptions-item>\r\n <el-descriptions-item label=\"基本课时量\">{{ fmtValue(detailData.jbksl) }}</el-descriptions-item>\r\n <el-descriptions-item label=\"课时类型\">{{ fmtEmpty(detailData.kslx) }}</el-descriptions-item>\r\n <el-descriptions-item label=\"项目类型\">{{ fmtEmpty(detailData.xmlx) }}</el-descriptions-item>\r\n <el-descriptions-item label=\"说明\" :span=\"2\">{{ fmtEmpty(detailData.sm) }}</el-descriptions-item>\r\n <el-descriptions-item label=\"备注\" :span=\"2\">{{ fmtEmpty(detailData.bz) }}</el-descriptions-item>\r\n </el-descriptions>\r\n <div slot=\"footer\">\r\n <el-button @click=\"detailVisible = false\">关闭</el-button>\r\n </div>\r\n </el-dialog>\r\n </div>\r\n</template>\r\n\r\n<script>\r\nimport { saveAs } from 'file-saver'\r\nimport {\r\n listJointTraining,\r\n addJointTraining,\r\n updateJointTraining,\r\n deleteJointTraining,\r\n getJointTraining,\r\n importJointTraining,\r\n exportJointTraining\r\n} from '@/api/classHour/jointTraining'\r\n\r\nexport default {\r\n name: 'JointTrainingIndex',\r\n data() {\r\n return {\r\n // ==================== 1. 查询条件 ====================\r\n searchForm: {\r\n mc: ''\r\n },\r\n\r\n // ==================== 2. 文件操作 ====================\r\n selectedFile: null,\r\n uploading: false,\r\n\r\n // ==================== 3. 表格数据 ====================\r\n tableLoading: false,\r\n tableData: [],\r\n pageNum: 1,\r\n pageSize: 10,\r\n total: 0,\r\n\r\n // ==================== 4. 新增/修改表单 ====================\r\n dialogVisible: false,\r\n dialogTitle: '新增联教联训',\r\n saving: false,\r\n form: this.createEmptyForm(),\r\n rules: {\r\n mc: [{ required: true, message: '请输入名称', trigger: 'blur' }]\r\n },\r\n\r\n // ==================== 5. 详情 ====================\r\n detailVisible: false,\r\n detailData: {}\r\n }\r\n },\r\n computed: {\r\n fileName() {\r\n return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'\r\n }\r\n },\r\n mounted() {\r\n this.fetchList()\r\n },\r\n methods: {\r\n createEmptyForm() {\r\n return {\r\n bh: '',\r\n mc: '',\r\n xz: '',\r\n yj: '',\r\n ksrq: '',\r\n jsrq: '',\r\n ts: null,\r\n jbksl: null,\r\n kslx: '',\r\n xmlx: '',\r\n sm: '',\r\n bz: ''\r\n }\r\n },\r\n\r\n /** 把后端 LocalDateTime(含 T/毫秒)规范为表单可回显的格式 */\r\n normalizeDateTime(val) {\r\n if (!val) return ''\r\n return String(val).replace('T', ' ').replace(/\\.\\d+$/, '').slice(0, 19)\r\n },\r\n\r\n // ==================== 查询 ====================\r\n fetchList() {\r\n this.tableLoading = true\r\n const params = { pageNum: this.pageNum, pageSize: this.pageSize }\r\n if (this.searchForm.mc && this.searchForm.mc.trim()) params.mc = this.searchForm.mc.trim()\r\n listJointTraining(params).then(res => {\r\n const data = (res && res.data) || {}\r\n this.tableData = data.records || []\r\n this.total = data.total || 0\r\n this.tableLoading = false\r\n }).catch(() => {\r\n this.tableData = []\r\n this.total = 0\r\n this.tableLoading = false\r\n })\r\n },\r\n\r\n handleQuery() {\r\n this.pageNum = 1\r\n this.fetchList()\r\n },\r\n\r\n handlePageChange(page) {\r\n this.pageNum = page\r\n this.fetchList()\r\n },\r\n\r\n handleSizeChange(size) {\r\n this.pageSize = size\r\n this.pageNum = 1\r\n this.fetchList()\r\n },\r\n\r\n /** 日期时间显示(去掉 T、截断到分钟) */\r\n fmtDateTime(row, column, cellValue) {\r\n if (cellValue === null || cellValue === undefined || cellValue === '') return '-'\r\n return String(cellValue).replace('T', ' ').slice(0, 16)\r\n },\r\n\r\n /** 空值显示为 '-' */\r\n fmtEmpty(val) {\r\n return val === null || val === undefined || val === '' ? '-' : val\r\n },\r\n\r\n /** 数值空值显示为 '-'(保留 0) */\r\n fmtValue(val) {\r\n return val === null || val === undefined ? '-' : val\r\n },\r\n\r\n // ==================== 新增 / 修改 ====================\r\n handleAdd() {\r\n this.form = this.createEmptyForm()\r\n this.dialogTitle = '新增联教联训'\r\n this.dialogVisible = true\r\n },\r\n\r\n handleEdit(row) {\r\n getJointTraining(row.bh).then(res => {\r\n const d = (res && res.data) || {}\r\n this.form = {\r\n bh: d.bh || '',\r\n mc: d.mc || '',\r\n xz: d.xz || '',\r\n yj: d.yj || '',\r\n ksrq: this.normalizeDateTime(d.ksrq),\r\n jsrq: this.normalizeDateTime(d.jsrq),\r\n ts: d.ts === null || d.ts === undefined ? null : d.ts,\r\n jbksl: d.jbksl === null || d.jbksl === undefined ? null : d.jbksl,\r\n kslx: d.kslx || '',\r\n xmlx: d.xmlx || '',\r\n sm: d.sm || '',\r\n bz: d.bz || ''\r\n }\r\n this.dialogTitle = '修改联教联训'\r\n this.dialogVisible = true\r\n }).catch(() => {})\r\n },\r\n\r\n handleSubmit() {\r\n this.$refs.formRef.validate(valid => {\r\n if (!valid) {\r\n this.$message.warning('请完善必填项后再提交')\r\n return\r\n }\r\n const payload = this.buildPayload()\r\n this.saving = true\r\n const req = payload.bh ? updateJointTraining(payload) : addJointTraining(payload)\r\n req.then(() => {\r\n this.$message.success(this.dialogTitle === '修改联教联训' ? '修改成功' : '新增成功')\r\n this.dialogVisible = false\r\n this.fetchList()\r\n }).catch(() => {}).finally(() => {\r\n this.saving = false\r\n })\r\n })\r\n },\r\n\r\n /** 构建请求体,仅含实体非空字段(创建/修改时间为后端自动维护,不传) */\r\n buildPayload() {\r\n const f = this.form\r\n const payload = {}\r\n if (f.bh && f.bh.trim()) payload.bh = f.bh.trim()\r\n if (f.mc && f.mc.trim()) payload.mc = f.mc.trim()\r\n if (f.xz && f.xz.trim()) payload.xz = f.xz.trim()\r\n if (f.yj && f.yj.trim()) payload.yj = f.yj.trim()\r\n if (f.ksrq) payload.ksrq = f.ksrq\r\n if (f.jsrq) payload.jsrq = f.jsrq\r\n if (f.ts !== null && f.ts !== undefined) payload.ts = f.ts\r\n if (f.jbksl !== null && f.jbksl !== undefined) payload.jbksl = f.jbksl\r\n if (f.kslx && f.kslx.trim()) payload.kslx = f.kslx.trim()\r\n if (f.xmlx && f.xmlx.trim()) payload.xmlx = f.xmlx.trim()\r\n if (f.sm && f.sm.trim()) payload.sm = f.sm.trim()\r\n if (f.bz && f.bz.trim()) payload.bz = f.bz.trim()\r\n return payload\r\n },\r\n\r\n // ==================== 删除 ====================\r\n handleDelete(row) {\r\n this.$confirm(`确定要删除「${row.mc || row.bh || '该记录'}」吗?`, '系统提示', {\r\n confirmButtonText: '确定',\r\n cancelButtonText: '取消',\r\n type: 'warning'\r\n }).then(() => deleteJointTraining(row.bh)).then(() => {\r\n this.$message.success('删除成功')\r\n this.fetchList()\r\n }).catch(() => {})\r\n },\r\n\r\n // ==================== 详情 ====================\r\n handleDetail(row) {\r\n getJointTraining(row.bh).then(res => {\r\n this.detailData = (res && res.data) || {}\r\n this.detailVisible = true\r\n }).catch(() => {})\r\n },\r\n\r\n // ==================== 导出 ====================\r\n handleExport() {\r\n exportJointTraining().then(blob => {\r\n saveAs(blob, '联教联训管理.xlsx')\r\n this.$message.success('导出成功')\r\n }).catch(() => {})\r\n },\r\n\r\n // ==================== 文件选择与导入 ====================\r\n handleChooseFile() {\r\n this.$refs.fileInputRef && this.$refs.fileInputRef.click()\r\n },\r\n\r\n handleFileChange(e) {\r\n const input = e.target\r\n this.selectedFile = (input.files && input.files[0]) || null\r\n },\r\n\r\n handleUpload() {\r\n if (!this.selectedFile) {\r\n this.$message.warning('请先选择文件')\r\n return\r\n }\r\n this.uploading = true\r\n importJointTraining(this.selectedFile).then(res => {\r\n const count = res && res.data\r\n if (count !== null && count !== undefined) {\r\n this.$message.success(`导入成功,共 ${count} 条记录`)\r\n } else {\r\n this.$message.success((res && res.msg) || '导入成功')\r\n }\r\n this.selectedFile = null\r\n this.$refs.fileInputRef && (this.$refs.fileInputRef.value = '')\r\n this.pageNum = 1\r\n this.fetchList()\r\n }).catch(() => {}).finally(() => {\r\n this.uploading = false\r\n })\r\n },\r\n\r\n handleDownloadTemplate() {\r\n // 后端暂未提供模板下载接口,仅作提示\r\n this.$message.info('后端暂未提供该接口')\r\n }\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.app-container {\r\n padding: 20px;\r\n}\r\n\r\n.joint-training-page {\r\n .list-title {\r\n font-size: 16px;\r\n font-weight: 600;\r\n color: #303133;\r\n margin-bottom: 12px;\r\n }\r\n\r\n // ==================== 1. 查询条件区域 ====================\r\n .search-card {\r\n margin-bottom: 16px;\r\n\r\n .search-form {\r\n .w-full {\r\n width: 100%;\r\n }\r\n\r\n .search-actions {\r\n display: flex;\r\n justify-content: flex-end;\r\n align-items: center;\r\n height: 40px;\r\n }\r\n }\r\n }\r\n\r\n // ==================== 2. 操作区域 ====================\r\n .upload-card {\r\n margin-bottom: 16px;\r\n\r\n .upload-row {\r\n display: flex;\r\n align-items: center;\r\n flex-wrap: wrap;\r\n gap: 12px;\r\n\r\n .upload-left {\r\n display: flex;\r\n align-items: center;\r\n gap: 12px;\r\n flex-wrap: wrap;\r\n\r\n .file-name {\r\n font-size: 12px;\r\n color: #909399;\r\n\r\n &.has-file {\r\n color: #303133;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n // ==================== 3. 数据表格 ====================\r\n .table-card {\r\n margin-bottom: 16px;\r\n\r\n .table-title {\r\n font-size: 14px;\r\n font-weight: 600;\r\n color: #303133;\r\n margin-bottom: 12px;\r\n }\r\n\r\n .pagination {\r\n display: flex;\r\n justify-content: flex-end;\r\n margin-top: 12px;\r\n }\r\n\r\n .text-danger {\r\n color: #f56c6c;\r\n }\r\n }\r\n\r\n // ==================== 4. 新增/修改表单 ====================\r\n .add-form {\r\n max-height: 62vh;\r\n overflow-y: auto;\r\n padding-right: 4px;\r\n }\r\n}\r\n</style>\r\n"]}]}