专业添加导入模板下载和数据上传
This commit is contained in:
@@ -45,12 +45,25 @@ export function delMajor(zydh) {
|
||||
})
|
||||
}
|
||||
|
||||
// 下载人才培养方案课程数据文件模板
|
||||
// 下载专业导入模板(xlsx,列头与后端导入解析一致)
|
||||
export function downloadMajorTemplate() {
|
||||
return request({
|
||||
url: '/download/training-program',
|
||||
url: '/discipline/major/template',
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 30000
|
||||
})
|
||||
}
|
||||
|
||||
// 导入专业(Excel,form-data 字段名 file)
|
||||
export function importMajor(file) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request({
|
||||
url: '/discipline/major/import',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
timeout: 120000
|
||||
})
|
||||
}
|
||||
@@ -68,6 +68,7 @@
|
||||
<span class="table-title">专业列表:</span>
|
||||
<div class="table-actions">
|
||||
<el-button type="primary" icon="el-icon-download" @click="handleDownloadTemplate">下载模板</el-button>
|
||||
<el-button type="primary" icon="el-icon-upload2" @click="handleOpenImport">导入专业</el-button>
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增专业</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -384,17 +385,69 @@
|
||||
<el-button @click="viewDialogVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 5. 导入专业对话框 ==================== -->
|
||||
<el-dialog
|
||||
title="导入专业"
|
||||
:visible.sync="importDialogVisible"
|
||||
width="720px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-alert type="info" :closable="false" show-icon class="import-tip">
|
||||
<template slot="title">导入说明</template>
|
||||
<div class="import-tip-body">
|
||||
<p>1. 请先下载专业导入模板,按表头逐列填写;<b>专业名称、专业代码、学年制、学期数、培训层次、培训类型</b>为必填。</p>
|
||||
<p>2. 专业代号已存在(或未填代号但「专业名称 + 专业方向 + 培训类型」已存在)的记录会被跳过,不会覆盖库中已有数据。</p>
|
||||
<p>3. 单行校验不通过只跳过该行并给出原因,其余合格行正常导入;未填写的可空字段使用系统缺省值。</p>
|
||||
</div>
|
||||
</el-alert>
|
||||
|
||||
<div class="import-toolbar">
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">下载导入模板</el-button>
|
||||
<el-button icon="el-icon-folder-opened" @click="handleChooseImportFile">选择文件</el-button>
|
||||
<span class="import-file-name" :class="{ 'has-file': importFile }">{{ importFileName }}</span>
|
||||
<input ref="importFileInput" type="file" accept=".xls,.xlsx" style="display: none" @change="handleImportFileChange" />
|
||||
</div>
|
||||
|
||||
<div v-if="importResult" class="import-result">
|
||||
<div class="import-summary">
|
||||
共解析 <b>{{ importResult.totalCount || 0 }}</b> 行:新增
|
||||
<b class="text-success">{{ importResult.insertCount || 0 }}</b> 条,跳过
|
||||
<b class="text-warning">{{ importResult.skipCount || 0 }}</b> 条,失败
|
||||
<b class="text-danger">{{ importResult.failCount || 0 }}</b> 条
|
||||
</div>
|
||||
<div v-if="importResult.skipList && importResult.skipList.length" class="import-detail">
|
||||
<div class="import-detail-title">跳过明细:</div>
|
||||
<ul>
|
||||
<li v-for="(item, idx) in importResult.skipList" :key="'skip' + idx">{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="importResult.failList && importResult.failList.length" class="import-detail">
|
||||
<div class="import-detail-title text-danger">失败明细:</div>
|
||||
<ul>
|
||||
<li v-for="(item, idx) in importResult.failList" :key="'fail' + idx">{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="importDialogVisible = false">关 闭</el-button>
|
||||
<el-button type="primary" :loading="importing" :disabled="!importFile" @click="handleImportSubmit">开始导入</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveAs } from 'file-saver'
|
||||
import {
|
||||
listMajor,
|
||||
getMajor,
|
||||
addMajor,
|
||||
updateMajor,
|
||||
delMajor,
|
||||
downloadMajorTemplate
|
||||
downloadMajorTemplate,
|
||||
importMajor
|
||||
} from "@/api/subjectMajor/major"
|
||||
import { listDiscipline } from '@/api/subjectMajor/discipline'
|
||||
import { getDicts } from '@/api/system/dict/data'
|
||||
@@ -465,7 +518,19 @@ export default {
|
||||
// ==================== 详情 ====================
|
||||
viewDialogVisible: false,
|
||||
viewLoading: false,
|
||||
viewForm: {}
|
||||
viewForm: {},
|
||||
|
||||
// ==================== 导入 ====================
|
||||
importDialogVisible: false,
|
||||
importFile: null,
|
||||
importing: false,
|
||||
importResult: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** 导入对话框中的文件名展示 */
|
||||
importFileName() {
|
||||
return (this.importFile && this.importFile.name) || '未选择任何文件'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -766,16 +831,66 @@ export default {
|
||||
// ==================== 下载模板 ====================
|
||||
handleDownloadTemplate() {
|
||||
downloadMajorTemplate().then(res => {
|
||||
const blob = new Blob([res], { type: 'application/vnd.ms-excel;charset=utf-8' })
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = '人才培养方案课程数据文件模板.xls'
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
const blob = new Blob([res], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
saveAs(blob, '专业导入模板.xlsx')
|
||||
this.$message.success('模板下载成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 导入 ====================
|
||||
handleOpenImport() {
|
||||
this.importDialogVisible = true
|
||||
this.importFile = null
|
||||
this.importResult = null
|
||||
if (this.$refs.importFileInput) {
|
||||
this.$refs.importFileInput.value = ''
|
||||
}
|
||||
},
|
||||
|
||||
handleChooseImportFile() {
|
||||
this.$refs.importFileInput && this.$refs.importFileInput.click()
|
||||
},
|
||||
|
||||
handleImportFileChange(e) {
|
||||
const input = e.target
|
||||
this.importResult = null
|
||||
const file = input.files && input.files.length > 0 ? input.files[0] : null
|
||||
// 前端先拦非 Excel 文件,避免提交后只拿到「系统未知错误」级别的提示
|
||||
if (file && !/\.xlsx?$/i.test(file.name)) {
|
||||
this.$message.warning('仅支持 .xls / .xlsx 格式的文件,请重新选择')
|
||||
this.importFile = null
|
||||
input.value = ''
|
||||
return
|
||||
}
|
||||
this.importFile = file
|
||||
},
|
||||
|
||||
handleImportSubmit() {
|
||||
if (!this.importFile) {
|
||||
this.$message.warning('请先选择要导入的文件')
|
||||
return
|
||||
}
|
||||
this.importing = true
|
||||
importMajor(this.importFile).then(response => {
|
||||
const result = response.data || {}
|
||||
this.importResult = result
|
||||
const inserted = result.insertCount || 0
|
||||
if (result.failCount) {
|
||||
this.$message.warning(`导入完成:新增 ${inserted} 条,跳过 ${result.skipCount || 0} 条,失败 ${result.failCount} 条`)
|
||||
} else if (result.insertCount) {
|
||||
this.$message.success(`导入成功:新增 ${inserted} 条,跳过 ${result.skipCount || 0} 条`)
|
||||
} else {
|
||||
this.$message.warning(`未新增数据:跳过 ${result.skipCount || 0} 条`)
|
||||
}
|
||||
this.fetchList()
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.importing = false
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
handleView(row) {
|
||||
this.viewDialogVisible = true
|
||||
@@ -859,6 +974,78 @@ export default {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.import-tip {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.import-tip-body {
|
||||
line-height: 20px;
|
||||
|
||||
p {
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.import-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.import-file-name {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
|
||||
&.has-file {
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.import-result {
|
||||
margin-top: 16px;
|
||||
padding: 12px 14px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
|
||||
.import-summary {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.import-detail {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
|
||||
.import-detail-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 6px 0 0;
|
||||
padding-left: 20px;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
|
||||
li {
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ty-tag {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
Reference in New Issue
Block a user