This commit is contained in:
lijing
2026-08-12 17:19:37 +08:00
commit e73d22744e
131 changed files with 66997 additions and 0 deletions
+679
View File
@@ -0,0 +1,679 @@
<template>
<div class="page-wrap">
<Tabs v-model="activeTab">
<TabPane label="校区管理" name="campus"></TabPane>
<TabPane label="系部管理" name="dept"></TabPane>
<TabPane label="专业管理" name="major"></TabPane>
</Tabs>
<!-- ====================== 校区管理【hover显示删除】 ====================== -->
<div v-if="activeTab === 'campus'" class="tab-panel">
<div class="btn-container">
<draggable
v-model="campuslist"
animation="150"
ghost-class="drag-ghost"
class="drag-box"
:options="{ group: 'people' }"
@start="drag = true"
@end="drag = false"
:move="getdata1"
@update="datadragEnd1"
>
<div
v-for="(item, index) in campuslist"
:key="index"
class="tag-wrap"
@dblclick="openEditModal('campus', index)"
>
<div class="tag-btn">{{ item.name }}</div>
<span class="close-btn" @click.stop="delCampus(index)">×</span>
</div>
</draggable>
<Button icon="ios-add" size="large" class="add-btn" @click="openAddModal('campus')">添加校区</Button>
</div>
<div class="tip-info">
<Icon type="ios-information-circle-outline"></Icon>
双击编辑,拖动排序
</div>
</div>
<!-- ====================== 系部管理【hover显示删除】 ====================== -->
<div v-if="activeTab === 'dept'" class="tab-panel">
<div class="btn-container">
<draggable
v-model="divisionlist"
animation="150"
ghost-class="drag-ghost"
class="drag-box"
:options="{ group: 'people' }"
@start="drag = true"
@end="drag = false"
:move="getdata2"
@update="datadragEnd2"
>
<div
v-for="(item, index) in divisionlist"
:key="index"
class="tag-wrap"
@dblclick="openEditModal('dept', index)"
>
<div class="tag-btn">{{ item.name }}</div>
<span class="close-btn" @click.stop="delDept(index)">×</span>
</div>
</draggable>
<Button icon="ios-add" size="large" class="add-btn" @click="openAddModal('dept')">添加系部</Button>
</div>
<div class="tip-info">
<Icon type="ios-information-circle-outline"></Icon>
双击编辑,拖动排序
</div>
</div>
<!-- ====================== 专业管理【修复拖拽失效】 ====================== -->
<div v-if="activeTab === 'major'" class="tab-panel major-adapt-wrap">
<div class="major-top-bar">
<Button type="primary" icon="ios-add" @click="openMajorAdd">添加专业</Button>
<div class="search-box">
<i-Input
v-model="majorKeyword"
placeholder="输入代码或名称搜索"
@keyup.enter="searchMajor"
>
<Icon slot="suffix" type="ios-search" @click="searchMajor" style="cursor:pointer;"></Icon>
</i-Input>
</div>
</div>
<div class="table-scroll-box" ref="tableBoxRef">
<Table
border
stripe
:columns="majorColumns"
:data="majorTableData"
class="major-table"
ref="tableRef"
></Table>
</div>
<!-- <div class="pagination-fixed-bottom">
<span class="total-desc">共 {{ majorTotal }} 条</span>
<Page
:current.sync="majorPageNum"
:total="majorTotal"
:page-size="majorPageSize"
:page-size-opts="[10,20,50]"
show-sizer
@on-change="majorPageChange"
@on-page-size-change="majorSizeChange"
></Page>
</div> -->
</div>
<!-- 校区/系部 新增编辑弹窗 -->
<Modal v-model="modalVisible" :title="modalTitle" width="420">
<Form ref="formRef" :model="formData" :rules="formRule" label-width="90">
<FormItem label="校区名称" v-if="modalType === 'campus'" prop="name">
<Input v-model="formData.name" placeholder="请输入校区名称" />
</FormItem>
<FormItem label="系部名称" v-if="modalType === 'dept'" prop="name">
<Input v-model="formData.name" placeholder="请输入系部名称" />
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="modalVisible = false" style="margin-right: 10px">取消</Button>
<Button type="primary" @click="submitModal">提交</Button>
</div>
</Modal>
<!-- 专业新增编辑弹窗 -->
<Modal v-model="majorModalVisible" :title="majorModalTitle" width="460">
<Form ref="majorFormRef" :model="majorForm" :rules="majorFormRule" label-width="90">
<FormItem label="专业代码">
<Input v-model="majorForm.code" placeholder="请输入专业代码" />
</FormItem>
<FormItem label="专业名称" prop="name">
<Input v-model="majorForm.name" placeholder="请输入专业名称" />
</FormItem>
<FormItem label="学制" prop="schsystemLeng">
<Input v-model="majorForm.schsystemLeng" type="number" placeholder="请输入学制" />
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="majorModalVisible = false" style="margin-right: 10px">取消</Button>
<Button type="primary" @click="submitMajorModal">提交</Button>
</div>
</Modal>
</div>
</template>
<script>
import draggable from 'vuedraggable'
import Sortable from 'sortablejs'
export default {
name: 'SchoolSetting',
components: { draggable },
data() {
return {
activeTab: 'campus',
campuslist: [],
divisionlist: [],
modalVisible: false,
modalType: '',
modalTitle: '',
editIndex: null,
formData: { id:0,name:'' },
formRule: {
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
},
majorKeyword: '',
majorPageNum: 1,
majorPageSize: 10,
majorTotal: 22,
majorColumns: [
{
title: '序号',
key: '_index',
align: 'center',
width: 70,
render: (h, params) => h('span', params.index + 1)
},
{ title: 'ID', key: 'id', align: 'center' },
{ title: '专业代码', key: 'code', align: 'center' },
{ title: '专业名称', key: 'name', align: 'center' },
{ title: '学制', key: 'schsystemLeng', align: 'center' },
{
title: '操作',
key: 'action',
align: 'center',
width: 170,
render: (h, params) => {
return h('div', { style: 'display:flex;gap:8px;justify-content:center;align-items:center' }, [
h('Button', {
props: { type: 'text', icon: 'ios-create', size: 'small' },
on: { click: () => this.openMajorEdit(params.index) }
}),
h('Button', {
props: { type: 'text', icon: 'ios-trash', size: 'small' },
on: { click: () => this.delMajor(params.index) }
}),
// 拖拽把手class固定为 drag-handle
h('Icon', {
props: { type: 'ios-move', size: 18, title: '拖动排序' },
class: 'drag-handle drag-icon'
})
])
}
}
],
majorTableData: [],
majorModalVisible: false,
majorModalTitle: '',
majorEditIndex: null,
majorForm: { code: '', name: '', schsystemLeng: '' },
majorFormRule: {
code: [{ required: true, message: '专业代码不能为空', trigger: 'blur' }],
name: [{ required: true, message: '专业名称不能为空', trigger: 'blur' }],
schsystemLeng: [{ required: true, message: '学制不能为空', trigger: 'blur' }]
},
tableDrag: null
}
},
watch: {
// 切换标签页重新初始化拖拽
activeTab(val) {
if (val === 'major') {
this.$nextTick(() => this.initTableDrag())
this.getmajorslist();
} else if(val === 'campus') {
if (this.tableDrag) this.tableDrag.destroy();
this.getcampuslist();
}else{
if (this.tableDrag) this.tableDrag.destroy();
this.getdivisionlist();
}
}
},
created(){
this.getcampuslist();
},
mounted() {
// 延时确保表格完全渲染完毕再初始化
setTimeout(() => {
if (this.activeTab === 'major') {
this.initTableDrag()
}
}, 120)
},
beforeDestroy() {
if (this.tableDrag) this.tableDrag.destroy()
},
methods: {
//获取学校分校列表
getcampuslist() {
const _this = this;
var item = {};
_this.$api.get(APIConfig.BschClassSystem.getcampuslist, item, resp => {
if (resp.code == 0) {
_this.campuslist = resp.data;
} else {
_this.campuslist = [];
}
});
},
//获取系部列表
getdivisionlist() {
const _this = this;
var item = {};
_this.$api.get(APIConfig.BschClassSystem.getdivisionlist, item, resp => {
if (resp.code == 0) {
_this.divisionlist = resp.data;
} else {
_this.divisionlist = [];
}
});
},
//获取专业列表
getmajorslist() {
const _this = this;
var item = {
keyword:_this.majorKeyword
};
_this.$api.get(APIConfig.BschClassSystem.getmajorslist, item, resp => {
if (resp.code == 0) {
_this.majorTableData = resp.data;
} else {
_this.majorTableData = [];
}
});
},
//校区拖动前
getdata1 (evt) {
//console.log(evt.draggedContext.element)
},
//校区拖动后
datadragEnd1 (evt) {
var _this=this;
//console.log('拖动后的索引 :' + evt.newIndex);
//console.log('拖动前的索引 :' + evt.oldIndex);
var ids=[],orders=[];
_this.campuslist.forEach(element => {
ids.push(element.id);
});
var url=APIConfig.BschClassSystem.campussort;
_this.$api.post(url,JSON.stringify(ids),(resp)=>{
_this.$Message.info('拖动排序成功!');
_this.getcampuslist();
});
},
//系部拖动前
getdata2 (evt) {
//console.log(evt.draggedContext.element)
},
//系部拖动后
datadragEnd2 (evt) {
var _this=this;
//console.log('拖动后的索引 :' + evt.newIndex);
//console.log('拖动前的索引 :' + evt.oldIndex);
var ids=[],orders=[];
_this.divisionlist.forEach(element => {
ids.push(element.id);
});
var url=APIConfig.BschClassSystem.divisionsort;
_this.$api.post(url,JSON.stringify(ids),(resp)=>{
_this.$Message.info('拖动排序成功!');
_this.getdivisionlist();
});
},
// 表格拖拽初始化【修复关键点】
initTableDrag() {
if (this.tableDrag) {
this.tableDrag.destroy()
this.tableDrag = null
}
const tableEl = this.$refs.tableRef?.$el
if (!tableEl) return
const tbody = tableEl.querySelector('tbody')
if (!tbody) return
this.tableDrag = new Sortable(tbody, {
animation: 0, // 关闭Sortable自带DOM位移动画
swap: false,
handle: '.drag-handle',
ghostClass: 'table-drag-ghost',
preventOnFilter: true,
// 关键:Sortable只计算下标,不改动页面DOM结构
forceAutoScroll: true,
onEnd: (evt) => {
const oldIndex = evt.oldIndex
const newIndex = evt.newIndex
if (oldIndex === newIndex) return
// ========== 关键 1:撤销 Sortable 的 DOM 操作 ==========
const tbody = evt.to
const rows = Array.from(tbody.querySelectorAll('tr'))
// 计算元素原来应该插回的位置
const refIndex = oldIndex < newIndex ? oldIndex : oldIndex + 1
const refNode = rows[refIndex]
if (refNode) {
tbody.insertBefore(evt.item, refNode)
} else {
tbody.appendChild(evt.item)
}
// ========== 关键 2:用 splice 移动数组元素(不是交换!) ==========
const movedItem = this.majorTableData[oldIndex]
this.majorTableData.splice(oldIndex, 1)
this.majorTableData.splice(newIndex, 0, movedItem)
// ========== 后端排序接口 ==========
const ids = this.majorTableData.map(item => item.id)
const url = APIConfig.BschClassSystem.majorssort
this.$api.post(url, JSON.stringify(ids), (resp) => {
this.$Message.info('拖动排序成功!');
})
}
})
},
// ========== 校区删除 ==========
delCampus(index) {
this.$Modal.confirm({
title: '提示',
content: '确定删除该校区?',
onOk: () => {
var url=APIConfig.BschClassSystem.campusdelete+this.campuslist[index].id;
this.$api.post(url,{},(resp)=>{
this.$Message.success('删除成功!');
this.getcampuslist();
});
}
})
},
// ========== 系部删除 ==========
delDept(index) {
this.$Modal.confirm({
title: '提示',
content: '确定删除该系部?',
onOk: () => {
var url=APIConfig.BschClassSystem.divisiondelete+this.divisionlist[index].id;
this.$api.post(url,{},(resp)=>{
this.$Message.success('删除成功!');
this.getdivisionlist();
});
}
})
},
// ========== 校区/系部 弹窗逻辑 ==========
openAddModal(type) {
this.modalType = type;
this.editIndex = null;
this.formData.id = 0;
this.formData.name = '';
this.modalTitle = type === 'campus' ? '新增校区' : '新增系部';
this.modalVisible = true;
this.$nextTick(() => this.$refs.formRef.clearValidate());
},
openEditModal(type, index) {
this.modalType = type;
this.editIndex = index;
const list = type === 'campus' ? this.campuslist : this.divisionlist;
this.formData = list[index];
this.modalTitle = type === 'campus' ? '校区信息' : '系部信息';
this.modalVisible = true;
this.$nextTick(() => this.$refs.formRef.clearValidate());
},
submitModal() {
this.$refs.formRef.validate(valid => {
if (!valid) return
const name = this.formData.name.trim()
const list = this.modalType === 'campus' ? this.campuslist : this.divisionlist;
const hasRepeat = list.some((item, i) => item.name === name && i !== this.editIndex);
if (hasRepeat) {
this.$Message.error('名称已存在,不能重复')
return
}
if (this.editIndex === null) {
const submitData = {
name: name
};
const url =this.modalType === 'campus' ? APIConfig.BschClassSystem.campussave:APIConfig.BschClassSystem.divisionsave;
this.$api.post(url, JSON.stringify(submitData), resp => {
if (resp.code == 0) {
this.$Message.success("添加成功!");
this.modalType === 'campus' ? this.getcampuslist() : this.getdivisionlist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
} else {
const submitData = {
id:this.formData.id,
sort:this.formData.sort,
name: name
};
const url =this.modalType === 'campus' ? APIConfig.BschClassSystem.campussave:APIConfig.BschClassSystem.divisionsave;
this.$api.post(url, JSON.stringify(submitData), resp => {
if (resp.code == 0) {
this.$Message.success("修改成功!");
this.modalType === 'campus' ? this.getcampuslist() : this.getdivisionlist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
this.modalVisible = false
})
},
// ========== 专业管理 ==========
openMajorAdd() {
this.majorEditIndex = null
this.majorForm = { code: '', name: '', schsystemLeng: '' }
this.majorModalTitle = '新增专业'
this.majorModalVisible = true
this.$nextTick(() => this.$refs.majorFormRef.clearValidate())
},
openMajorEdit(index) {
this.majorEditIndex = index
const row = this.majorTableData[index];
row.schsystemLeng=this.majorTableData[index].schsystemLeng+'';
this.majorForm = { ...row }
this.$nextTick(() => this.$refs.majorFormRef.clearValidate())
this.majorModalTitle = '编辑专业'
this.majorModalVisible = true
},
submitMajorModal() {
this.$refs.majorFormRef.validate(valid => {
if (!valid) return
if (this.majorEditIndex === null) {
const submitData =this.majorForm;
const url = APIConfig.BschClassSystem.majorssave;
this.$api.post(url, JSON.stringify(submitData), resp => {
if (resp.code == 0) {
this.$Message.success("新增专业成功!");
this.getmajorslist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
} else {
const submitData =this.majorForm;
const url = APIConfig.BschClassSystem.majorssave;
this.$api.post(url, JSON.stringify(submitData), resp => {
if (resp.code == 0) {
this.$Message.success("编辑专业成功!");
this.getmajorslist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
this.majorModalVisible = false
this.$nextTick(() => this.initTableDrag())
})
},
delMajor(index) {
this.$Modal.confirm({
title: '提示',
content: '确定删除该专业?',
onOk: () => {
var url=APIConfig.BschClassSystem.majorsdelete+this.majorTableData[index].id;
this.$api.post(url,{},(resp)=>{
this.$Message.success('删除成功!');
this.getmajorslist();
});
this.$nextTick(() => this.initTableDrag())
}
})
},
searchMajor() {
this.majorPageNum = 1
this.$nextTick(() => this.initTableDrag());
this.getmajorslist();
},
majorPageChange(page) {
this.majorPageNum = page
this.$nextTick(() => this.initTableDrag())
},
majorSizeChange(size) {
this.majorPageSize = size
this.majorPageNum = 1
this.$nextTick(() => this.initTableDrag())
}
}
}
</script>
<style scoped>
.page-wrap {
padding: 16px;
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.page-title {
margin: 0 0 12px;
font-size: 18px;
font-weight: 500;
}
.tab-panel {
margin-top: 20px;
flex-shrink: 0;
}
.btn-container {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16px;
}
.drag-box {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
/* 悬浮删除容器 */
.tag-wrap {
position: relative;
cursor: move;
user-select: none;
}
.tag-btn {
border: 1px solid #666;
padding: 8px 22px;
border-radius: 2px;
}
.close-btn {
position: absolute;
top: -6px;
right: -6px;
width: 18px;
height: 18px;
border-radius: 50%;
background: #f56c6c;
color: #fff;
font-size: 14px;
text-align: center;
line-height: 18px;
display: none;
cursor: pointer;
}
.tag-wrap:hover .close-btn {
display: block;
}
.add-btn {
padding: 8px 22px;
}
.tip-info {
margin-top: 30px;
color: #999;
font-size: 13px;
display: flex;
align-items: center;
gap: 6px;
}
::v-deep .drag-ghost {
opacity: 0.4;
background: #cde4ff;
}
/* 专业布局 */
.major-adapt-wrap {
flex: 1;
display: flex;
flex-direction: column;
margin-top: 0px;
min-height: 0;
}
.major-top-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
flex-shrink: 0;
background: #F7F7F7;
padding: 10px;
}
.search-box {
width: 260px;
}
.table-scroll-box {
flex: 1;
overflow-y: auto;
}
.major-table {
width: 100%;
}
.drag-icon {
cursor: move;
margin-left: 8px;
font-size: 18px;
}
/* 关键:穿透scoped,Sortable能识别把手 */
::v-deep .drag-handle {
cursor: grab !important;
}
::v-deep .table-drag-ghost {
background-color: #e8f3ff !important;
}
.pagination-fixed-bottom {
flex-shrink: 0;
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 14px;
gap: 12px;
}
.total-desc {
color: #666;
font-size: 13px;
}
</style>