Files
education/frontend/src/views/teachBusiness/build/index.vue
T
liweijie b30e39f91a 1、教学楼添加批量删除功能;
2、教学楼导入代号从excel获取,不要自己生成
2026-09-16 09:07:47 +08:00

540 lines
18 KiB
Vue
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.
<template>
<div class="app-container build-page">
<!-- 查询条件 -->
<el-card shadow="never" class="search-card">
<el-form :model="searchForm" label-width="100px" class="search-form" @submit.native.prevent>
<el-row :gutter="24">
<el-col :xs="24" :md="8">
<el-form-item label="教学楼代号">
<el-input v-model="searchForm.jxldh" placeholder="请输入教学楼代号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="教学楼名称">
<el-input v-model="searchForm.jxlmc" placeholder="请输入教学楼名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="地点">
<el-input v-model="searchForm.dd" placeholder="请输入地点" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<div class="search-actions">
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
</div>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 数据列表 -->
<el-card shadow="never" class="table-card">
<div class="list-header">
<div class="list-title">教学楼列表</div>
<div class="list-actions">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
<el-button type="danger" plain icon="el-icon-delete" :disabled="selectedRows.length === 0"
@click="handleBatchDelete">删除</el-button>
<el-button type="warning" plain icon="el-icon-edit" :disabled="selectedRows.length !== 1"
@click="handleModify">修改</el-button>
<el-button icon="el-icon-upload2" @click="openImportDialog">模板导入</el-button>
</div>
</div>
<el-table ref="buildTable" v-loading="loading" :data="tableData" border stripe highlight-current-row
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column prop="xh" label="序号" width="80" align="center" />
<el-table-column prop="jxldh" label="教学楼代号" width="160" align="center" show-overflow-tooltip />
<el-table-column prop="jxlmc" label="教学楼名称" min-width="180" show-overflow-tooltip header-align="center" />
<el-table-column prop="dd" label="地点" min-width="160" show-overflow-tooltip header-align="center" />
<el-table-column prop="bz" label="备注" min-width="200" show-overflow-tooltip header-align="center" />
</el-table>
<el-pagination class="pagination" background layout="total, sizes, prev, pager, next" :current-page="pageNum"
:page-size="pageSize" :total="total" :page-sizes="[10, 20, 50, 100]" @size-change="handleSizeChange"
@current-change="handlePageChange" />
</el-card>
<!-- 新增/修改弹窗 -->
<el-dialog :title="dialog.title" :visible.sync="dialog.visible" width="620px" append-to-body
:close-on-click-modal="false">
<el-form ref="buildForm" :model="dialog.form" :rules="rules" label-width="110px">
<el-form-item label="教学楼代号" prop="jxldh">
<el-input v-model="dialog.form.jxldh" placeholder="如 JXL09" maxlength="50" show-word-limit clearable />
</el-form-item>
<el-form-item label="教学楼名称" prop="jxlmc">
<el-input v-model="dialog.form.jxlmc" placeholder="请输入教学楼名称" maxlength="50" show-word-limit clearable />
</el-form-item>
<el-form-item label="地点">
<el-input v-model="dialog.form.dd" placeholder="请输入地点" maxlength="50" show-word-limit clearable />
</el-form-item>
<el-form-item label="序号">
<el-input v-model="dialog.form.xh" placeholder="留空则自动生成(按现有最大序号 +1)" maxlength="50" clearable />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="dialog.form.bz" type="textarea" :rows="3" placeholder="请输入备注" maxlength="100"
show-word-limit />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialog.visible = false">取 消</el-button>
<el-button type="primary" :loading="dialog.submitting" @click="submitDialog">确 定</el-button>
</div>
</el-dialog>
<!-- 模板导入弹窗 -->
<el-dialog title="教学楼模板导入" :visible.sync="importDialog.visible" width="660px" append-to-body
:close-on-click-modal="false">
<el-alert type="info" :closable="false" show-icon class="import-tip"
title="请先下载导入模板,按模板列填写后再上传。" />
<el-form label-width="110px" class="import-form">
<el-form-item label="导入模板">
<el-button icon="el-icon-download" :loading="importDialog.downloading" @click="handleDownloadTemplate">
下载导入模板
</el-button>
<span class="tip-text">模板 sheet 为「教学楼表」,教学楼标识号留空则自动生成,代号已存在的行将跳过</span>
</el-form-item>
<el-form-item label="数据文件">
<div class="file-input">
<input ref="importFileInput" type="file" accept=".xls,.xlsx" style="display: none"
@change="handleImportFileChange" />
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
<span class="file-name">{{ importDialog.fileName || '未选择任何文件' }}</span>
<el-button v-if="importDialog.file" type="text" class="danger-text-btn" @click="clearImportFile">
清除
</el-button>
</div>
</el-form-item>
<el-form-item label="导入结果" v-if="importDialog.result">
<div class="import-result">
共导入 <span class="num">{{ importDialog.result.insertCount }}</span> 条教学楼数据<template
v-if="importDialog.result.skipCount > 0">,跳过 <span
class="num warn">{{ importDialog.result.skipCount }}</span> 条(教学楼代号已存在)</template>。
<div v-if="importDialog.result.skipList && importDialog.result.skipList.length" class="skip-detail">
<div v-for="(item, idx) in importDialog.result.skipList" :key="idx">{{ item }}</div>
</div>
</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="importDialog.visible = false">关 闭</el-button>
<el-button type="primary" :loading="importDialog.importing" :disabled="!importDialog.file"
@click="handleImport">开始导入</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { saveAs } from 'file-saver'
import {
addBuild,
batchDeleteBuild,
updateBuild,
listBuild,
importBuild,
downloadBuildTemplate
} from '@/api/teachBusiness/build'
export default {
name: 'Build',
data() {
return {
loading: false,
searchForm: {
jxldh: '',
jxlmc: '',
dd: ''
},
tableData: [],
selectedRows: [],
total: 0,
pageNum: 1,
pageSize: 20,
dialog: {
visible: false,
title: '',
isEdit: false,
submitting: false,
form: this.createEmptyForm()
},
importDialog: {
visible: false,
file: null,
fileName: '',
importing: false,
downloading: false,
result: null
},
rules: {
jxldh: [
{ required: true, message: '请输入教学楼代号', trigger: 'blur' },
{ max: 50, message: '教学楼代号长度不能超过 50 个字符', trigger: 'blur' }
],
jxlmc: [
{ required: true, message: '请输入教学楼名称', trigger: 'blur' },
{ max: 50, message: '教学楼名称长度不能超过 50 个字符', trigger: 'blur' }
]
}
}
},
created() {
this.fetchList()
},
methods: {
/* ---------- 查询 ---------- */
buildQuery() {
const params = {}
Object.keys(this.searchForm).forEach(key => {
const value = this.searchForm[key]
if (value !== '' && value !== null && value !== undefined) {
params[key] = String(value).trim()
}
})
return params
},
fetchList() {
this.loading = true
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize,
...this.buildQuery()
}
listBuild(params).then(response => {
const data = (response && response.data) || {}
this.tableData = data.records || []
this.total = data.total || 0
this.loading = false
this.selectedRows = []
if (this.$refs.buildTable) this.$refs.buildTable.clearSelection()
}).catch(() => {
this.tableData = []
this.total = 0
this.loading = false
})
},
handleQuery() {
this.pageNum = 1
this.fetchList()
},
handleReset() {
this.searchForm = { jxldh: '', jxlmc: '', dd: '' }
this.pageNum = 1
this.fetchList()
},
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.fetchList()
},
handlePageChange(page) {
this.pageNum = page
this.fetchList()
},
handleSelectionChange(rows) {
this.selectedRows = rows || []
},
/* ---------- 新增 / 修改 ---------- */
createEmptyForm() {
return {
id: '',
jxldh: '',
jxlmc: '',
dd: '',
xh: '',
bz: ''
}
},
handleAdd() {
this.dialog.title = '新增教学楼'
this.dialog.isEdit = false
this.dialog.form = this.createEmptyForm()
this.dialog.visible = true
this.$nextTick(() => {
if (this.$refs.buildForm) this.$refs.buildForm.clearValidate()
})
},
handleModify() {
if (this.selectedRows.length !== 1) {
this.$message.warning('请选择一条教学楼数据进行修改')
return
}
const row = this.selectedRows[0]
this.dialog.title = '修改教学楼'
this.dialog.isEdit = true
this.dialog.form = {
id: row.id || '',
jxldh: row.jxldh || '',
jxlmc: row.jxlmc || '',
dd: row.dd || '',
xh: row.xh || '',
bz: row.bz || ''
}
this.dialog.visible = true
this.$nextTick(() => {
if (this.$refs.buildForm) this.$refs.buildForm.clearValidate()
})
},
buildPayload() {
const form = this.dialog.form
const payload = {
jxldh: (form.jxldh || '').trim(),
jxlmc: (form.jxlmc || '').trim()
}
if (this.dialog.isEdit) payload.id = form.id
// 可选字段留空时不提交,交由后端自动生成 / 保持原值
const optional = { dd: form.dd, xh: form.xh, bz: form.bz }
Object.keys(optional).forEach(key => {
const value = (optional[key] === null || optional[key] === undefined) ? '' : String(optional[key]).trim()
if (value !== '') payload[key] = value
})
return payload
},
submitDialog() {
if (!this.$refs.buildForm) return
this.$refs.buildForm.validate(valid => {
if (!valid) return
const payload = this.buildPayload()
const isEdit = this.dialog.isEdit
this.dialog.submitting = true
const request = isEdit ? updateBuild(payload) : addBuild(payload)
request.then(() => {
this.$message.success(isEdit ? '修改成功' : '新增成功')
this.dialog.submitting = false
this.dialog.visible = false
this.fetchList()
}).catch(() => {
this.dialog.submitting = false
})
})
},
/* ---------- 删除(批量删除,已绑定教学场地的由后端跳过) ---------- */
handleBatchDelete() {
const rows = this.selectedRows || []
if (rows.length === 0) {
this.$message.warning('请选择要删除的教学楼数据')
return
}
const names = rows.map(row => row.jxlmc || row.jxldh).join('、')
this.$confirm('确定删除选中的 ' + rows.length + ' 条教学楼数据吗?' + '(' + names + ')已绑定教学场地的教学楼不会被删除。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => this.doBatchDelete(rows)).catch(() => {})
},
doBatchDelete(rows) {
this.loading = true
batchDeleteBuild(rows.map(row => row.id)).then(response => {
const data = (response && response.data) || {}
const deleteCount = data.deleteCount || 0
const skipList = data.skipList || []
this.loading = false
if (skipList.length > 0) {
const detailHtml = skipList.map(item => '<div>' + item + '</div>').join('')
this.$alert(
'已删除 ' + deleteCount + ' 条,' + skipList.length + ' 条因已绑定教学场地未删除。<br/>' + detailHtml,
'删除结果',
{ dangerouslyUseHTMLString: true, confirmButtonText: '确定' }
)
} else {
this.$message.success('已删除 ' + deleteCount + ' 条教学楼数据')
}
this.fetchList()
}).catch(() => {
this.loading = false
this.fetchList()
})
},
/* ---------- 模板导入 ---------- */
openImportDialog() {
this.importDialog.file = null
this.importDialog.fileName = ''
this.importDialog.result = null
this.importDialog.visible = true
if (this.$refs.importFileInput) this.$refs.importFileInput.value = ''
},
handleDownloadTemplate() {
this.importDialog.downloading = true
downloadBuildTemplate().then(blob => {
saveAs(blob, '教学楼管理模板.xls')
this.$message.success('模板下载成功')
this.importDialog.downloading = false
}).catch(() => {
this.importDialog.downloading = false
})
},
handleChooseFile() {
if (this.$refs.importFileInput) this.$refs.importFileInput.click()
},
handleImportFileChange(e) {
const input = e.target
this.importDialog.result = null
if (!input.files || input.files.length === 0) {
this.importDialog.file = null
this.importDialog.fileName = ''
return
}
const file = input.files[0]
const name = (file.name || '').toLowerCase()
if (!name.endsWith('.xls') && !name.endsWith('.xlsx')) {
this.$message.warning('仅支持 .xls / .xlsx 格式的 Excel 文件')
this.importDialog.file = null
this.importDialog.fileName = ''
input.value = ''
return
}
this.importDialog.file = file
this.importDialog.fileName = file.name
},
clearImportFile() {
this.importDialog.file = null
this.importDialog.fileName = ''
this.importDialog.result = null
if (this.$refs.importFileInput) this.$refs.importFileInput.value = ''
},
handleImport() {
if (!this.importDialog.file) {
this.$message.warning('请先选择文件')
return
}
this.importDialog.importing = true
importBuild(this.importDialog.file).then(response => {
const data = (response && response.data) || {}
this.clearImportFile()
this.importDialog.importing = false
this.importDialog.result = {
insertCount: data.insertCount || 0,
skipCount: data.skipCount || 0,
skipList: data.skipList || []
}
const skipTip = data.skipCount > 0 ? ',跳过 ' + data.skipCount + ' 条已存在代号' : ''
this.$message.success('导入成功,共导入 ' + (data.insertCount || 0) + ' 条' + skipTip)
this.fetchList()
}).catch(() => {
this.importDialog.importing = false
})
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.build-page {
.search-card {
margin-bottom: 16px;
.search-form {
.search-actions {
padding-top: 4px;
}
}
}
.table-card {
.list-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.list-actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
}
.pagination {
margin-top: 16px;
text-align: right;
}
}
.import-tip {
margin-bottom: 16px;
}
.import-form {
.tip-text {
margin-left: 12px;
font-size: 13px;
color: #909399;
}
.file-input {
display: flex;
align-items: center;
gap: 12px;
.file-name {
font-size: 14px;
color: #606266;
}
}
.import-result {
font-size: 14px;
color: #303133;
.num {
font-weight: 600;
color: #67c23a;
padding: 0 4px;
&.warn {
color: #e6a23c;
}
}
.skip-detail {
margin-top: 8px;
font-size: 13px;
color: #909399;
max-height: 120px;
overflow-y: auto;
}
}
}
}
.danger-text-btn {
color: #f56c6c;
&:hover {
color: #f78989;
}
}
</style>