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
+211
View File
@@ -0,0 +1,211 @@
<template>
<div id="app" v-if="isLogin">
<layout :layoutData="layoutData" ref="layout"></layout>
<div :class="['base-wrap-main', layoutData.isAlone ? 'alone-page' : '']">
<template v-if="!isNoAuthority">
<transition name="fade" mode="out-in">
<router-view v-if="isRouterAlive"></router-view>
</transition>
</template>
<div v-else class="no-authority-wrap">
<h3>暂无权限</h3>
<h6>如需查看,请联络管理员为您分配权限</h6>
</div>
</div>
</div>
</template>
<script>
import layout from "@/frame/layout";
const appendLink = (baseHost, name, version) => {
var headHTML = `<link type="text/css" rel="stylesheet" href="${baseHost}/BschHome/front/css/${name}.css?v=${version}">`;
$("head:eq(0)").append(headHTML);
};
export default {
name: "App",
data() {
return {
isRouterAlive: true,
isFull: false,
isLogin: false,
layoutData: {},
isNoAuthority: false,
};
},
components: { layout },
provide() {
return {
reload: this.reload,
};
},
async created() {
const _this = this;
try {
var data = await this.getLayoutData();
const baseHost = data.pageUrl.replace("/v/", "");
var version = localStorage.getItem("themeVersion");
appendLink(baseHost, "base.layout", version ? version : new Date().getTime());
var bschResult = await this.getBschHomeJsonp(baseHost, data.id);
this.setCurrentRole(bschResult, baseHost);
data = await this.getLayoutData();
this.layoutInit(data, bschResult.appList, baseHost);
this.isLogin = true;
} catch (error) {
console.error(error);
this.$Message.error("载入用户信息失败!");
}
//console.log(process.env);
},
methods: {
reload() {
this.isRouterAlive = false;
this.$nextTick(() => (this.isRouterAlive = true));
},
getLayoutData() {
const _this = this;
return new Promise((resolve, reject) => {
_this.$api.get(
APIConfig.Base.GetLayout,
null,
resp => {
resolve(resp.data);
},
error => {
reject(error);
}
);
});
},
getBschHomeJsonp(baseHost, userId) {
var version = localStorage.getItem("themeVersion") || new Date().getTime();
return new Promise((resolve, reject) => {
var crossStorage,
crossUrl = `${baseHost}/BschHome/front/libs/cross`;
try {
var HEAD = document.getElementsByTagName("head")[0];
var node = document.createElement("script");
node.src = `${crossUrl}/client.js?v=${version}`;
node.onload = function () {
crossStorage = new Client(`${crossUrl}/middle.html?v=${version}`, () => {
crossStorage.getRoteData({ userId }, resp => {
resolve(resp);
});
});
//分页数据默认条数
const customPageCount = localStorage.getItem("customPageCount");
crossStorage.getItem("customPageCount", resp => {
if (resp == null) return false;
if (customPageCount == null) localStorage.setItem("customPageCount", resp);
else if (resp != customPageCount) {
crossStorage.setItem({
key: "customPageCount",
value: customPageCount,
});
}
});
};
HEAD.appendChild(node);
} catch (error) {
console.error(error);
resolve(null);
}
});
},
layoutInit(data, appList, baseHost) {
this.layoutData.appName = data.topName;
this.layoutData.baseHost = baseHost;
this.layoutData.ssoHost = data.ssoUrl;
let arr = [];
const routes = this.$router.options.routes;
data.pathList.forEach(element => {
let iconIndex = routes.findIndex(x => typeof x.meta !== "undefined" && typeof x.meta.modId !== "undefined" && typeof x.meta.icon !== "undefined" && x.meta.modId == element.modId);
let icon = iconIndex > -1 ? "iconfont " + routes[iconIndex].meta.icon : ""; //icon不传则为默认
arr.push({
id: element.id,
name: element.name,
modId: element.modId,
page: element.page,
children: element.children || [],
icon, //icon不传则为默认
});
});
// arr[0].icon = "iconfont icon-qunzu"; //icon样式示例,参见static/style/font 目录
//console.log(arr);
this.layoutData.modList = arr.sort((a, b) => a.modId - b.modId);
this.layoutData.isAlone = arr.length == 1; //当只有一个选项卡时,隐藏menu和nav
// let { id, name, profilePicture } = data;
let { id, username, avatar } = data;
this.layoutData.userInfo = { id, name: username, avatar };
this.layoutData.appList = appList || [];
//无权限
if (arr.length == 0) {
this.layoutData.isAlone = true;
this.isNoAuthority = true;
}
},
setCurrentRole(result, baseHost) {
if (result) {
let { childId, roleId, schId } = result.checkRole;
localStorage.setItem("currentRole", encodeURI(JSON.stringify({ childId, roleId, schId })));
localStorage.setItem("themeVersion", result.themeVersion);
appendLink(baseHost, result.themeName, result.themeVersion);
} else {
appendLink(baseHost, "default-theme", new Date().getTime());
}
},
},
};
</script>
<style lang="scss">
#loging-box {
display: none;
}
.fade-enter-active,
.fade-leave-active {
transition: all 1ms cubic-bezier(0, 0.99, 1, 0.03);
}
/* 进入开始 */
.fade-enter {
// transform: translateY(10px);
opacity: 0;
}
/* 出去终点 */
.fade-leave-active {
// transform: translateY(-10px);
// opacity: 0;
}
.back-enter-active,
.back-leave-active {
transition: all 1ms cubic-bezier(0, 0.99, 1, 0.03);
}
/* 进入开始 */
.back-enter {
display: none;
}
/* 出去终点 */
.back-leave-active {
display: none;
}
.main-wrap {
padding: 120px 0 0 200px;
height: 100%;
}
.main-panel {
height: 100%;
overflow-y: auto;
position: relative;
box-sizing: border-box;
padding: 15px;
}
.main-panel.is-page {
height: calc(100% - 60px);
}
.full {
padding: 0;
}
</style>
+255
View File
@@ -0,0 +1,255 @@
<template>
<div id="app" v-if="isLogin">
<layout-old v-if="isLayoutOld" :layoutData="layoutData"></layout-old>
<layout v-else :layoutData="layoutData"></layout>
<div :class="['base-wrap-main', layoutData.isAlone ? 'alone-page' : '']">
<transition v-if="!isNoAuthority" :name="!isBack ? 'fade' : 'back'" :mode="!isBack ? 'out-in' : 'in-out'">
<router-view v-if="isRouterAlive"></router-view>
</transition>
<div v-else class="no-authority-wrap">
<h3>暂无权限</h3>
<h6>如需查看,请联络管理员为您分配权限</h6>
</div>
</div>
<!-- <Modal v-model="isAlertModal" :closable="false" :mask-closable="false" title="更新提示" width="830">
<Alert show-icon>
因为铃铛等环境无法同一时间更新,为不影响正在运行的项目,所以新的标准样式名称改为base.layout.beta.css
</Alert>
<p>
<img width="100%" src="./assets/image/1636426109.jpg" alt="" />
</p>
</Modal> -->
</div>
</template>
<script>
import layout from "./frame/layout.vue";
import layoutOld from "./frame/layout_old.vue";
const appendLink = (baseHost, name, version) => {
var headHTML = `<link type="text/css" rel="stylesheet" href="${baseHost}/BschHome/front/css/${name}.css?v=${version}">`;
$("head:eq(0)").append(headHTML);
};
export default {
components: { layout, layoutOld },
name: "app",
data() {
return {
isRouterAlive: true,
isBack: false,
isLogin: false,
isNoAuthority: false,
layoutData: {},
isLayoutOld: conf.isLayoutOld,
isAlertModal: false,
};
},
provide() {
return {
reload: this.reload,
};
},
async created() {
try {
const baseHost = window.conf.baseHost,
ssoHost = window.conf.ssoHost;
var version = localStorage.getItem("themeVersion") || new Date().getTime();
appendLink(baseHost, "base.layout.beta", version);
var info = await this.getUserInfo();
var bschResult = await this.getBschHomeJsonp(baseHost, info.id);
this.setCurrentRole(bschResult, baseHost, info.id);
var mod = await this.getUserMod();
this.layoutInit(info, mod, bschResult.appList, baseHost, ssoHost);
this.isLogin = true;
} catch (error) {
console.error(error);
this.$Message.error("载入用户信息失败!");
}
// this.isAlertModal = true;
// if (localStorage.getItem("cook_is_alert") == null) {
// localStorage.setItem("cook_is_alert", true);
// this.isAlertModal = true;
// }
},
mounted() {
var isTrident = navigator.userAgent.indexOf("Trident") > -1; // 是否是IE内核
if (isTrident) {
$("html").css("overflow", "hidden");
}
},
methods: {
reload() {
this.isRouterAlive = false;
this.$nextTick(() => (this.isRouterAlive = true));
},
getUserInfo() {
const _this = this;
return new Promise((resolve, reject) => {
_this.$api.get(
APIConfig.Base.GetUserInfo,
null,
resp => {
resolve(resp.data);
},
error => reject(error)
);
});
},
getUserMod() {
const _this = this;
return new Promise((resolve, reject) => {
_this.$api.get(
APIConfig.Base.GetUserMod,
null,
resp => {
let resps={"code":0,"msg":"","data":
[
{"id":1493,"appId":551,"modId":1,"name":"学制选择","parentId":0,"path":null,"level":0,"page":"/","icon":null,"orderNumber":1493,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":2,"name":"时段设置","parentId":0,"path":null,"level":0,"page":"/itemSet","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":3,"name":"区域操作日志","parentId":0,"path":null,"level":0,"page":"/areaOperateDaily","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":4,"name":"学校设置","parentId":0,"path":null,"level":0,"page":"/schoolSetting","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":5,"name":"课程设置","parentId":0,"path":null,"level":0,"page":"/subjectManForZy","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":6,"name":"学科管理","parentId":0,"path":null,"level":0,"page":"/subjectSetzhi","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":7,"name":"班级管理","parentId":0,"path":null,"level":0,"page":"/classManage","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":8,"name":"选修班管理","parentId":0,"path":null,"level":0,"page":"/electiveClassManage","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":9,"name":"教师管理","parentId":0,"path":null,"level":0,"page":"/teacherManage","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":10,"name":"学生管理","parentId":0,"path":null,"level":0,"page":"/studentManage","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":11,"name":"历史查询","parentId":0,"path":null,"level":0,"page":"/historySearch","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":12,"name":"学生异动管理","parentId":0,"path":null,"level":0,"page":"/studentYDManage","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":13,"name":"教师转入管理","parentId":0,"path":null,"level":0,"page":"/zhuanruinfo","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":14,"name":"学生转入管理","parentId":0,"path":null,"level":0,"page":"/zhuanruinfostu","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
{"id":1494,"appId":551,"modId":15,"name":"日志管理","parentId":0,"path":null,"level":0,"page":"/dailySearch","icon":null,"orderNumber":1494,"usable":true,"children":[],"modules":[]},
],"timeNow":"2026-06-17 14:44:05"}
resolve(resps.data);
},
error => reject(error)
);
});
},
getBschHomeJsonp(baseHost, userId) {
var version = localStorage.getItem("themeVersion") || new Date().getTime();
return new Promise((resolve, reject) => {
var crossStorage,
crossUrl = `${baseHost}/BschHome/front/libs/cross`;
try {
var HEAD = document.getElementsByTagName("head")[0];
var node = document.createElement("script");
node.src = `${crossUrl}/client.js?v=${version}`;
node.onload = function () {
crossStorage = new Client(`${crossUrl}/middle.html?v=${version}`, () => {
crossStorage.getRoteData({ userId }, resp => {
resolve(resp);
});
//分页数据默认条数
const customPageCount = localStorage.getItem("customPageCount");
crossStorage.getItem("customPageCount", resp => {
if (resp == null) return false;
if (customPageCount == null) localStorage.setItem("customPageCount", resp);
else if (resp != customPageCount) {
crossStorage.setItem({
key: "customPageCount",
value: customPageCount,
});
}
});
});
};
HEAD.appendChild(node);
} catch (error) {
console.error(error);
console.error("获取身份信息失败!");
resolve(null);
}
});
},
layoutInit(info, mod, appList, baseHost, ssoHost) {
this.layoutData.appName = "班级管理";
this.layoutData.baseHost = baseHost;
this.layoutData.ssoHost = ssoHost;
const routes = this.$router.options.routes;
let arr = [];
mod.forEach(element => {
let iconIndex = routes.findIndex(
x =>
typeof x.meta !== "undefined" &&
typeof x.meta.modId !== "undefined" &&
typeof x.meta.icon !== "undefined" &&
x.meta.modId == element.modId
);
let icon = iconIndex > -1 ? "iconfont " + routes[iconIndex].meta.icon : ""; //icon不传则为默认
arr.push({
id: element.id,
name: element.name,
modId: element.modId,
page: element.page,
children: element.children || [],
icon,
});
});
// arr[0].icon = "iconfont icon-apps"; //icon样式示例,参见static/style/font 目录
this.layoutData.modList = arr.sort((a, b) => a.modId - b.modId);
this.layoutData.isAlone = arr.length == 1; //当只有一个选项卡时,隐藏menu和nav
let { id, name, profilePicture } = info;
this.layoutData.userInfo = { id, name, avatar: profilePicture };
this.layoutData.appList = appList || [];
//无权限
if (arr.length == 0) {
this.layoutData.isAlone = true;
this.isNoAuthority = true;
}
},
setCurrentRole(result, baseHost, userId) {
if (result) {
let { childId, roleId, schId } = result.checkRole;
localStorage.setItem("currentRole", encodeURI(JSON.stringify({ childId, roleId, schId, userId })));
localStorage.setItem("themeVersion", result.themeVersion);
appendLink(baseHost, result.themeName, result.themeVersion);
} else {
appendLink(baseHost, "default-theme", new Date().getTime());
}
},
},
};
</script>
<style lang="scss">
#loging-box,
.mop-load-div {
display: none;
}
/* 开始过渡阶段,动画出去阶段 */
.fade-enter-active,
.fade-leave-active {
transition: all 1ms cubic-bezier(0, 0.99, 1, 0.03);
}
/* 进入开始 */
.fade-enter {
// transform: translateY(10px);
opacity: 0;
}
/* 出去终点 */
.fade-leave-active {
// transform: translateY(-10px);
// opacity: 0;
}
.back-enter-active,
.back-leave-active {
transition: all 1ms cubic-bezier(0, 0.99, 1, 0.03);
}
/* 进入开始 */
.back-enter {
display: none;
}
/* 出去终点 */
.back-leave-active {
display: none;
}
#app {
height: 100%;
}
</style>
+198
View File
@@ -0,0 +1,198 @@
// 配置API接口地址
// var root = '/api/v1'
// var root = '/api/debugapi'
var root = "/api";
// var root = ''
// 引用axios
var axios = require("axios");
// import Notice from 'iview/src/components/notice';
import { Message } from "view-design";
// 自定义判断元素类型JS
function toType(obj) {
return {}.toString
.call(obj)
.match(/\s([a-zA-Z]+)/)[1]
.toLowerCase();
}
// 参数过滤函数
function filterNull(o) {
for (var key in o) {
if (o[key] === null) {
delete o[key];
}
if (toType(o[key]) === "string") {
o[key] = o[key].trim();
} else if (toType(o[key]) === "object") {
o[key] = filterNull(o[key]);
} else if (toType(o[key]) === "array") {
o[key] = filterNull(o[key]);
}
}
return o;
}
/*
接口处理函数
这个函数每个项目都是不一样的,我现在调整的是适用于
https://cnodejs.org/api/v1 的接口,如果是其他接口
需要根据接口的参数进行调整。参考说明文档地址:
https://cnodejs.org/topic/5378720ed6e2d16149fa16bd
主要是,不同的接口的成功标识和失败提示是不一致的。
另外,不同的项目的处理方法也是不一致的,这里出错就是简单的alert
*/
function apiAxios(method, url, params, success, failure) {
let conentTypeIsJosn = false;
if (params && typeof params != "string") {
params = filterNull(params);
} else conentTypeIsJosn = true;
// if (!window.location.origin) {
// window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
// }
// let redirectUrl=window.location.origin + window.location.hash.replace('#','/xiaoxi/#');
// console.log(window.location);
axios({
method: method,
url: url,
data: method === "POST" || method === "PUT" ? params : null,
params: method === "GET" || method === "DELETE" ? params : null,
baseURL: root,
withCredentials: true,
crossDomain: true,
transformRequest: [
function (data) {
// Do whatever you want to transform the data
if (conentTypeIsJosn) return data;
let ret = "";
for (let it in data) {
ret += encodeURIComponent(it) + "=" + encodeURIComponent(data[it]) + "&";
}
return ret;
},
],
headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
"Content-Type": conentTypeIsJosn ? "application/json" : "application/x-www-form-urlencoded",
// bschapirequest: 1,
yschapirequest:1,
yschreferer: this.$utils.strToHexCharCode(window.location.href),
},
})
.then(function (res) {
let response = res.data;
if (response.code == 302) {
window.location.href = response.urlToRedirectTo;
return;
} else if (response.code == 0) {
if (success) {
success(response);
}
}else if (response.code == 4) {
Message.error(response.msg); //错误处理
} else {
if (failure) {
failure(response);
} else {
if (response.data == 2) {
Message.error(response.msg); //错误处理
setTimeout(() => {
location.reload();
}, 1000);
} else {
Message.info(response.msg); //错误处理
}
}
}
})
.catch(function (err) {
let res = err.response;
console.error(res || err);
if (res) {
Message.destroy();
clearTimeout(tiemObj);
if (res.data.msg) {
Message.error(res.data.msg); //错误处理
} else {
Message.error("网络请求出错"); //错误处理
}
return;
}
});
}
//字符串的16进制表达
function strToHexCharCode(_str) {
if (_str === "") return "";
var hexCharCode = [];
for (var i = 0; i < _str.length; i++) {
var str = _str.charCodeAt(i).toString(16);
if (_str.length == 1) str += "0" + _str;
hexCharCode.push(str);
}
return hexCharCode.join("");
}
let requestCount = 0,
tiemObj;
// http request 拦截器
axios.interceptors.request.use(config => {
requestCount++;
if (requestCount == 1) {
tiemObj = setTimeout(() => {
Message.loading({ content: "加载中...", duration: 0 });
}, 800);
}
// if (localStorage.getItem("currentRole"))
// config.headers["currentRole"] = localStorage.getItem("currentRole");
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
config.headers["currentrole"] = currentRole;
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
// config.headers["schoolId"] = schId;
// config.headers["userId"] = userId;
// config.headers["roleId"] = roleId;
if (roleId == 3) {
config.headers["userId"] = childId;
}
sessionStorage.setItem("userId",userId);
}
if(sessionStorage.getItem("uType")){
config.headers["uType"] = sessionStorage.getItem("uType");
}
return config;
});
// http response 拦截器
axios.interceptors.response.use(response => {
requestCount--;
if (requestCount === 0) {
setTimeout(() => {
Message.destroy();
}, 1500);
clearTimeout(tiemObj);
}
return response;
});
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
// axios.defaults.headers.post['Content-Type'] = 'application/json; charset=utf-8';
// axios.defaults.withCredentials = true
// 返回在vue模板中的调用接口
export default {
get: function (url, params, success, failure) {
return apiAxios("GET", url, params, success, failure);
},
post: function (url, params, success, failure) {
return apiAxios("POST", url, params, success, failure);
},
put: function (url, params, success, failure) {
return apiAxios("PUT", url, params, success, failure);
},
delete: function (url, params, success, failure) {
return apiAxios("DELETE", url, params, success, failure);
},
};
+180
View File
@@ -0,0 +1,180 @@
// 配置API接口地址
// var root = '/api/v1'
// var root = '/api/debugapi'
var root = "/api";
// var root = ''
// 引用axios
var axios = require("axios");
// import Notice from 'iview/src/components/notice';
import { Message } from "view-design";
// 自定义判断元素类型JS
function toType(obj) {
return {}.toString
.call(obj)
.match(/\s([a-zA-Z]+)/)[1]
.toLowerCase();
}
// 参数过滤函数
function filterNull(o) {
for (var key in o) {
if (o[key] === null) {
delete o[key];
}
if (toType(o[key]) === "string") {
o[key] = o[key].trim();
} else if (toType(o[key]) === "object") {
o[key] = filterNull(o[key]);
} else if (toType(o[key]) === "array") {
o[key] = filterNull(o[key]);
}
}
return o;
}
/*
接口处理函数
这个函数每个项目都是不一样的,我现在调整的是适用于
https://cnodejs.org/api/v1 的接口,如果是其他接口
需要根据接口的参数进行调整。参考说明文档地址:
https://cnodejs.org/topic/5378720ed6e2d16149fa16bd
主要是,不同的接口的成功标识和失败提示是不一致的。
另外,不同的项目的处理方法也是不一致的,这里出错就是简单的alert
*/
function apiAxios(method, url, params, success, failure) {
let conentTypeIsJosn = false;
if (params && typeof params != "string") {
params = filterNull(params);
} else conentTypeIsJosn = true;
// if (!window.location.origin) {
// window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
// }
// let redirectUrl=window.location.origin + window.location.hash.replace('#','/xiaoxi/#');
// console.log(window.location);
axios({
method: method,
url: url,
data: method === "POST" || method === "PUT" ? params : null,
params: method === "GET" || method === "DELETE" ? params : null,
baseURL: root,
// withCredentials: true,
crossDomain: true,
transformRequest: [
function(data) {
// Do whatever you want to transform the data
if (conentTypeIsJosn) return data;
let ret = "";
for (let it in data) {
ret +=
encodeURIComponent(it) + "=" + encodeURIComponent(data[it]) + "&";
}
return ret;
},
],
headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
"Content-Type": conentTypeIsJosn
? "application/json"
: "application/x-www-form-urlencoded",
},
})
.then(function(res) {
let response = res.data;
if (response.State) {
if (success) {
success(response);
}
} else {
if (failure) {
failure(response.Data);
} else {
if (response.Data == 2) {
Message.error(response.Message); //错误处理
setTimeout(() => {
location.reload();
}, 1000);
} else {
Message.info(response.Message); //错误处理
}
}
}
})
.catch(function(err) {
let res = err.response;
console.error(res || err);
if (res) {
Message.destroy();
clearTimeout(tiemObj);
// if(res.status===401&&res.data.OtherData.ErrorCode==2){
if (res.status === 401) {
window.location.reload();
// if(process.env.NODE_ENV === 'production')
// Message.error(res.data.Message);//错误处理
// setTimeout(() => {
// location.reload();
// }, 0);
} else if (res.data.Message) {
Message.error(res.data.Message); //错误处理
} else {
Message.error("网络请求出错"); //错误处理
}
return;
}
});
}
let requestCount = 0,
tiemObj;
// http request 拦截器
axios.interceptors.request.use((config) => {
requestCount++;
if (requestCount == 1) {
tiemObj = setTimeout(() => {
Message.loading({ content: "加载中...", duration: 0 });
}, 800);
}
if (localStorage.getItem("currentRole"))
config.headers["currentRole"] = localStorage.getItem("currentRole");
if (process.env.NODE_ENV !== "production") {
//如果是开发环境
config.headers["DebugUser"] = "zhd";
config.headers["DebugAppType"] = true;
}
return config;
});
// http response 拦截器
axios.interceptors.response.use((response) => {
requestCount--;
if (requestCount === 0) {
setTimeout(() => {
Message.destroy();
}, 1500);
clearTimeout(tiemObj);
}
return response;
});
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
// axios.defaults.headers.post['Content-Type'] = 'application/json; charset=utf-8';
// axios.defaults.withCredentials = true
// 返回在vue模板中的调用接口
export default {
get: function(url, params, success, failure) {
return apiAxios("GET", url, params, success, failure);
},
post: function(url, params, success, failure) {
return apiAxios("POST", url, params, success, failure);
},
put: function(url, params, success, failure) {
return apiAxios("PUT", url, params, success, failure);
},
delete: function(url, params, success, failure) {
return apiAxios("DELETE", url, params, success, failure);
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+59
View File
@@ -0,0 +1,59 @@
var HtmlUtil = {
/*1.用浏览器内部转换器实现html转码*/
htmlEncode: function(html) {
//1.首先动态创建一个容器标签元素,如DIV
var temp = document.createElement("div");
//2.然后将要转换的字符串设置为这个元素的innerText(ie支持)或者textContent(火狐,google支持)
temp.textContent != undefined ? (temp.textContent = html) : (temp.innerText = html);
//3.最后返回这个元素的innerHTML,即得到经过HTML编码转换的字符串了
var output = temp.innerHTML;
temp = null;
return output;
},
/*2.用浏览器内部转换器实现html解码*/
htmlDecode: function(text) {
//1.首先动态创建一个容器标签元素,如DIV
var temp = document.createElement("div");
//2.然后将要转换的字符串设置为这个元素的innerHTML(ie,火狐,google都支持)
temp.innerHTML = text;
//3.最后返回这个元素的innerText(ie支持)或者textContent(火狐,google支持),即得到经过HTML解码的字符串了。
var output = temp.innerText || temp.textContent;
temp = null;
return output;
},
/*3.用正则表达式实现html转码*/
htmlEncodeByRegExp: function(str) {
var s = "";
if (str.length == 0) return "";
s = str.replace(/&/g, "&amp;");
s = s.replace(/</g, "&lt;");
s = s.replace(/>/g, "&gt;");
s = s.replace(/ /g, "&nbsp;");
s = s.replace(/\'/g, "&#39;");
s = s.replace(/\"/g, "&quot;");
return s;
},
/*4.用正则表达式实现html解码*/
htmlDecodeByRegExp: function(str) {
var s = "";
if (str.length == 0) return "";
s = str.replace(/&amp;/g, "&");
s = s.replace(/&lt;/g, "<");
s = s.replace(/&gt;/g, ">");
s = s.replace(/&nbsp;/g, " ");
s = s.replace(/&#39;/g, "'");
s = s.replace(/&quot;/g, '"');
return s;
}
};
export const load = name => {
let url = (process.env.NODE_ENV === "production" ? "/SchConfig/standard" : "") + "/code/" + name;
// let url = "../code/" + name;
return new Promise((resolve, reject) => {
$.get(url, resp => {
let encode = HtmlUtil.htmlEncode(resp);
resolve({ html: resp, encode });
});
});
};
+22
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+97
View File
@@ -0,0 +1,97 @@
ol,p,h1,h2,h3,h4,h5,ul,dd,dt,ol{padding:0px;margin:0px;list-style:none}
html{-webkit-text-size-adjust:none}
body{width:100%;margin:0;padding:0;position:relative;height:100%;color:#333;min-width:1200px}
html,body,#app{margin:0;padding:0;height:100%;font-size:14px;}
a{
text-decoration:none;-webkit-tap-highlight-color:transparent;outline:none;
&:hover,&:active{text-decoration:none}
}
*{border:0;margin:0;padding:0;box-sizing:border-box;font-family:'PingFang SC',tahoma,arial,'helvetica neue','hiragino sans gb','microsoft yahei',sans-serif}
//搜索框
.search-wrap {
background: #f7f7f7;
padding: 15px;
&_date {
.ivu-date-picker {
width: 150px;
}
}
&_sel{
width:150px;
float: left;
}
&_input{
width:200px;
float: left;
}
}
.public {
&-custom-switch {
.ivu-radio-wrapper-checked {
background: #6b8cf4 !important;
border-color: #6b8cf4 !important;
}
.ivu-radio-group-item {
width: 90px;
text-align: center;
}
}
&-upload-img {
li {
width: 100px;
height: 100px;
border: 1px solid #eee;
border-radius: 2px;
padding: 4px;
position: relative;
float: left;
margin: 0 10px 10px 0;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
&:hover .upload-list-cover {
display: block;
}
.upload-list-cover {
position: absolute;
top: 4px;
bottom: 4px;
left: 4px;
right: 4px;
background: rgba(0, 0, 0, 0.6);
text-align: center;
display: none;
transition: all 0.3s;
i {
color: #fff;
position: relative;
top: 50%;
cursor: pointer;
transform: translateY(-50%);
}
}
}
li.add-li {
border: none;
padding: 0;
width: 100px;
height: 100px;
}
li .add-box i {
line-height: 100px;
}
}
}
+1
View File
@@ -0,0 +1 @@
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;margin:0.67em 0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em;}pre{white-space:pre-wrap;}q{quotes:"\201C" "\201D" "\2018" "\2019";}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:0;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}
+161
View File
@@ -0,0 +1,161 @@
.f10 {
font-size: 10px;
}
.f12 {
font-size: 12px;
}
.f13 {
font-size: 13px;
}
.f14 {
font-size: 14px;
}
.f15 {
font-size: 15px;
}
.f16 {
font-size: 16px;
}
.f17 {
font-size: 17px;
}
.f18 {
font-size: 18px;
}
.f20 {
font-size: 20px;
}
.f24 {
font-size: 24px;
}
// 文字对齐
.tl {
text-align: left;
}
.tc {
text-align: center;
}
.tr {
text-align: right;
}
//浮动
.fr {
float: right !important;
}
.fl {
float: left !important;
}
.fix {
*zoom: 1;
}
.fix:after {
display: table;
content: "";
clear: both;
}
.clearfix {
@extend .fix;
}
// 显示
.dn {
display: none;
}
.di {
display: inline;
}
.db {
display: block;
}
.dib {
display: inline-block;
}
.dt {
display: table;
}
div.dib {
*display: inline;
*zoom: 1;
}
.vm {
vertical-align: middle;
}
.vib {
display: inline-block;
vertical-align: middle;
}
// 定位
.pr {
position: relative;
}
.pa {
position: absolute;
}
.pf {
position: fixed;
}
.oh {
overflow: hidden;
}
@for $i from 1 through 100 {
.width-#{$i} {
width: $i * 1px;
}
.height-#{$i} {
height: $i * 1px;
}
.ml-#{$i} {
margin-left: $i * 1px;
}
.mr-#{$i} {
margin-right: $i * 1px;
}
.mt-#{$i} {
margin-top: $i * 1px;
}
.mb-#{$i} {
margin-bottom: $i * 1px;
}
}
.align-middle {
position: relative;
top: 50%;
transform: translateY(-50%);
}
.align-middle-wrap {
display: flex;
align-items: center;
}
//0.5px
@mixin border(
$borderWidth: 1px,
$borderStyle: solid,
$borderColor: #ddd,
$borderRadius: 0
) {
position: relative;
&:before {
content: "";
position: absolute;
width: 200%;
height: 200%;
top: 0;
left: 0;
transform-origin: left top;
-webkit-transform-origin: left top;
-webkit-transform: scale(0.5);
box-sizing: border-box;
pointer-events: none;
}
&:before {
border-width: $borderWidth;
border-style: $borderStyle;
border-color: $borderColor;
border-radius: $borderRadius;
}
}
+84
View File
@@ -0,0 +1,84 @@
/*滚动条样式 */
/*第一种样式,所有的滚动条,都默认这个样子*/
*::-webkit-scrollbar {
width: 10px;
height: 10px;
}
/*正常情况下滑块的样式*/
*::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, .05);
border-radius: 10px;
-webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
}
/*鼠标悬浮在该类指向的控件上时滑块的样式*/
*:hover::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, .2);
border-radius: 10px;
-webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
}
/*鼠标悬浮在滑块上时滑块的样式*/
*::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, .3);
-webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
}
/*正常时候的主干部分*/
*::-webkit-scrollbar-track {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0);
background-color: white;
}
/*鼠标悬浮在滚动条上的主干部分*/
*::-webkit-scrollbar-track:hover {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .4);
background-color: rgba(0, 0, 0, .01);
}
/*第一种样式结束*/
/*第二种样式*/
.scroll2::-webkit-scrollbar {
width: 10px;
height: 10px;
}
/*正常时候的主干部分*/
.scroll2::-webkit-scrollbar-track {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0);
background-color: white;
}
/*正常情况下滑块的样式*/
.scroll2::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, .05);
border-radius: 10px;
-webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
}
/*鼠标悬浮在该类指向的控件上时滑块的样式*/
.scroll2:hover::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, .2);
border-radius: 10px;
-webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
}
/*鼠标悬浮在滚动条上的主干部分*/
.scroll2::-webkit-scrollbar-track:hover {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .4);
}
/*鼠标悬浮在滑块上时滑块的样式*/
.scroll2::-webkit-scrollbar-thumb:hover {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .4);
}
/*第二种样式结束*/
+31
View File
@@ -0,0 +1,31 @@
@import "base";
@import "normalize";
@import "utils";
@import "bscroll.css";
// @import "iview_override";
.webapp-panel{
background: url(../image/phone.png) no-repeat;
width: 337px;
height: 580px;
padding: 50px 35px 60px 35px;
}
.ivu-tree-arrow .ivu-icon-ios-arrow-forward:before {
content: "\e65c"; /* 例如,使用阿里巴巴矢量图标库的某个图标 */
font-family: "iconfont"; /* 替换为你的图标字体 */
color: #8e9397;
}
.ivu-tree-arrow-open .ivu-icon-ios-arrow-forward:before {
content: "\e65d"; /* 例如,使用阿里巴巴矢量图标库的某个图标 */
font-family: "iconfont"; /* 替换为你的图标字体 */
color: #8e9397;
}
.ivu-tree-arrow-open i{
transform: rotate(0deg);
}
@media screen and (max-width: 1366px) {
html {
zoom: 0.9;
}
}
+473
View File
@@ -0,0 +1,473 @@
$tabBtnColor: #577ae9;//tab主题色
$treeCheckColor: #577ae9;//树主题色
$treeCheckBorderColor: #dddee1;//边框色
$treeCheckGroupColor: #f7f7f7;
.fix {
*zoom: 1;
&::before,
&::after {
display: table;
content: "";
line-height: 0;
}
&::after {
clear: both;
}
}
.fl {
float: left;
}
.fr {
float: right;
}
.align-middle {
position: relative;
top: 50%;
transform: translateY(-50%);
}
.my-tab_btn {
margin: 10px auto;
position: relative;
.item-tab_btn {
font-size: 16px;
font-weight: 550;
color: #656667;
float: left;
text-align: center;
position: relative;
box-sizing: border-box;
height: 32px;
line-height: 32px;
margin-left: 15px;
}
.item-tab_btn.active {
color: #272829;
}
.my-tab_slider {
width: 42px;
position: absolute;
left: 0px;
bottom: -1px;
z-index: 1;
transition: transform 0.3s ease-in-out;
transform-origin: 0 0;
& > .indicator {
height: 2px;
width: 17px;
background: $tabBtnColor;
margin: 0 auto;
}
}
}
.my-tab_card {
margin: 15px 0 15px 10px;
padding-right: 30px;
position: relative;
.item-wrap{
white-space: nowrap;
display: flex;
align-items: center;
overflow: hidden;
overflow-x: scroll;
}
.item-tab_card {
float: left;
font-size: 13px;
padding: 0px 15px;
color: #808080;
height: 27px;
line-height: 27px;
transition: all 200ms;
}
.item-tab_card.active {
color: #272829;
background: #f7f7f7;
border-radius: 27px;
}
}
.my-modal {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0.5;
background: #000;
z-index: 100;
}
.tree-popup {
position: fixed;
background: #fff;
top: 100%;
left: 0;
transform: translateY(0);
backface-visibility: hidden;
transition: 0.2s ease-in;
width: 100%;
height: 85%;
z-index: 101;
border-radius: 8px 8px 0 0;
&_show {
transform: translateY(-100%);
}
&_hide {
transform: translateY(0%);
}
&_header {
position: relative;
overflow: hidden;
@extend .my-border_bottom;
& .close-btn {
position: absolute;
padding: 5px;
right: 3%;
top: 10px;
}
}
&_body {
overflow-y: auto;
min-height: 250px;
}
&_footer {
height: 60px;
width: 100%;
box-sizing: border-box;
background: #fff;
position: relative;
padding: 0 10px 0 20px;
@extend .my-border_top;
.check-box {
float: left;
&_text {
margin-left: 3px;
margin-right: 30px;
vertical-align: middle;
font-size: 16px;
}
}
& p {
font-size: 16px;
color: #808080;
span {
font-size: 18px;
color: $treeCheckColor;
}
}
& button {
width: 80px;
height: 40px;
color: #fff;
background: $treeCheckColor;
border-radius: 5px;
outline: none;
}
}
}
.search-list {
background: #f7f7f7;
height: calc(100% - 46px);
margin-top:46px;
box-sizing: border-box;
overflow-y: auto;
.search-head {
height: 46px;
line-height: 46px;
padding: 0 10px 0 15px;
width: 100%;
position: fixed;
box-sizing: border-box;
background: #f7f7f7;
input {
background: #efefef;
-webkit-appearance: none;
outline: none;
width: calc(100% - 70px);
height: 30px;
line-height: 30px;
padding: 0 15px;
border-radius: 28px;
font-size: 14px;
&::-webkit-input-placeholder {
font-size: 14px;
}
}
span {
margin-left: 5px;
color: #272829;
font-size: 14px;
}
.close-icon {
width: 18px;
line-height: 18px;
height: 18px;
position: absolute;
right: 60px;
top: 15px;
}
}
.search-item {
background: #fff;
height: 46px;
line-height: 46px;
padding-left: 12px;
a {
position: relative;
display: block;
width: 100%;
color: #333;
outline: none;
@extend .my-border_bottom;
}
&_text {
margin-left: 2px;
vertical-align: middle;
}
&_group {
float: right;
color: #808080;
font-size: 12px;
margin-right: 15px;
}
}
}
.user-popup {
position: fixed;
background: #fff;
top: 100%;
left: 0;
transform: translateY(0);
backface-visibility: hidden;
transition: 0.2s ease-in;
width: 100%;
height: 75%;
z-index: 105;
border-radius: 8px 8px 0 0;
&_show {
transform: translateY(-100%);
}
&_hide {
transform: translateY(0%);
}
&_header {
position: relative;
overflow: hidden;
@extend .my-border_bottom;
h4 {
font-size: 18px;
margin: 12px 0 5px;
color: #272829;
text-align: center;
}
h6 {
color: #595959;
font-size: 13px;
margin: 5px 0 15px;
text-align: center;
}
& .close-btn {
position: absolute;
padding: 5px;
right: 3%;
top: 10px;
}
}
&_body {
overflow-y: auto;
height: calc(100% - 70px);
padding: 12px 12px;
box-sizing: border-box;
}
&_ul {
@extend .fix;
li {
background: #f4f8ff;
float: left;
margin: 4px;
width: calc(33.333% - 8px);
border-radius: 4px;
padding: 8px;
box-sizing: border-box;
h4,
h6 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
h4 {
font-size: 16px;
margin: 6px 0;
color: #272829;
text-align: center;
}
h6 {
color: #595959;
font-size: 12px;
height: 16px;
margin: 6px 0;
text-align: center;
}
}
}
}
.search-box {
position: absolute;
right: 10px;
width: 20px;
height: 20px;
box-sizing: border-box;
padding: 2px;
top: 2px;
background: #fff;
}
.bg-arrow_grey {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABE0lEQVRYR+3VMQ6CMBQG4PdImPUIHsEjeBRZCJ30KDqVsNSjeASPwBF0Y6KmiSQM0NfXdiDmsZDQ0n79Wx4IG7twYx4QELUjkpAkRCVAtcsZ+p+EjDH7YRj2SqmeWhWnveu6Y1mWfVVV76X3Vs9Q27Y3RLyM41gppR6cSdf6aq3PRVEYa+29aZorC+RWYq19AsAuB2rCAMAHEU91Xb9YINc5FyoU4+YkP/tUFAcTBEpJiosJBsWgYjAsEAcVi2GDQlApmCiQD5WKiQYtodwzV/SoOkMVWPKz9w0wLwm/ft6iR2GSEpoGn6HAV4FDMFlAbhCt9cHdc/yIk7YsdNWcfgKi0pKEJCEqAapdzpAkRCVAtX8BqXjGJWAVZBoAAAAASUVORK5CYII=");
background-repeat: no-repeat;
display: inline-block;
width: 64px;
height: 64px;
zoom: 0.3;
background-size: contain;
}
.conta-wrap {
position: relative;
.tree-item {
padding: 12px;
color: #333;
position: relative;
a {
color: #333;
display: inline-block;
outline: none;
text-decoration: none;
}
input {
display: none;
}
label {
position: relative;
vertical-align: middle;
margin-left: 2px;
}
&_group {
background: $treeCheckGroupColor;
@extend .my-border_bottom;
}
&_arrow {
&::before {
@extend .bg-arrow_grey;
content: "";
display: block;
position: absolute;
right: 2%;
top: 0;
bottom: 0;
margin: auto 0;
transition: 300ms cubic-bezier(0.47, 0, 0.745, 0.715);
}
}
&_open,
&_close {
@extend .tree-item_arrow;
}
&_close::before {
transform: rotate(180deg);
}
}
}
.my-checkbox-core {
border-radius: 3px;
width: 20px;
height: 20px;
display: inline-block;
background-color: #fff;
border: 1px solid $treeCheckBorderColor;
position: relative;
vertical-align: middle;
}
.my-checkbox-core.active {
background-color: $treeCheckColor;
border-color: $treeCheckColor;
&::after {
border: 2px solid transparent;
border-left: 0;
border-top: 0;
content: " ";
top: 3px;
left: 7px;
position: absolute;
width: 4px;
height: 8px;
-webkit-transform: rotate(45deg) scale(0);
transform: rotate(45deg) scale(0);
transition: -webkit-transform 0.2s;
-webkit-transition: -webkit-transform 0.2s;
transition: transform 0.2s;
transition: transform 0.2s, -webkit-transform 0.2s;
border-color: #fff;
-webkit-transform: rotate(45deg) scale(1);
transform: rotate(45deg) scale(1);
}
}
.my-border_bottom::after {
content: " ";
position: absolute;
color: #d9d9d9;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
border-bottom: 1px solid #d9d9d9;
height: 1px;
left: 0;
bottom: 0;
right: 0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
transition: 0;
}
.my-border_top::after {
content: " ";
position: absolute;
color: #d9d9d9;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
border-top: 1px solid #d9d9d9;
width: 100%;
height: 1px;
left: 0;
top: 0;
right: 0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
transition: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 300ms ease-in-out;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
+505
View File
@@ -0,0 +1,505 @@
<template>
<div>
<div :id="domId" :class="['tree-popup', isPopupShow ? 'tree-popup_show' : 'tree-popup_hide']">
<div class="tree-popup_header">
<div class="my-tab_btn fix">
<div
:class="{
'item-tab_btn': true,
active: firstTabIndex == tindex,
}"
@click="tabBtnChange(tindex)"
v-for="(titem, tindex) in firstTabData"
>
{{ titem }}
</div>
<div class="my-tab_slider" v-if="firstTabData.length > 0" :style="tabSliderStyle">
<div class="indicator"></div>
</div>
</div>
<div class="my-tab_card fix" v-if="secondTabData.length > 1">
<div class="item-wrap">
<div
:class="{
'item-tab_card': true,
active: secondTabIndex == tindex,
}"
@click="tabCardChange(tindex)"
v-for="(titem, tindex) in secondTabData"
>
{{ titem }}
</div>
</div>
<div class="search-box" @click="isSearchShow = !isSearchShow">
<svg t="1626398915347" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4152" width="16" height="16">
<path
d="M921.3 874L738.1 690.8c51.3-62.6 82.1-142.5 82.1-229.6 0-200.1-162.8-363-363-363-200.1 0-363 162.8-363 363s162.8 363 363 363c87 0 167-30.8 229.6-82.1L870 925.3c7.1 7.1 16.4 10.6 25.7 10.6s18.6-3.5 25.7-10.6c14.1-14.2 14.1-37.2-0.1-51.3zM166.8 461.2c0-160.1 130.3-290.4 290.4-290.4s290.4 130.3 290.4 290.4-130.3 290.4-290.4 290.4-290.4-130.3-290.4-290.4z"
p-id="4153"
fill="#808080"
></path>
</svg>
</div>
</div>
<div class="close-btn" @click="popupColse">
<svg t="1626397122082" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3133" width="14" height="14">
<path d="M877.216 491.808" p-id="3134" fill="#595959"></path>
<path
d="M575.328 510.496 946.784 140.672c17.568-17.504 17.664-45.824 0.192-63.424-17.504-17.632-45.792-17.664-63.36-0.192L512.032 446.944 143.712 77.216C126.304 59.712 97.92 59.648 80.384 77.12 62.848 94.624 62.816 123.008 80.288 140.576l368.224 369.632L77.216 879.808c-17.568 17.504-17.664 45.824-0.192 63.424 8.736 8.8 20.256 13.216 31.776 13.216 11.424 0 22.848-4.352 31.584-13.056l371.36-369.696 371.68 373.088C892.192 955.616 903.68 960 915.168 960c11.456 0 22.912-4.384 31.648-13.088 17.504-17.504 17.568-45.824 0.096-63.392L575.328 510.496 575.328 510.496zM575.328 510.496"
p-id="3135"
fill="#595959"
></path>
</svg>
</div>
</div>
<div class="tree-popup_body">
<tree v-if="!isSearchShow" :treeData="currentTree" :userids="tempUserIds"></tree>
<div v-else class="search-list">
<div class="search-head" :style="searchHeadStyle">
<input type="text" placeholder="请输入关键字" v-model="searchText" />
<div class="close-icon" v-show="searchText" @click="searchText = null">
<svg t="1626484787749" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2043" width="18" height="18">
<path
d="M512 51.2C258.048 51.2 51.2 258.048 51.2 512s206.848 460.8 460.8 460.8 460.8-206.848 460.8-460.8S765.952 51.2 512 51.2z m181.248 612.352c8.192 8.192 8.192 20.48 0 28.672-4.096 4.096-9.216 6.144-14.336 6.144s-10.24-2.048-14.336-6.144L512 540.672 360.448 693.248c-4.096 4.096-9.216 6.144-14.336 6.144s-10.24-2.048-14.336-6.144c-8.192-8.192-8.192-20.48 0-28.672L483.328 512 330.752 360.448c-8.192-8.192-8.192-20.48 0-28.672s20.48-8.192 28.672 0L512 483.328l151.552-151.552c8.192-8.192 20.48-8.192 28.672 0s8.192 20.48 0 28.672L540.672 512l152.576 151.552z"
p-id="2044"
fill="#cccccc"
></path>
</svg>
</div>
<span @click="isSearchShow = false">取消</span>
</div>
<div class="search-item" v-for="item in searchList">
<a href="javascript:;" @click="checkTreeById(item.id, !item.isCheck)">
<span :class="['my-checkbox-core', item.isCheck ? 'active' : '']"></span>
<span class="search-item_text">{{ item.name }}</span>
<span class="search-item_group">{{ item.group }}</span>
</a>
</div>
</div>
</div>
<div class="tree-popup_footer fix">
<div class="check-box align-middle" v-show="!isSearchShow">
<label @click="saveAllCheck">
<span :class="['my-checkbox-core', isAllTreeCheck ? 'active' : '']"></span>
<span class="check-box_text">全选</span>
</label>
</div>
<p class="fl align-middle" @click="isUserModalShow = true">
已选人数:
<span>{{ tempUserIds.length }}</span>
</p>
<button v-show="isSearchShow" class="fr align-middle" @click="isSearchShow = false">确认</button>
<button v-show="!isSearchShow" class="fr align-middle" @click="submit">提交</button>
</div>
</div>
<div v-if="isModalShow" class="my-modal" @click="popupColse"></div>
<div :class="['user-popup', isUserModalShow ? 'user-popup_show' : 'user-popup_hide']">
<div class="user-popup_header">
<h4>已选人员</h4>
<h6>共{{ tempUserToNames.length }}人</h6>
<div class="close-btn" @click="isUserModalShow = false">
<svg t="1626397122082" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3133" width="14" height="14">
<path d="M877.216 491.808" p-id="3134" fill="#595959"></path>
<path
d="M575.328 510.496 946.784 140.672c17.568-17.504 17.664-45.824 0.192-63.424-17.504-17.632-45.792-17.664-63.36-0.192L512.032 446.944 143.712 77.216C126.304 59.712 97.92 59.648 80.384 77.12 62.848 94.624 62.816 123.008 80.288 140.576l368.224 369.632L77.216 879.808c-17.568 17.504-17.664 45.824-0.192 63.424 8.736 8.8 20.256 13.216 31.776 13.216 11.424 0 22.848-4.352 31.584-13.056l371.36-369.696 371.68 373.088C892.192 955.616 903.68 960 915.168 960c11.456 0 22.912-4.384 31.648-13.088 17.504-17.504 17.568-45.824 0.096-63.392L575.328 510.496 575.328 510.496zM575.328 510.496"
p-id="3135"
fill="#595959"
></path>
</svg>
</div>
</div>
<div class="user-popup_body">
<ul class="user-popup_ul">
<li v-for="item in tempUserToNames">
<h4>{{ item.name }}</h4>
<h6>{{ item.group }}</h6>
</li>
</ul>
</div>
</div>
<div v-if="isUserModalShow" style="z-index: 103" class="my-modal" @click="isUserModalShow = false"></div>
</div>
</template>
<script>
// import { Toast } from "vant";
require("./index.scss");
import tree from "./tree.vue";
export default {
components: { tree },
props: {
treeData: {
type: Array,
default: [],
},
userids: {
type: Array,
default: [],
},
},
model: {
prop: "userids",
event: "change",
},
provide() {
return {
checkTreeById: this.checkTreeById,
};
},
data() {
return {
tempUserIds: [],
firstTabIndex: 0,
secondTabIndex: 0,
tabData: [],
currentTree: [],
tabSliderStyle: {},
searchHeadStyle: {},
isPopupShow: false,
isModalShow: false,
isUserModalShow: false,
isAllTreeCheck: false,
isSearchShow: false,
searchText: null,
cacheMap: new Map(),
duplicateIds: [],
domId: "dom_" + new Date().getTime(),
};
},
computed: {
firstTabData() {
if (this.treeData.length > 0) return this.treeData.map(x => x.name);
else return [];
},
secondTabData() {
if (this.treeData.length == 0) return [];
return this.treeData[this.firstTabIndex].data.map(x => x.name);
},
currentTreeFlatten() {
const flatten = (data, pid, pname) => {
return data.reduce((arr, { id, name, children = [], type, isCheck }) => arr.concat([{ id, name, pid, type, isCheck, group: pname }], flatten(children, id, name)), []);
};
if (!this.currentTree) return [];
var newArr = flatten(this.currentTree, 0, "");
return newArr;
},
searchList() {
const _this = this;
let flattenArr = _this.currentTreeFlatten;
flattenArr = flattenArr.filter(x => x.type == 2);
if (!_this.searchText) return flattenArr;
else return flattenArr.filter(x => x.name.indexOf(_this.searchText) > -1);
},
tempUserToNames() {
const _this = this;
let flattenArr = _this.currentTreeFlatten;
var arr = flattenArr.filter(x => _this.tempUserIds.includes(x.id));
return arr;
},
},
created() {
this.tabData = this.treeData.map(x => x.name);
},
mounted() {},
methods: {
show() {
let timer = null;
if (this.firstTabData.length == 0) {
Toast.loading({ forbidClick: true });
timer = setTimeout(() => {
this.show();
}, 100);
return;
} else {
Toast.clear();
if (!!timer) {
clearTimeout(timer);
}
}
this.isModalShow = this.isPopupShow = true;
this.tabBtnChange(0);
var parentId = "#" + this.domId;
setTimeout(() => {
let treeH = document.querySelector(parentId + ".tree-popup").offsetHeight,
headerH = document.querySelector(parentId + " .tree-popup_header").offsetHeight;
document.querySelector(parentId + " .tree-popup_body").style.height = treeH - headerH - 60 + "px";
this.searchHeadStyle = { top: headerH + "px" };
}, 0);
},
popupColse() {
this.isPopupShow = false;
this.firstTabIndex = 0;
this.secondTabIndex = 0;
setTimeout(() => {
this.isModalShow = false;
}, 250);
},
tabBtnChange(index) {
this.firstTabIndex = index;
this.getTabSliderStyle(index);
this.changeEmpty();
this.tabCardChange(0);
},
tabCardChange(index) {
const _this = this;
var wrap = document.querySelector("#" + this.domId + " .item-wrap");
var tabs = document.querySelectorAll("#" + this.domId + " .item-tab_card");
let currentItem = tabs[index];
var pageX = document.documentElement.clientWidth,
blockW = currentItem.scrollWidth,
moveX = 0;
for (let i = 0; i < tabs.length; i++) {
const element = tabs[i];
if (i > index) break;
moveX += element.scrollWidth;
}
var left = moveX - pageX / 2 + blockW / 2;
wrap.scrollLeft = left;
this.secondTabIndex = index;
this.isAllTreeCheck = false;
this.isSearchShow = false;
this.initTreeData();
},
initTreeData() {
const _this = this;
const checkTree = tree => {
_this.currentTree = tree;
_this.tempUserIds = JSON.parse(JSON.stringify(_this.userids));
let duplArr = _this.duplicateIds.map(x => x.id);
_this.tempUserIds.forEach(element => {
let id = element;
if (duplArr.length > 0 && duplArr.includes(element)) {
let parent = _this.duplicateIds.find(x => x.id == element);
//处理重复id逻辑,仅选择第一个
parent && (id += "," + parent.pid[0]);
}
_this.checkTreeById(id, true);
});
};
let first = _this.firstTabIndex,
second = _this.secondTabIndex,
cacheMap = _this.cacheMap,
tData = _this.treeData;
var tree = tData[first].data[second].data;
var cacheKey = first + "," + second;
if (!!tree && typeof tree === "function") {
var cacheData = cacheMap.get(cacheKey);
if (!cacheData) {
tree().then(data => {
_this.duplicateIds = _this.dupTreeIds(data);
var jtree = JSON.parse(JSON.stringify(data));
cacheMap.set(cacheKey, jtree);
var ftree = _this.formatTree(jtree);
checkTree(ftree);
});
} else {
var ftree = _this.formatTree(cacheData);
checkTree(ftree);
}
} else {
_this.duplicateIds = _this.dupTreeIds(tree);
var jtree = JSON.parse(JSON.stringify(tree));
var ftree = _this.formatTree(jtree);
checkTree(ftree);
}
},
getTabSliderStyle(index) {
var dom = document.querySelectorAll(".item-tab_btn")[index];
if (!dom) return;
this.tabSliderStyle = {
width: dom.offsetWidth + "px",
transform: `translateX(${dom.offsetLeft}px)`,
};
},
saveAllCheck() {
const _this = this;
_this.isAllTreeCheck = !_this.isAllTreeCheck;
const recursionCheckAll = (tree, checked) => {
tree.forEach(element => {
element.isCheck = checked;
if (typeof element.children !== "undefined" && element.children.length > 0) recursionCheckAll(element.children, checked);
});
};
recursionCheckAll(_this.currentTree, _this.isAllTreeCheck);
_this.getTreeCheckIds();
},
changeEmpty() {
this.tempUserIds = [];
},
submit() {
const _this = this;
let objArr = [],
idsArr = this.tempUserIds.map(x => _this.getSplitId(x));
idsArr = Array.from(new Set(idsArr));
this.$emit("change", idsArr);
function delKey(obj, ...args) {
args.forEach(v => delete obj[v]);
return obj;
}
idsArr.forEach(element => {
let findObj = _this.currentTreeFlatten.find(x => _this.getSplitId(x.id) == element);
delKey(findObj, "id", "isCheck", "pid", "type");
objArr.push({ ...findObj, id: element });
});
this.$emit("on-submit", objArr);
this.popupColse();
},
getSplitId(strId) {
if (typeof strId === "string" && strId.indexOf(",") != -1) return parseInt(strId.split(",")[0]);
else return strId;
},
//#region treeOpera
formatTree(tree, level = 0) {
const _this = this;
tree.forEach(element => {
var findObj = _this.duplicateIds.map(x => x.id).find(x => x == element.id);
if (!!findObj) element.id = element.id + "," + element.pid; //重复id加上组id以免重复
element.show = level == 0;
element.isCheck = false;
element.type = 1;
if (typeof element.children !== "undefined" && element.children.length > 0) element.children = _this.formatTree(element.children, level + 1);
else {
// element.id = element.id + "," + element.pid; //防止用户在多个组重复id,加上组id以免重复
element.type = 2;
}
});
return tree;
},
flattenTree(tree) {
const flatten = (data, pid) => {
return data.reduce((arr, { id, name, children = [], type }) => arr.concat([{ id, name, type, pid }], flatten(children, id)), []);
};
return flatten(tree, 0);
},
dupTreeIds(tree) {
//一个人可能在多个组,取出这些id
const flatten = (data, pid) => {
return data.reduce((arr, { id, name, children = [] }) => arr.concat([{ id, name, pid }], flatten(children, id)), []);
};
// var flattenArr = flatten(tree, 0);
var flattenArr = this.flattenTree(tree);
var ctnArr = flattenArr.reduce((arr, { id, pid }) => {
var findObj = arr.find(x => x.id == id);
if (!!findObj) {
findObj.count += 1;
findObj.pid.push(pid);
} else arr.push({ id, count: 1, pid: [pid] });
return arr;
}, []);
// var dupArr = ctnArr.filter((x) => x.count > 1).map((x) => x.id);
var dupArr = ctnArr.filter(x => x.count > 1);
return dupArr;
},
findTreePids(id, tree) {
const _this = this;
var flattenArr = _this.flattenTree(tree);
var arr = [];
while (id > 0) {
var findObj = flattenArr.find(x => x.id == id);
if (findObj) {
id = findObj.pid;
arr.push(id);
} else {
id = -1;
}
}
return arr;
},
findTreeChildIds(id, tree) {
const _this = this;
const recursionChildIds = tree => {
let arrIds = [];
for (const key in tree) {
const element = tree[key];
arrIds.push(element.id);
if (typeof element.children !== "undefined" && element.children) {
var childIds = recursionChildIds(element.children);
arrIds = [...arrIds, ...childIds];
}
}
return arrIds;
};
let childTree = [];
if (id == 0) childTree = tree;
else {
var ctree = _this.findTreeByid(id, tree);
if (ctree && typeof ctree.children !== "undefined" && ctree.children) childTree = ctree.children;
else return [];
}
var ids = recursionChildIds(childTree);
return ids;
},
findTreeByid(id, tree) {
const _this = this;
var findTree;
for (const key in tree) {
const element = tree[key];
if (element.id == id) findTree = element;
if (typeof element.children !== "undefined" && element.children && !findTree) {
findTree = _this.findTreeByid(id, element.children);
}
}
return findTree;
},
findTreeByPid(id, tree) {
const _this = this;
var findId;
for (const key in tree) {
const element = tree[key];
if (element.id == id) {
findId = element.pid;
}
if (typeof element.children !== "undefined" && element.children && !findId) {
findId = _this.findTreeByPid(id, element.children);
}
}
return findId;
},
checkTreeById(id, checked) {
const _this = this;
var pids = this.findTreePids(id, this.currentTree);
var cids = this.findTreeChildIds(id, this.currentTree);
var idArr = [...cids, id];
checked && (idArr = [...pids, ...idArr]);
idArr.forEach(element => {
var t = this.findTreeByid(element, this.currentTree);
t && (t.isCheck = checked);
});
if (!checked) {
pids.forEach(element => {
var t = _this.findTreeByid(element, this.currentTree);
if (!t || typeof t.children === "undefined") return;
var findIndex = t.children.findIndex(x => x.isCheck && x.id != id);
findIndex === -1 && (t.isCheck = false);
});
}
this.getTreeCheckIds();
},
getTreeCheckIds() {
const _this = this;
var isEndWithType = true; //是否只返回最末端的结点(type=2)
const recursionCheckIds = tree => {
let arrIds = [];
for (const key in tree) {
const element = tree[key];
if (element.isCheck && (isEndWithType ? element.type == 2 : true)) arrIds.push(element.id);
if (typeof element.children !== "undefined" && element.children) {
var childIds = recursionCheckIds(element.children);
arrIds = [...arrIds, ...childIds];
}
}
return arrIds;
};
var t = recursionCheckIds(_this.currentTree);
this.tempUserIds = t;
},
//#endregion
},
};
</script>
+80
View File
@@ -0,0 +1,80 @@
<template>
<ul class="conta-wrap">
<li v-for="(item, index) in treeData">
<div
:class="['tree-item', getItemClass(item)]"
:style="{ 'padding-left': 30 * level + 12 + 'px' }"
@click="itemClick(item)"
>
<a
href="javascript:;"
style="dispaly: block"
@click.stop="itemCheck(item, $event)"
>
<input type="checkbox" v-model="item.isCheck" />
<span
:class="[
'my-checkbox-core',
item.isCheck ? 'active' : '',
]"
></span>
<label>{{ item.name }}</label>
</a>
</div>
<transition name="fade">
<tree
v-if="item.show"
:userids="userids"
:treeData="item.children"
:level="level + 1"
></tree>
</transition>
</li>
</ul>
</template>
<script>
require("./index.scss");
export default {
name: "tree",
inject: ["checkTreeById"],
props: {
level: {
type: Number,
default: 0,
},
treeData: {
type: Array,
default: [],
},
userids: {
type: Array,
default: [],
},
},
methods: {
getItemClass(item) {
if (item.type == 2) return;
let className = " tree-item_" + (item.show ? "open" : "close");
className += this.level === 0 ? " tree-item_group " : "";
return className;
},
itemCheck(item, e) {
if (
typeof e !== "undefined" &&
e.target.tagName.toLowerCase() === "input"
)
return;
this.checkTreeById(item.id, !item.isCheck);
},
itemClick(item) {
if (item.type == 2) {
// this.itemCheck(item);
this.checkTreeById(item.id, !item.isCheck);
} else if (item.type == 1) {
item.show = !item.show;
}
},
},
};
</script>
+54
View File
@@ -0,0 +1,54 @@
<template>
<Modal v-model="modal" :title="name" @on-visible-change="showChange" width="70%" footer-hide>
<Button class="js_copy_code" data-clipboard-action="copy" :data-clipboard-text="currentHtml">复制代码</Button>
<div v-highlight>
<pre v-show="isLoad">
<code id="test" class="html"></code>
</pre>
</div>
</Modal>
</template>
<script>
import { load } from "../../assets/js/code";
export default {
props: {
name: String,
url: String,
type: String,
},
data() {
return {
modal: false,
isLoad: false,
currentHtml: null,
clipboard: null,
};
},
async mounted() {
const _this = this;
this.modal = true;
this.isLoad = false;
let { html, encode } = await load(_this.url);
$("#test").attr("class", _this.type).html(encode);
this.isLoad = true;
this.currentHtml = html;
this.clipboard = new ClipboardJS(".js_copy_code");
this.clipboard.on("success", function (e) {
_this.$Message.success("copy success!");
e.clearSelection();
});
},
methods: {
showChange(state) {
if (!state) this.destroy();
},
destroy() {
this.clipboard.destroy();
setTimeout(() => {
document.body.removeChild(this.$el);
}, 300);
},
},
};
</script>
+24
View File
@@ -0,0 +1,24 @@
import Vue from "vue";
import codemodal from "./code_modal.vue";
import { Message } from "view-design";
export default props => {
// 创建实例
const vm = new Vue({
render(h) {
// h为createElement,返回VNode虚拟Node
return h(codemodal, { props });
}
}).$mount();
// 手动挂载实例到body
document.body.appendChild(vm.$el);
const comp = vm.$children[0];
// 销毁方法
// comp.remove = function() {
// document.body.removeChild(vm.$el);
// vm.$destroy();
// };
// 返回组件
return comp;
};
+240
View File
@@ -0,0 +1,240 @@
<template>
<div>
<!-- 富文本编辑器 -->
<quill-editor v-model="content"
ref="myTextEditor"
:options="editorOption"
@change="onChange"
>
<div id="toolbar" slot="toolbar">
<span class="ql-formats"><button type="button" class="ql-bold"></button></span>
<span class="ql-formats"><button type="button" class="ql-italic"></button></span>
<span class="ql-formats"><button type="button" class="ql-underline"></button></span>
<span class="ql-formats"><button type="button" class="ql-strike"></button></span>
<!-- 有序和无序 -->
<span class="ql-formats"><button type="button" class="ql-list" value="ordered"></button></span>
<span class="ql-formats"><button type="button" class="ql-list" value="bullet"></button></span>
<!-- 缩进 -->
<span class="ql-formats"><button type="button" class="ql-indent" value="-1"></button></span>
<span class="ql-formats"><button type="button" class="ql-indent" value="+1"></button></span>
<span class="ql-formats"> <button type="button" class="ql-direction" value="rtl"></button></span>
<!-- 自定义下拉 -->
<span class="ql-formats">
<select class="ql-size" id="ql-size">
<option value="small">小号</option>
<option value="">正常</option>
<option value="large">大号</option>
<option value="huge">特大</option>
</select>
<!-- <i-select size="small" @on-change="mychange">
<i-option value="small">小号</i-option>
<i-option value="large">正常</i-option>
<i-option value="huge">特大</i-option>
</i-select> -->
</span>
<span class="ql-formats">
<select class="ql-header">
<option value="1">标题1</option>
<option value="2">标题2</option>
<option value="3">标题3</option>
<option value="4">标题4</option>
<option value="5">标题5</option>
<option value="6">标题6</option>
<option selected="selected">正文</option>
</select>
</span>
<span class="ql-formats">
<select class="ql-color" v-model="fontColor">
<option v-for="item in colorArry" :key="item" :value="'#'+item"></option>
</select>
</span>
<span class="ql-formats">
<select class="ql-background" v-model="bgColor">
<option v-for="item in colorArry" :key="item" :value="'#'+item"></option>
</select>
</span>
<span class="ql-formats">
<select class="ql-font">
<option selected="selected"></option>
<option value="serif"></option>
<option value="monospace"></option>
</select>
</span>
<span class="ql-formats">
<select class="ql-align">
<option selected="selected"></option>
<option value="center"></option>
<option value="right"></option>
<option value="justify"></option>
</select>
</span>
<!-- <span class="ql-formats">
<button type="button" @click="isLink=true" class="ql-link"></button>
</span> -->
<span class="ql-formats">
<button type="button" class="my-formats-image">
<Upload
:action="formItem.uploadUrl"
:on-success="handleSuccess"
:data="uploadData"
accept="image/gif,image/jpeg,image/jpg,image/png">
<Icon type="image" size="16"></Icon>
</Upload>
</button>
</span>
</div>
</quill-editor>
<!-- 添加链接 -->
<Modal v-model="isLink" title="添加链接" width="450px">
<i-form :label-width="85" class="myform" ref="formItem.link" :model="formItem.link">
<Form-Item label="链接地址:" prop="url">
<i-input v-model="formItem.link.url" placeholder="请输入链接地址"></i-input>
</Form-Item>
<Form-Item label="链接名称:" prop="tit">
<i-input v-model="formItem.link.tit" placeholder="请输入链接名称"></i-input>
</Form-Item>
</i-form>
<div slot="footer">
<i-Button type="primary" @click="addLinkSave()">提交</i-Button>
<i-Button type="ghost" @click="myCancel('isLink','formItem.link')" style="margin-left: 8px">取消</i-Button>
</div>
</Modal>
</div>
</template>
<script>
// import {quillEditor} from 'vue-quill-editor'
export default {
// components: {quillEditor},
props: {
// 提示内容
placeholder:{
type: String
},
/*编辑器内容*/
value: {
type: String
},
/*上传图片的地址*/
uploadUrl: {
type: String
},
readonly:Boolean,
},
data() {
return {
content:this.value,
size:'',
fontColor:"000000",
bgColor:"ffffff",
colorArry:['000000','e60000','ff9900','ffff00','008a00','0066cc','9933ff','ffffff','facccc','ffebcc','ffffcc','cce8cc','cce0f5','ebd6ff','bbbbbb','f06666','ffc266','ffff66','66b966','66a3e0','c285ff','888888','a10000','b26b00','b2b200','006100','0047b2','6b24b2','444444','5c0000','663d00','666600','003700','002966','3d1466'],
editorOption: {
modules: {
toolbar: '#toolbar',
},
placeholder:this.placeholder || '请输入内容……',
readOnly: this.readonly,
},
showCrop:false,
isLink:false,
isImage:false,
formItem:{
link:{
url:'',
tit:'',
},
uploadUrl:this.uploadUrl || '',
},
uploadData:{
AppType:window.location.pathname.split('/')[1],
},
}
},
methods: {
onChange(){
this.$emit('input', this.content)
},
myCancel(name, resetName) {
var vm = this;
vm[name] = false;
if (resetName) {
console.log(vm.$refs[resetName])
this.$refs[resetName].resetFields();
}
},
addLinkSave(){
var _that=this;
_that.isLink=false;
_that.editor.focus();
_that.editor.insertText(_that.editor.getSelection().index, ' ', '', true);
_that.editor.insertText(_that.editor.getSelection().index-1, _that.formItem.link.tit+' ', 'link','http://'+ _that.formItem.link.url);
},
handleSuccess(res, file){
this.editor.focus();
console.log(res);
var url=res.host+res.fileurl;
this.editor.insertEmbed(this.editor.getSelection().index, 'image', url);
},
// onFocus(){
// $(".ql-editor img").removeClass("active");
// },
},
computed: {
editor() {
return this.$refs.myTextEditor.quill;
}
},
mounted(){
var _that=this;
// $(".ql-editor img").on('click',function(){
// _that.editor.blur();
// $(this).addClass("active");
// })
},
watch: {
'value'(newVal, oldVal) {
if (this.editor) {
if (newVal !== this.content) {
this.content = newVal
}
}
},
}
}
</script>
<style lang="scss">
.ql-tooltip{
display: none !important;
}
.my-formats-image{
outline: none;
ul.ivu-upload-list{
display: none;
}
}
.ql-container .ql-editor {
min-height: 20em;
padding-bottom: 1em;
max-height: 25em;
}
.ql-editor{
position: relative;
img{
position: relative;
}
img::after {
content:'123123';
display: block;
width: 12px;
height: 12px;
position: absolute;
top:0;right: 0;
background: red;
}
}
</style>
+64
View File
@@ -0,0 +1,64 @@
<template>
<Modal v-model="modal" @on-visible-change="showChange" :title="title || '提示'">
<Alert type="warning" class="mb-1" show-icon>
<Icon type="ios-bulb-outline" :class="{ 'mt-5': !info }" slot="icon" :size="28"></Icon>
<div v-if="msg" :class="['message-box', info ? '' : 'mt-10 mb-10 ml-10']">{{ msg }}</div>
<span v-if="info" slot="desc">{{ info }}</span>
</Alert>
<div slot="footer">
<Button type="primary" @click="ok">确定</Button>
<Button @click="cancle">取消</Button>
</div>
</Modal>
</template>
<script>
export default {
props: {
title: String,
msg: String,
info: String,
onOk: Function,
onCancel: Function,
},
data() {
return {
modal: false,
};
},
mounted() {
this.$nextTick(() => {
this.modal = true;
});
},
methods: {
ok() {
this.modal = false;
typeof this.onOk === "function" && this.onOk();
},
cancle() {
this.modal = false;
typeof this.onCancel === "function" && this.onCancel();
},
showChange(state) {
if (!state) this.destroy();
},
destroy() {
setTimeout(() => {
document.body.removeChild(this.$el);
}, 300);
},
},
};
</script>
<style scoped>
.my-confirwm {
margin-bottom: 0;
}
.message-box {
font-size: 16px;
color: #17233d;
display: block;
}
</style>
+23
View File
@@ -0,0 +1,23 @@
import Vue from "vue";
import confirm from "./confirm.vue";
export default props => {
// 创建实例
const vm = new Vue({
render(h) {
// h为createElement,返回VNode虚拟Node
return h(confirm, { props });
}
}).$mount();
// 手动挂载实例到body
document.body.appendChild(vm.$el);
const comp = vm.$children[0];
// 销毁方法
// comp.remove = function() {
// document.body.removeChild(vm.$el);
// vm.$destroy();
// };
// 返回组件
return comp;
};
+95
View File
@@ -0,0 +1,95 @@
<template>
<div :id="domId" :ref="domId" class="ztree"></div>
</template>
<script>
require("@public/libs/ztree/zTreeStyle.css");
import "@public/libs/ztree/jquery.ztree.all.min.js";
export default {
props: {
treeData: {
type: Object,
},
setting: {
type: Object,
},
},
data() {
return {
domId: "",
treeSetting: {
view: {
dblClickExpand: false,
showIcon: false,
},
check: {
enable: false,
},
callback: {},
},
};
},
created() {
this.$watch("treeData", (val) => {
if (val) {
this.$nextTick(() => {
this.initTree(this.treeData, this.domId);
});
}
});
},
mounted() {
this.domId = this.uuid();
},
methods: {
uuid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
},
initTree(_data, _domId) {
const _this = this;
_this.treeSetting.callback.onClick = (
event,
treeId,
treeNode,
clickFlag
) => {
// console.log(event, treeId, treeNode, clickFlag);
_this.$emit("on-tree-click", treeNode);
};
var zNodes = _this.openFistNode(_data);
$.fn.zTree.init($("#" + _domId), _this.treeSetting, zNodes);
var treeObj = $.fn.zTree.getZTreeObj(_domId);
var nodes = treeObj.getNodes();
if (nodes && nodes.length > 0) {
setTimeout(function () {
$("#" + nodes[0].tId + "_a").click();
}, 0);
}
},
openFistNode(_zNodes) {
//展开一级节点
if (_zNodes && _zNodes.length > 0) {
for (var key in _zNodes) {
if (_zNodes.hasOwnProperty(key)) {
var element = _zNodes[key];
element.open = true;
}
}
} else {
_zNodes.open = true;
}
return _zNodes;
},
},
};
</script>
<style>
</style>
+159
View File
@@ -0,0 +1,159 @@
<template>
<div :id="domId" :ref="domId" class="ztree"></div>
</template>
<script>
require("@public/libs/ztree/zTreeStyle.css");
import "@public/libs/ztree/jquery.ztree.all.min.js";
export default {
props: {
treeData: {
type: Object,
default: () => ({})
},
setting: {
type: Object,
default: () => ({})
},
// 外部传入需要默认选中的节点id,不传则选中第一个一级节点
selectId: {
type: [Number, String],
default: null
}
},
data() {
return {
domId: "",
treeSetting: {
view: {
dblClickExpand: false,
showIcon: false,
},
check: {
enable: false,
},
callback: {},
},
};
},
watch: {
treeData(val) {
if (val && val.length) {
this.$nextTick(() => {
this.initTree(this.treeData, this.domId);
});
}
},
// 监听外部传入选中id变化,自动重新选中
selectId() {
this.$nextTick(() => {
this.handleDefaultSelect();
})
}
},
created() {
this.$watch("treeData", (val) => {
if (val) {
this.$nextTick(() => {
this.initTree(this.treeData, this.domId);
});
}
});
},
mounted() {
this.domId = this.uuid();
},
methods: {
getZtreeInstance() {
return $.fn.zTree.getZTreeObj(this.domId);
},
uuid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
},
/**
* 核心方法:根据节点id选中节点,并逐层展开所有父节点
* @param {String|Number} targetId 目标节点id
*/
selectNodeById(targetId) {
const treeObj = $.fn.zTree.getZTreeObj(this.domId);
if (!treeObj || !targetId) return;
const targetNode = treeObj.getNodeByParam('id', targetId);
if (!targetNode) return;
// 高亮选中节点
treeObj.selectNode(targetNode);
// 循环向上展开所有父节点
let currNode = targetNode;
while (currNode) {
treeObj.expandNode(currNode, true, false, false);
currNode = currNode.getParentNode();
}
},
/**
* 默认选中逻辑:有selectId选对应节点,无则选第一个一级节点
*/
handleDefaultSelect() {
const treeObj = $.fn.zTree.getZTreeObj(this.domId);
if (!treeObj) return;
const rootNodes = treeObj.getNodes();
if (!rootNodes || rootNodes.length === 0) return;
if (this.selectId !== null && this.selectId !== undefined) {
// 外部传入id,选中指定节点
this.selectNodeById(this.selectId);
} else {
// 无传入id,默认选中第一个一级节点
const firstRoot = rootNodes[0];
treeObj.selectNode(firstRoot);
treeObj.expandNode(firstRoot, true, false, false);
}
},
initTree(_data, _domId) {
const _this = this;
// 合并自定义setting
_this.treeSetting = Object.assign({}, _this.treeSetting, _this.setting);
// 绑定点击回调
_this.treeSetting.callback.onClick = (event, treeId, treeNode) => {
_this.$emit("on-tree-click", treeNode);
};
// 绑定复选框点击回调
_this.treeSetting.callback.onCheck = (event, treeId, treeNode) => {
const zTreeObj = $.fn.zTree.getZTreeObj(treeId);
// getCheckedNodes(true) 获取所有手动勾选节点,不分层级
const allChecked = zTreeObj.getCheckedNodes(true);
this.$emit('on-tree-Check', allChecked);
};
// 展开所有一级节点
var zNodes = _this.openFistNode(_data);
// 初始化树
$.fn.zTree.init($("#" + _domId), _this.treeSetting, zNodes);
// 执行默认选中逻辑
this.$nextTick(() => {
this.handleDefaultSelect();
})
},
openFistNode(_zNodes) {
if (_zNodes && _zNodes.length > 0) {
for (var i = 0; i < _zNodes.length; i++) {
_zNodes[i].open = true;
}
}
return _zNodes;
}
},
};
</script>
<style>
</style>
+112
View File
@@ -0,0 +1,112 @@
<template>
<div>
<Table
:columns="columns"
:data="listData"
@on-selection-change="selectChange"
class="link-table table-bottom-box"
:height="tableHeight"
no-data-text="暂无数据"
></Table>
<div class="page-panel">
<div class="fl">
<slot :sel-ids="selectList"></slot>
</div>
<Page
class="fr"
:total="total"
:current="dataParmas.page"
:page-size="dataParmas.count"
:page-size-opts="[10, 20, 30, 40]"
@on-page-size-change="pageSizeUpdate"
@on-change="pageUpdate"
placement="top"
show-total
show-sizer
></Page>
</div>
</div>
</template>
<script>
export default {
props: {
tableHeight: {
default: 0,
type: Number,
},
dataUrl: {
default: "",
type: String,
},
dataParmas: {
default: {},
type: Object,
},
dataFilter: {
type: Function,
default: null,
},
columns: {
default: [],
type: Array,
},
autoLoad: {
default: true,
Type: Boolean,
},
},
data() {
return {
listData: [],
selectList: [],
total: 30,
};
},
created() {
this.dataParmas.page = 1;
this.dataParmas.count = parseInt(localStorage.getItem("customPageCount") || 10);
if (this.autoLoad) this.getData();
},
methods: {
getData() {
const _this = this;
let item = {},
{ dataUrl, dataParmas } = this;
// console.log("dataParmas", dataParmas);
for (const key in dataParmas) {
const element = dataParmas[key];
item[key] = element;
}
_this.$api.get(dataUrl, item, resp => {
let rData = resp.data;
if (typeof _this.dataFilter === "function") {
let fData = _this.dataFilter(rData);
_this.listData = JSON.parse(JSON.stringify(fData));
} else _this.listData = rData;
});
},
selectChange(selection) {
this.selectList = selection;
},
pageUpdate(page) {
this.dataParmas.page = page;
this.getData();
},
pageSizeUpdate(size) {
this.dataParmas.page = 1;
this.dataParmas.count = size;
localStorage.setItem("customPageCount", size);
this.getData();
},
reloadData() {
this.getData();
},
},
};
</script>
<style>
</style>
+169
View File
@@ -0,0 +1,169 @@
<template>
<div class="type_set-box">
<div class="type_name">
<i class="iconfont icon-stop fl mr-10" v-if="!isEdit && !active" style="color: #ff5959"></i>
<Input v-if="isEdit" v-model="type.name" ref="type_input" @on-blur="update" @on-enter="update" />
<span v-else class="fl">{{ type.name }}</span>
</div>
<div class="type_action flex">
<div class="action_item" @click="open">
<span v-if="active">停用</span>
<span v-else>启用</span>
</div>
<div class="action_edit" @click="edit">
<span>编辑</span>
</div>
<div class="action_delete" @click="remove">
<span>删除</span>
</div>
</div>
<div class="divider"></div>
<div class="divider2"></div>
</div>
</template>
<script>
export default {
name: "TypeSetBox",
props: {
type: {
name: { type: String, default: "" },
id: { type: String, default: "" },
},
active: { type: Boolean, default: true },
},
data() {
return {
isEdit: false,
eventType: "",
};
},
watch: {
type: {
deep: true,
handler(newVal) {
if (this.isEdit) {
this.isEdit = false;
}
},
},
"type.name"(newVal) {
if (this.isEdit) {
this.isEdit = false;
}
},
},
methods: {
open() {
this.$emit("active", this.type.id);
},
edit() {
this.isEdit = !this.isEdit;
if (this.isEdit) this.$nextTick(() => this.$refs["type_input"].focus());
},
update(e) {
if (!this.type.name) {
this.$Message.error("值不得为空!");
return;
}
if (this.eventType == "") {
this.eventType = e.type;
this.isEdit = false;
this.$emit("edit", this.type.id, this.type.name);
setTimeout(() => {
this.eventType = "";
}, 500);
}
},
remove() {
this.$emit("remove", this.type.id);
},
},
};
</script>
<style lang="scss" scoped>
.flex {
display: flex;
}
.type_set-box {
width: 222px;
background: #ffffff;
margin-right: 15px;
margin-bottom: 15px;
.type_name {
height: 66px;
border: 1px solid #e9e9e9;
border-bottom: none;
padding-left: 15px;
padding-right: 15px;
line-height: 66px;
display: flex;
justify-content: center;
align-items: center;
span {
height: 24px;
font-size: 15px;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 500;
color: #272829;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 222px;
display: inline-block;
}
.iconfont {
font-size: 26px;
}
}
.type_action {
flex-wrap: wrap;
justify-content: center;
align-items: center;
width: 222px;
height: 30px;
line-height: 30px;
background: #f7f7f7;
border-radius: 0px 0px 2px 2px;
border-top: none;
border: 1px solid #e9e9e9;
span {
font-size: 12px;
}
div {
cursor: pointer;
flex: 1;
text-align: center;
}
div:hover {
color: #6c91f0;
}
}
.divider {
// position: relative;
height: 14px;
width: 1px;
background: #e8e8e8;
// left: 73px;
// top: -23px;
float: left;
margin-left: 71px;
margin-top: -22px;
}
.divider2 {
// position: relative;
// left: 146px;
// top: -37px;
height: 14px;
width: 1px;
background: #e8e8e8;
float: left;
margin-left: 147px;
margin-top: -22px;
}
}
</style>
+100
View File
@@ -0,0 +1,100 @@
<template>
<div>
<ul class="public-upload-img fix">
<li v-for="(item, index) in uploadList">
<template v-if="item.status === 'finished'">
<img :src="item.url" alt="" />
<div class="upload-list-cover">
<Icon type="ios-eye-outline" size="24" title="预览图片" @click.native="handleView(item.url)"></Icon>
<Icon size="24" class="ml-10" title="删除图片" type="ios-trash-outline" @click.native="handleRemove(index)"></Icon>
</div>
</template>
<template v-else>
<Progress v-if="item.showProgress" :percent="item.percentage" class="mt-24" hide-info></Progress>
</template>
</li>
<li class="add-li">
<Upload
ref="upload"
:show-upload-list="false"
:on-success="handleSuccess"
:format="['jpg', 'jpeg', 'png', 'gif']"
accept="image/gif,image/jpeg,image/jpg,image/png"
:max-size="2048"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize"
:default-file-list="defaultList"
type="drag"
:action="uploadUrl"
>
<div class="add-box">
<Icon type="ios-add" size="50"></Icon>
</div>
</Upload>
</li>
</ul>
<Modal title="图片预览" v-model="visible" footer-hide>
<img :src="imgName" v-if="visible" style="width: 100%" />
</Modal>
</div>
</template>
<script>
export default {
props: {
baseUrl: { type: String, default: "" },
imgList: { type: Array, default: [] },
uploadUrl: { type: String, default: "" },
},
data() {
return {
imgName: "",
visible: false,
uploadList: [],
defaultList: [],
};
},
created() {
let arr = [];
this.imgList.forEach(element => {
arr.push({
name: "",
url: this.baseUrl + element,
});
});
this.defaultList = arr;
},
mounted() {
console.log(this.$refs.upload.fileList);
this.uploadList = this.$refs.upload.fileList;
},
methods: {
handleView(name) {
this.imgName = name;
this.visible = true;
},
handleRemove(index) {
this.$refs.upload.fileList.splice(index, 1);
this.imgList.splice(index, 1);
},
handleSuccess(res, file) {
console.log(res, file);
file.url = this.baseUrl + res.data;
this.imgList.push(res.data);
},
handleFormatError(file) {
console.error(file);
},
handleMaxSize(file) {
this.$Notice.warning({
title: "图片过大",
desc: "图片最大上传大小为20M.",
});
},
},
};
</script>
<style>
</style>
+456
View File
@@ -0,0 +1,456 @@
<template>
<Modal v-model="modal" title="选择用户" width="720px" :mask-closable="false" :closable="false">
<Tabs class="tabs-panel" type="card" v-if="firstTabIndex && firstTabIndex.length > 1" v-model="firstTabIndex" @on-click="firstTabChange">
<TabPane v-for="(item, index) in firstTabData" :label="item" :name="index + ''"></TabPane>
</Tabs>
<AutoComplete @on-search="handleSearch" @on-select="handleSelect" placeholder="输入名字搜索" v-model="searchTreeName" :clearable="true" class="auto-box">
<Option v-for="item in currentFlatten" :value="item.name">
<span>{{ item.name }}</span>
<span class="fr info">{{ item.group }}</span>
</Option>
</AutoComplete>
<div class="tree-wrap fix">
<div class="tree-panel fl">
<ul class="tree-tab-ul fix">
<li
@click="secondTabChange(index)"
:style="{ width: 100 / secondTabData.length + '%' }"
:class="{ active: index == secondTabIndex, fl: true }"
v-for="(item, index) in secondTabData"
>
{{ item }}
</li>
</ul>
<div class="tree-box">
<div :id="ztreeDomId" class="ztree"></div>
</div>
</div>
<div class="arrow-box fl">
<svg t="1626916477711" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10021" width="36" height="36">
<path
d="M138.935 256.078v511.844h63.42V256.078h-63.42z m126.842 0v511.844h127.215V256.078H265.777zM885.064 512L583.254 64.323v191.755H456.414v511.844h126.842v191.755L885.064 512z"
p-id="10022"
fill="#2d8cf0"
></path>
</svg>
</div>
<div class="user-panel fr">
<div class="title-box fix">
<Checkbox @on-change="checkAllUser" v-model="isCheckUserAll">全选</Checkbox>
<div class="fr">{{ tempUserList.length }}</div>
<a href="javascript:;" @click="delCheckUser" v-if="tempUserList.findIndex(x => x.isCheck) > -1">删除选中</a>
</div>
<ul class="user-box">
<li :class="{ active: item.isCheck }" v-for="(item, index) in tempUserList" @click="checkUserOne(item)">
{{ item.name }}
<span class="info">{{ item.group }}</span>
</li>
</ul>
</div>
</div>
<div slot="footer">
<i-Button type="primary" @click="submit">提交</i-Button>
<i-Button @click="modal = false" style="margin-left: 8px">取消</i-Button>
</div>
</Modal>
</template>
<script>
require("@public/libs/ztree/zTreeStyle.css");
import "@public/libs/ztree/jquery.ztree.all.min.js";
export default {
props: {
treeData: {
type: Array,
default() {
return [];
},
},
userids: {
type: Array,
default() {
return [];
},
},
ignoreIds: {
type: Array,
default() {
return [];
},
},
},
model: {
prop: "userids",
event: "change",
},
data() {
return {
ztreeDomId: null,
modal: false,
isCheckUserAll: false,
firstTabIndex: 0,
secondTabIndex: 0,
cacheMap: new Map(),
currentTree: [],
currentFlatten: [],
searchTreeName: null,
tempUserList: [],
isDisabled: false,
};
},
watch: {
searchTreeName(val) {
this.handleSearch(val);
},
},
computed: {
firstTabData() {
if (this.treeData.length > 1) return this.treeData.map(x => x.name);
else return [];
},
secondTabData() {
if (this.treeData.length == 0) return [];
return this.treeData[this.firstTabIndex].data.map(x => x.name);
},
currentTreeFlatten() {
const flatten = (data, pid, pname) => {
return data.reduce((arr, { id, name, children = [], type }) => arr.concat([{ id, name, pid, type, group: pname }], flatten(children, id, name)), []);
};
if (!this.currentTree) return [];
var newArr = flatten(this.currentTree, 0, "");
return newArr;
},
},
mounted() {
this.isDisabled = this.ignoreIds.length > 0;
this.ztreeDomId = this.uuid();
},
methods: {
show() {
this.modal = true;
this.firstTabChange("0");
},
firstTabChange(index) {
this.firstTabIndex = index;
this.secondTabChange(0);
},
secondTabChange(index) {
const _this = this;
if (index != 0 && this.secondTabIndex == index) return;
this.secondTabIndex = index;
this.changeEmpty();
this.initTree();
},
handleSearch(query) {
const _this = this;
let arr = [],
searchArr = this.currentTreeFlatten.filter(x => x.type == 2 && x.name.indexOf(query) != -1);
for (const item of searchArr) {
!arr.map(x => _this.getSplitId(x.id)).includes(_this.getSplitId(item.id)) && arr.push(item);
}
this.currentFlatten = arr;
},
handleSelect(query) {
const _this = this;
if (query.indexOf(",") != -1) query = query.split(",")[0];
var treeObj = $.fn.zTree.getZTreeObj(this.ztreeDomId);
var node = treeObj.getNodeByParam("name", query, null);
if (!!node) treeObj.checkNode(node, true, true, true);
},
formatTree(tree, level = 0) {
const _this = this;
tree.forEach(element => {
element.open = level == 0;
element.type = 1;
element.id = element.id + "," + element.pid;
element.checked = false;
if (typeof element.children !== "undefined" && element.children.length > 0) element.children = _this.formatTree(element.children, level + 1);
else {
element.type = 2;
}
});
return tree;
},
initTree() {
const _this = this;
let first = _this.firstTabIndex,
second = _this.secondTabIndex,
cacheKey = first + "," + second,
cacheMap = _this.cacheMap,
tree = this.treeData[first].data[second].data;
if (!!tree && typeof tree === "function") {
var cacheData = cacheMap.get(cacheKey);
if (!cacheData) {
tree().then(data => {
cacheMap.set(cacheKey, data);
_this.createTree(data, _this.ztreeDomId);
});
} else {
_this.createTree(cacheData, _this.ztreeDomId);
}
} else {
_this.createTree(tree, _this.ztreeDomId);
}
},
createTree(_data, _domId) {
const _this = this;
var jtree = JSON.parse(JSON.stringify(_data));
var ftree = _this.formatTree(jtree);
_this.currentTree = ftree;
//_this.tempUserList = JSON.parse(JSON.stringify(_this.userids));
let treeSetting = {
view: {
dblClickExpand: false,
showIcon: false,
},
check: {
enable: true,
chkboxType: { Y: "ps", N: "ps" },
},
callback: {},
};
function formatUser(tree, checked) {
const recursionCheckIds = tree => {
let arrIds = [];
for (const key in tree) {
const element = tree[key];
if (element.type == 2 && !element.chkDisabled) arrIds.push({ id: element.id, tid: element.tId });
if (typeof element.children !== "undefined" && element.children) {
var childIds = recursionCheckIds(element.children);
arrIds = [...arrIds, ...childIds];
}
}
return arrIds;
};
var checkIds = recursionCheckIds([tree]);
var checkUserList = _this.currentTreeFlatten.filter(x => checkIds.map(x => x.id).includes(x.id));
let checkUserArr = [];
for (const element of checkUserList) {
var findObj = checkIds.find(x => x.id == element.id);
if (!!findObj && checkUserArr.findIndex(x => x.id == element.id) === -1 && _this.tempUserList.findIndex(x => x.id == element.id) === -1) {
checkUserArr.push({ ...element, isCheck: false, tid: findObj.tid });
}
}
if (checked) _this.tempUserList = _this.tempUserList.concat(checkUserArr);
else {
var ids = checkUserList.map(x => x.id);
_this.tempUserList = _this.tempUserList.filter(x => !ids.includes(x.id));
}
}
//当禁止时,是否还显示在右侧
var isCheckFormat = treeNode => {
let id = _this.getSplitId(treeNode.id);
return _this.ignoreIds.findIndex(x => x == id) === -1;
};
treeSetting.callback.onClick = (event, treeId, treeNode, clickFlag) => {
let check = treeNode.checked;
let treeObj = $.fn.zTree.getZTreeObj(treeId);
treeObj.checkNode(treeNode, !check, true);
if (isCheckFormat(treeNode)) formatUser(treeNode, treeNode.checked);
};
treeSetting.callback.onCheck = (event, treeId, treeNode) => {
if (isCheckFormat(treeNode)) formatUser(treeNode, treeNode.checked);
};
var treeObj = $.fn.zTree.init($("#" + _domId), treeSetting, ftree);
//checkUser
var nodes = treeObj.getNodesByFilter(x => _this.userids.includes(_this.getSplitId(x.id)));
var arr = [];
nodes.forEach(element => {
var splitId = _this.getSplitId(element.id); //重复id只选择第一个
if (!arr.includes(splitId)) treeObj.checkNode(element, true, true, true);
arr.push(splitId);
});
var ignorNodes = treeObj.getNodesByFilter(x => _this.ignoreIds.includes(_this.getSplitId(x.id)));
ignorNodes.forEach(element => {
treeObj.checkNode(element, true, true, true);
treeObj.setChkDisabled(element, true);
});
},
checkAllUser(check) {
//全选用户
const _this = this;
_this.tempUserList.forEach(element => {
element.isCheck = check;
});
},
checkUserOne(item) {
item.isCheck = !item.isCheck;
let tlist = this.tempUserList;
this.isCheckUserAll = tlist.length > 0 && tlist.filter(x => x.isCheck).length == tlist.length;
},
delCheckUser() {
//删除选中用户
const _this = this;
let tlist = _this.tempUserList,
delArr = [];
for (const item of tlist) {
item.isCheck && delArr.push(item);
}
var treeObj = $.fn.zTree.getZTreeObj(_this.ztreeDomId);
delArr.forEach(element => {
var node = treeObj.getNodeByTId(element.tid);
treeObj.checkNode(node, false, true, true);
});
_this.isCheckUserAll = false;
},
changeEmpty() {
this.tempUserList = [];
this.searchTreeName = "";
},
submit() {
const _this = this;
let objArr = [],
idsArr = this.tempUserList.map(x => _this.getSplitId(x.id));
idsArr = Array.from(new Set(idsArr));
this.$emit("change", idsArr);
function delKey(obj, ...args) {
args.forEach(v => {
delete obj[v];
});
return obj;
}
idsArr.forEach(element => {
let findObj = _this.tempUserList.find(x => _this.getSplitId(x.id) == element);
delKey(findObj, "id", "isCheck", "tid", "pid", "type");
let item = { ...findObj, id: element };
if (_this.isDisabled) item.disabled = this.userids.findIndex(x => x == element) !== -1;
objArr.push(item);
});
if (_this.isDisabled) {
var treeObj = $.fn.zTree.getZTreeObj(_this.ztreeDomId);
var nodes = treeObj.getNodesByFilter(x => x.chkDisabled);
var disIds = nodes.map(x => _this.getSplitId(x.id));
objArr.forEach(element => {
element.disabled = disIds.findIndex(x => element.id == x) > -1;
});
}
this.$emit("on-submit", objArr);
this.modal = false;
},
getSplitId(strId) {
if (typeof strId === "string" && strId.indexOf(",") != -1) return parseInt(strId.split(",")[0]);
else return strId;
},
uuid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
},
},
};
</script>
<style lang="scss" scoped>
.tree-wrap {
height: 430px;
position: relative;
}
.tree-panel {
border: 1px solid #eee;
width: 280px;
height: 430px;
overflow: hidden;
}
.tree-tab-ul {
li {
background: #f7f7f7;
border-bottom: 1px solid #eee;
height: 34px;
line-height: 34px;
text-align: center;
cursor: pointer;
}
li.active {
background: #fff;
color: #2d8cf0;
border-bottom: none;
cursor: default;
}
}
.tree-box {
padding: 4px 0;
height: 394px;
width: auto;
overflow-y: auto;
border: none;
}
.user-panel {
border: 1px solid #eee;
width: 280px;
.title-box {
background: #f7f7f7;
height: 34px;
line-height: 34px;
padding: 0 12px;
a {
text-decoration: none;
float: right;
margin-right: 20px;
outline: none;
}
}
.user-box {
height: 396px;
overflow-y: auto;
li {
line-height: 32px;
padding-left: 12px;
cursor: pointer;
.info {
float: right;
margin-right: 12px;
font-size: 12px;
color: gray;
}
}
li:hover {
background: #f3f3f3;
}
li.active {
background: #e4edf6;
}
}
}
.arrow-box {
width: 128px;
text-align: center;
position: relative;
top: 50%;
margin-top: -17px;
}
.auto-box {
width: 200px;
margin-bottom: 10px;
.ivu-select-item .info {
font-size: 12px;
color: gray;
position: relative;
top: 2px;
}
}
.ztree li {
overflow: hidden;
}
.ztree li a {
width: 100%;
}
.ztree li a:hover {
text-decoration: none;
}
.ivu-auto-complete .ivu-select-dropdown-list {
max-height: 310px;
overflow-y: auto;
}
</style>
+157
View File
@@ -0,0 +1,157 @@
import conf from "./hostConf.js";
let BaseUrl = conf.serviceUrl;
export default {
Base: {
GetUserInfo: BaseUrl + "/api/GetUserInfo",
GetUserMod: BaseUrl + "/api/GetUserMod",
},
BschClassSystem: {
//年级树
getGradeTree:BaseUrl + "/api/tree/getGradeTree",//获取年级树
getClassTree:BaseUrl + "/api/tree/getClassTree",//获取班级树
getTeaTree:BaseUrl + "/api/tree/getTeaTree",//获取教师树
getGroupTree:BaseUrl + "/api/tree/getGroupTree",//获取全区树
getHigherSchGradeTree:BaseUrl + "/api/tree/getHigherSchGradeTree",//获取高校年级树
getHigherSchClassTree:BaseUrl + "/api/tree/getHigherSchClassTree",//获取高校班级树
getgradeselect:BaseUrl + "/api/grade/select",//获取年级下拉
getschclassselect:BaseUrl + "/api/schclass/select",//获取班级下拉
getSchoolList:BaseUrl + "/api/tree/getSchoolList",//获取学校下拉
getselectBySchId:BaseUrl + "/api/grade/selectBySchId",//通过学校ID获取年级下拉
getselectBySchIdAndGradeId:BaseUrl + "/api/schclass/selectBySchIdAndGradeId",//通过学校ID、年级ID获取班级下拉
getselectBySchsystemId:BaseUrl + "/api/grade/selectBySchsystemId",//通过学制ID获取年级下拉
getTeacherTree:BaseUrl + "/api/tree/getTeacherTree",//获取教师选人树
//学制选择
getschsystemlist: BaseUrl + "/api/schsystem/list",//获取学校绑定学制列表
schsystemsave: BaseUrl + "/api/schsystem/save",//保存学制
getschsystemselect: BaseUrl + "/api/schsystem/select",//获取选择的学制
getType: BaseUrl + "/api/schsystem/getType",//获取学制类型
//时段设置
getyeartermlist: BaseUrl + "/api/yearterm/list",//获取学年学期
yeartermadd: BaseUrl + "/api/yearterm/add",//添加学年学期
yeartermedit: BaseUrl + "/api/yearterm/edit",//编辑学年学期
//区域日志管理
systemSearch: BaseUrl + "/api/log/systemSearch",//获取日志
//学校设置
getcampuslist: BaseUrl + "/api/campus/list",//获取校区
campussave: BaseUrl + "/api/campus/save",//保存校区
campusdelete: BaseUrl + "/api/campus/delete/",//删除校区
campussort: BaseUrl + "/api/campus/sort",//校区排序
getcampusselect: BaseUrl + "/api/campus/select",//校区下拉
//系部设置
getdivisionlist: BaseUrl + "/api/division/list",//获取系部列表
divisionsave: BaseUrl + "/api/division/save",//保存系部
divisiondelete: BaseUrl + "/api/division/delete/",//删除系部
divisionsort: BaseUrl + "/api/division/sort",//系部排序
getdivisionselect: BaseUrl + "/api/division/select",//系部下拉
//专业设置
getmajorslist: BaseUrl + "/api/majors/list",//获取专业列表
majorssave: BaseUrl + "/api/majors/save",//保存专业
majorsdelete: BaseUrl + "/api/majors/delete/",//删除专业
majorssort: BaseUrl + "/api/majors/sort",//专业排序
getmajorsselect: BaseUrl + "/api/majors/select",//专业下拉
//课程管理
getcourselist: BaseUrl + "/api/course/search",//获取课程列表
coursesave: BaseUrl + "/api/course/save",//保存课程
coursedelete: BaseUrl + "/api/course/delete/",//删除课程
getcoursetypeselect: BaseUrl + "/api/course/typeSelect/",//课程类别下拉
//学科设置
getsubjectlist: BaseUrl + "/api/subject/list",//获取学科列表
subjectsave: BaseUrl + "/api/subject/save",//添加/编辑学科
subjectsort: BaseUrl + "/api/subject/sort",//学科排序
subjectdelete: BaseUrl + "/api/subject/delete/",//学科删除
getsubjectselect: BaseUrl + "/api/subject/select",//获取学科下拉
//K12班级管理
getsearcBaseSchClass: BaseUrl + "/api/schclass/searcBaseSchClass",//获取班级列表
addBaseSchClass: BaseUrl + "/api/schclass/addBaseSchClass",//添加班级列表
deleteBaseSchClass: BaseUrl + "/api/schclass/delete/",//删除班级列表
editClassName: BaseUrl + "/api/schclass/editClassName",//编辑班级别名
editClassFirstTeacher: BaseUrl + "/api/schclass/editClassFirstTeacher",//编辑班主任
editClassSecondTeacher: BaseUrl + "/api/schclass/editClassSecondTeacher",//编辑副班主任
editClassCampus: BaseUrl + "/api/schclass/editClassCampus",//编辑校区
schclassdownloadTemplate: BaseUrl + "/api/schclass/downloadTemplate",//下载模版
schclassImport: BaseUrl + "/api/schclass/importClasses",//导入班级
//高校班级管理
getsearcHigherSchClass: BaseUrl + "/api/schclass/searcHigherSchClass",//获取班级列表
saveHigherSchClass: BaseUrl + "/api/schclass/saveHigherSchClass",//添加班级列表
deleteHigherSchClass: BaseUrl + "/api/schclass/HigherSchClassdelete/",//删除班级列表
downloadHeightTemplate: BaseUrl + "/api/schclass/downloadHeightTemplate",//高校下载模版
importHeightClasses: BaseUrl + "/api/schclass/importHeightClasses",//导入高校班级
//教师管理
getteachersearch: BaseUrl + "/api/teacher/search",//获取教师列表
getgroupteacherSelect: BaseUrl + "/api/teacher/groupSelect",//获取教师组下拉
getgroupSelectBySchId: BaseUrl + "/api/teacher/groupSelectBySchId",//获取教师学科组下拉
getteacherSelect: BaseUrl + "/api/teacher/select",//获取教师下拉
teachersave: BaseUrl + "/api/teacher/save",//保存教师
teacherdelete: BaseUrl + "/api/teacher/delete/",//删除教师
teachersetStatus: BaseUrl + "/api/teacher/setStatus",//设置教师状态
transferOut: BaseUrl + "/api/teacher/setTransferOut/",//设置移出教师
moveGroup: BaseUrl + "/api/teacher/moveGroup",//移动教师
setTransferIn: BaseUrl + "/api/teacher/setTransferIn",//教师转入
transferOutSchSelect: BaseUrl + "/api/teacher/transferOutSchSelect",//教师转出学校下拉
getsearchTransferOut: BaseUrl + "/api/teacher/searchTransferOut",//教师转出教师列表
teacherdownloadTemplate: BaseUrl + "/api/teacher/downloadTemplate",//下载教师模版
teacherImport: BaseUrl + "/api/teacher/Import",//教师导入
// 教师任教
getteachssearch: BaseUrl + "/api/teachs/search",//获取任教列表
teachssave: BaseUrl + "/api/teachs/save",//保存任教
teachsdelete: BaseUrl + "/api/teachs/delete/",//删除任教
delClassTeach: BaseUrl + "/api/teachs/delClassTeach",//删除班级任教
teachsexport: BaseUrl + "/api/teachs/export",//导出任教
TeachdownloadTemplate: BaseUrl + "/api/teachs/downloadTemplate",//导出任教模版
importTeach: BaseUrl + "/api/teachs/importTeach",//导入任教
getSchClassTeach: BaseUrl + "/api/teachs/getSchClassTeach",//通过班级ID获取任教
//教师分组管理
getSchCustomGroup: BaseUrl + "/api/group/getSchCustomGroup",//获取教师分组列表
saveSchCustomGroup: BaseUrl + "/api/group/saveSchCustomGroup",//保存教师分组
saveMembers: BaseUrl + "/api/group/saveMembers",//设置组员
setLeaders: BaseUrl + "/api/group/setLeaders",//设置组长
deleteSchCustomGroup: BaseUrl + "/api/group/deleteSchCustomGroup",//删除分组
GroupdownloadTemplate: BaseUrl + "/api/group/downloadTemplate",//导入分组模版
importCustomGroup: BaseUrl + "/api/group/importCustomGroup",//导入分组
//k12学生管理
getstudentsearch: BaseUrl + "/api/student/baseSchSearch",//获取学生列表
studentsave: BaseUrl + "/api/student/save",//保存学生
studentdelete: BaseUrl + "/api/student/delete/",//删除学生
getstudentabnormal: BaseUrl + "/api/student/abnormal",//获取学生状态
studenttransferClass: BaseUrl + "/api/student/transferClass",//学生调班
studentsetAbnormalStatus: BaseUrl + "/api/student/setAbnormalStatus",//学生异动
studenttransferOut: BaseUrl + "/api/student/setTransferOut/",//设置移出学生
studenttransferOutSchSelect: BaseUrl + "/api/student/transferOutSchSelect",//学生转出学校下拉
getstudentsearchTransferOut: BaseUrl + "/api/student/searchTransferOut",//学生转出列表
studentsetTransferIn: BaseUrl + "/api/student/setTransferIn",//学生转入
studentdownloadTemplate: BaseUrl + "/api/student/downloadTemplate",//下载学生导入模版
importStudents: BaseUrl + "/api/student/importStudents",//学生导入
studentClassNoTemplate: BaseUrl + "/api/student/studentClassNoTemplate",//下载学生学号导入模版
importStudentClassNo: BaseUrl + "/api/student/importStudentClassNo",//学生学号导入
searchSchClassStudent: BaseUrl + "/api/student/searchSchClassStudent",//通过班级ID获取学生
//高校学生管理
gethigherSchSearch: BaseUrl + "/api/student/higherSchSearch",//获取学生列表
// 学生异动管理
getbaseSchAbnormalSearch: BaseUrl + "/api/student/baseSchAbnormalSearch",//获取K12学生异动列表
gethigherSchAbnormalSearch: BaseUrl + "/api/student/higherSchAbnormalSearch",//获取高校学生异动列表
//日志管理
getlogsearch: BaseUrl + "/api/log/search",//获取日志列表
//公共接口
uploadFile: BaseUrl + "/api/uploadFile",//上传文件
}
};
+12
View File
@@ -0,0 +1,12 @@
let conf = {
baseHost: "http://base.bestsch.com",
ssoHost: "http://sso.bestsch.com",
serviceUrl: "http://192.168.10.166:8087",
fileHost:'http://base.bestsch.com/ClassroomMan/file',
isLayoutOld: false
// serviceUrl: "http://basedebug.bestsch.com/BschHome/",
};
if (typeof window.conf !== "undefined") conf = window.conf;
export default conf;
+251
View File
@@ -0,0 +1,251 @@
<template>
<div class="layout-wrap">
<header class="header-container fix">
<div class="fl align-middle icon-box">
<div :class="['icon-btn', isPanel ? 'active' : '']" @click="showPanel">
<svg width="22" height="22" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M24 0.972656H0V24.9727H24V0.972656Z" fill="white" fill-opacity="0.01" />
<path
d="M9 2.97266H3C2.44772 2.97266 2 3.42037 2 3.97266V9.97266C2 10.5249 2.44772 10.9727 3 10.9727H9C9.55228 10.9727 10 10.5249 10 9.97266V3.97266C10 3.42037 9.55228 2.97266 9 2.97266Z"
stroke="white"
stroke-width="1.6"
stroke-linejoin="round"
/>
<path
d="M9 14.9727H3C2.44772 14.9727 2 15.4204 2 15.9727V21.9727C2 22.5249 2.44772 22.9727 3 22.9727H9C9.55228 22.9727 10 22.5249 10 21.9727V15.9727C10 15.4204 9.55228 14.9727 9 14.9727Z"
stroke="white"
stroke-width="1.6"
stroke-linejoin="round"
/>
<path
d="M21 2.97266H15C14.4477 2.97266 14 3.42037 14 3.97266V9.97266C14 10.5249 14.4477 10.9727 15 10.9727H21C21.5523 10.9727 22 10.5249 22 9.97266V3.97266C22 3.42037 21.5523 2.97266 21 2.97266Z"
stroke="white"
stroke-width="1.6"
stroke-linejoin="round"
/>
<path
d="M14 14.9727H22"
stroke="white"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M18 18.9727H22"
stroke="white"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M14 22.9727H22"
stroke="white"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
/>
</g>
<defs>
<clipPath id="clip0">
<rect width="24" height="24" fill="white" transform="translate(0 0.972656)" />
</clipPath>
</defs>
</svg>
</div>
</div>
<div class="delimiter fl align-middle"></div>
<h4 class="fl align-middle">{{ appName }}</h4>
<div class="school-bg"></div>
<div class="header-aside fr">
<img :src="userInfo.avatar" class="fl align-middle" @click="linkUserInfo" />
<a class="name fl align-middle text-ellipsis" href="javascript:void(0);" @click="linkUserInfo">
{{ userInfo.name }}
</a>
<div class="delimiter fl align-middle"></div>
<a href="javascript:;" @click="logout" class="logout fl align-middle" title="退出登陆">
<svg
t="1615536538850"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="3048"
width="18"
height="18"
>
<path
d="M554.666667 426.666667a42.666667 42.666667 0 0 1-85.333334 0V128a42.666667 42.666667 0 0 1 85.333334 0v298.666667z m101.973333-133.376a42.666667 42.666667 0 0 1 41.429333-74.581334 384 384 0 1 1-372.138666 0 42.666667 42.666667 0 0 1 41.386666 74.581334 298.666667 298.666667 0 1 0 289.365334 0z"
p-id="3049"
fill="#ffffff"
></path>
</svg>
</a>
</div>
</header>
<div class="left-menu" v-if="!isAlone">
<div class="menu-top-box"></div>
<ul>
<li
:class="item.page == currPath ? 'active' : ''"
v-for="(item, index) in modList"
:key="'menu_li_' + index"
>
<a href="javascript:;" @click="linkMenu(item.page)">
<div v-if="item.icon" :class="['icon-box fl', item.icon]"></div>
<div v-else class="icon-box fl">
<svg
t="1615860353141"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="6624"
width="14"
height="14"
>
<path
d="M0 102.4h307.2v307.2H0zM0 614.4h307.2v307.2H0zM409.6 102.4h614.4v102.4H409.6zM409.6 307.2h614.4v102.4H409.6zM409.6 614.4h614.4v102.4H409.6zM409.6 819.2h614.4v102.4H409.6z"
p-id="6625"
fill="#656667"
></path>
</svg>
</div>
{{ item.name }}
</a>
</li>
</ul>
<div class="clear-box"></div>
</div>
<template v-if="!isAlone">
<div class="path-nav">
<Breadcrumb separator=">">
<a :href="baseHost + '/BschHome/front/index.html'">
<BreadcrumbItem>
<Icon type="ios-home-outline" size="14"></Icon>
首页
</BreadcrumbItem>
</a>
<BreadcrumbItem to="/">{{ appName }}</BreadcrumbItem>
<BreadcrumbItem v-if="typeof navPath === 'string'">{{ navPath }}</BreadcrumbItem>
<template v-else v-for="(item, index) in navPath">
<BreadcrumbItem v-if="index != navPath.length - 1" :to="item.page">
{{ item.name }}
</BreadcrumbItem>
<BreadcrumbItem v-else>{{ item.name }}</BreadcrumbItem>
</template>
</Breadcrumb>
</div>
<!-- <div class="path-nav" v-else>
{{ navPath }}
</div> -->
</template>
<transition name="fade_apps">
<div class="app-panel" ref="appPanel" v-if="isPanel">
<div class="tips-box"></div>
<ul class="fix">
<li class="fl" v-for="item in appList">
<a :href="item.url" target="_bank" rel="noopener noreferrer">
<div class="icon-box">
<img :src="item.icon" alt="" />
</div>
<p class="text-ellipsis">{{ item.name }}</p>
</a>
</li>
</ul>
<div class="link-panel">
<a :href="baseHost + '/BschHome/front/index.html#/apps'" target="_bank" rel="noopener noreferrer">
全部应用
<i>→</i>
</a>
</div>
</div>
</transition>
</div>
</template>
<script>
export default {
inject: ["reload"],
props: {
layoutData: {
appName: String,
appList: Array,
modList: Array,
userInfo: Object,
ssoHost: String,
baseHost: String,
isAlone: Boolean, //是否只有一个选项卡
},
},
data() {
return {
color: "#fff",
isPanel: false,
themeColor: [],
currPath: "/layout",
navPath: "默认列表",
};
},
created() {
const _this = this;
for (const key in _this.layoutData) _this[key] = _this.layoutData[key];
_this.routeChange(_this.$route); //初次加载时手动触发route的change
this.$watch("$route", _this.routeChange);
},
mounted() {},
methods: {
showPanel() {
const _this = this;
if (_this.appList.length == 0) {
_this.$Message.info("暂无常用应用!");
return;
}
_this.isPanel = false;
setTimeout(() => {
_this.isPanel = true;
setTimeout(showPanel, 0);
}, 0);
var showPanel = () => {
_this.$refs["appPanel"].onclick = e => e.stopPropagation();
document.onclick = () => (_this.isPanel = false);
};
},
routeChange(router) {
const _this = this;
window.document.title = router.meta.title || "弘育云平台";
let currentMod = router.meta.modId;
//时间有限,仅支持一层.暂时写死,有需求再改
_this.modList.forEach(element => {
let childIndex = element.children.findIndex(x => x.modId == currentMod);
if (currentMod == element.modId || childIndex > -1) {
_this.currPath = element.page;
if (childIndex > -1) {
let navArr = [];
navArr.push({ name: element.name, page: element.page });
navArr.push({
name: element.children[childIndex].name,
page: element.children[childIndex].page,
});
_this.navPath = navArr;
} else _this.navPath = element.name;
}
});
},
linkMenu(page) {
const path = this.$route.path;
if (page === path) this.reload();
else this.$router.push(page);
},
linkUserInfo() {
top.location.href = this.baseHost + "/v/userinfo";
},
logout() {
let url = window.location.href;
url = "/logout?service=" + url;
top.location.href = this.ssoHost + url;
},
},
};
</script>
+305
View File
@@ -0,0 +1,305 @@
<template>
<div class="layout-wrap">
<header class="header-container fix">
<div class="fl align-middle icon-box">
<div
:class="['icon-btn', isPanel ? 'active' : '']"
@click="showPanel"
>
<svg
t="1615518122113"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="1276"
width="18"
height="18"
>
<path
d="M710.54933333 68.11733334a64 64 0 0 1 90.496 0l165.504 165.504a64 64 0 0 1 0 90.496l-165.504 165.504a64 64 0 0 1-90.496 0l-165.504-165.504a64 64 0 0 1 0-90.496l165.504-165.504zM165.33333333 69.33333334a96 96 0 0 0-96 96v224a96 96 0 0 0 96 96h224a96 96 0 0 0 96-96V165.33333334a96 96 0 0 0-96-96H165.33333333zM165.33333333 549.33333334a96 96 0 0 0-96 96v224a96 96 0 0 0 96 96h224a96 96 0 0 0 96-96v-224a96 96 0 0 0-96-96H165.33333333zM645.33333333 549.33333334a96 96 0 0 0-96 96v224a96 96 0 0 0 96 96h224a96 96 0 0 0 96-96v-224a96 96 0 0 0-96-96h-224z"
p-id="1277"
:fill="color"
></path>
</svg>
</div>
</div>
<div class="delimiter fl align-middle"></div>
<h4 class="fl align-middle">{{ appName }}</h4>
<div class="school-bg"></div>
<div class="header-aside fr">
<img
:src="userInfo.avatar"
class="fl align-middle"
@click="linkUserInfo"
/>
<a
class="name fl align-middle text-ellipsis"
href="javascript:void(0);"
@click="linkUserInfo"
>{{ userInfo.name }}</a
>
<div class="delimiter fl align-middle"></div>
<a
href="javascript:;"
@click="logout"
class="logout fl align-middle"
title="退出登陆"
>
<svg
t="1615536538850"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="3048"
width="18"
height="18"
>
<path
d="M554.666667 426.666667a42.666667 42.666667 0 0 1-85.333334 0V128a42.666667 42.666667 0 0 1 85.333334 0v298.666667z m101.973333-133.376a42.666667 42.666667 0 0 1 41.429333-74.581334 384 384 0 1 1-372.138666 0 42.666667 42.666667 0 0 1 41.386666 74.581334 298.666667 298.666667 0 1 0 289.365334 0z"
p-id="3049"
fill="#ffffff"
></path>
<!-- d5defe -->
</svg>
</a>
</div>
</header>
<div class="left-menu" v-if="!isAlone">
<div class="menu-top-box"></div>
<ul>
<template v-for="(item, index) in modArr">
<template v-if="item.children && item.children.length > 0">
<li>
<a href="javascript:;" @click="childChange(index)">
<i
:class="
item.childState
? 'arrow-down'
: 'arrow-up'
"
></i>
<div
v-if="item.icon"
:class="['icon-box fl', item.icon]"
></div>
<div v-else class="icon-box fl">
<svg
t="1615860353141"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="6624"
width="14"
height="14"
>
<path
d="M0 102.4h307.2v307.2H0zM0 614.4h307.2v307.2H0zM409.6 102.4h614.4v102.4H409.6zM409.6 307.2h614.4v102.4H409.6zM409.6 614.4h614.4v102.4H409.6zM409.6 819.2h614.4v102.4H409.6z"
p-id="6625"
fill="#656667"
></path>
</svg>
</div>
{{ item.name }}
</a>
</li>
<transition name="fade_child_ul">
<div
class="child-item-wrap"
v-show="item.childState"
>
<li
:class="
citem.page == currPath
? 'active child-item'
: 'child-item'
"
v-for="citem in item.children"
>
<a
href="javascript:;"
@click="linkMenu(citem.page)"
>
{{ citem.name }}
</a>
</li>
</div>
</transition>
</template>
<li
v-else
:class="item.page == currPath ? 'active' : ''"
:key="'menu_li_' + index"
>
<a href="javascript:;" @click="linkMenu(item.page)">
<div
v-if="item.icon"
:class="['icon-box fl', item.icon]"
></div>
<div v-else class="icon-box fl">
<svg
t="1615860353141"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="6624"
width="14"
height="14"
>
<path
d="M0 102.4h307.2v307.2H0zM0 614.4h307.2v307.2H0zM409.6 102.4h614.4v102.4H409.6zM409.6 307.2h614.4v102.4H409.6zM409.6 614.4h614.4v102.4H409.6zM409.6 819.2h614.4v102.4H409.6z"
p-id="6625"
fill="#656667"
></path>
</svg>
</div>
{{ item.name }}
</a>
</li>
</template>
</ul>
<div class="clear-box"></div>
</div>
<template v-if="!isAlone">
<div
class="path-nav"
v-if="navPath instanceof Array && navPath.length > 1"
>
<Breadcrumb separator=">">
<template v-for="(item, index) in navPath">
<BreadcrumbItem>{{ item.name }}</BreadcrumbItem>
</template>
</Breadcrumb>
</div>
<div class="path-nav" v-else>
{{ navPath }}
</div>
</template>
<transition name="fade_apps">
<div class="app-panel" ref="appPanel" v-if="isPanel">
<div class="tips-box"></div>
<ul class="fix">
<li class="fl" v-for="item in appList">
<a
:href="item.url"
target="_bank"
rel="noopener noreferrer"
>
<div class="icon-box">
<img :src="item.icon" alt="" />
</div>
<p class="text-ellipsis">{{ item.name }}</p>
</a>
</li>
</ul>
<div class="link-panel">
<a
:href="baseHost + '/BschHome/front/index.html#/apps'"
target="_bank"
rel="noopener noreferrer"
>全部应用<i>→</i></a
>
</div>
</div>
</transition>
</div>
</template>
<script>
export default {
inject: ["reload"],
props: {
layoutData: {
appName: String,
appList: Array,
modList: Array,
userInfo: Object,
ssoHost: String,
baseHost: String,
isAlone: Boolean, //是否只有一个选项卡
},
},
data() {
return {
color: "#fff",
isPanel: false,
themeColor: [],
currPath: "",
navPath: null,
modArr: [],
};
},
created() {
const _this = this;
for (const key in _this.layoutData) _this[key] = _this.layoutData[key];
_this.routeChange(_this.$route); //初次加载时手动触发route的change
this.$watch("$route", _this.routeChange);
this.modList.forEach((element) => {
element.childState = true;
this.modArr.push(element);
});
},
mounted() {},
methods: {
showPanel() {
const _this = this;
if (_this.appList.length == 0) {
_this.$Message.info("暂无常用应用!");
return;
}
_this.isPanel = false;
setTimeout(() => {
_this.isPanel = true;
setTimeout(showPanel, 0);
}, 0);
var showPanel = () => {
_this.$refs["appPanel"].onclick = (e) => e.stopPropagation();
document.onclick = () => (_this.isPanel = false);
};
},
routeChange(router) {
const _this = this;
window.document.title = router.meta.title || "弘育云平台";
let currentMod = router.meta.modId;
_this.currPath = router.path;
//时间有限,仅支持一层.暂时写死,有需求再改
_this.modList.forEach((element) => {
let childIndex = element.children.findIndex(
(x) => x.modId == currentMod
);
if (currentMod == element.modId || childIndex > -1) {
if (childIndex > -1) {
let navArr = [];
navArr.push({ name: element.name, page: element.page });
navArr.push({
name: element.children[childIndex].name,
page: element.children[childIndex].page,
});
_this.navPath = navArr;
} else _this.navPath = element.name;
}
});
},
linkMenu(page) {
const path = this.$route.path;
if (page === path) this.reload();
else this.$router.push(page);
},
linkUserInfo() {
top.location.href = this.baseHost + "/v/userinfo";
},
childChange(index) {
this.modArr[index].childState = !this.modArr[index].childState;
},
logout() {
let url = window.location.href;
url = "/logout?service=" + url;
top.location.href = this.ssoHost + url;
},
},
};
</script>
+263
View File
@@ -0,0 +1,263 @@
<template>
<div class="layout-wrap">
<header class="header-container fix">
<div class="fl align-middle icon-box">
 <div title="常用应用" :class="['icon-btn', isPanel ? 'active' : '']" @click="showPanel">
                    <i class="iconfont icon-apps"></i>
                </div>
</div>
<div class="delimiter fl align-middle"></div>
<h4 class="fl align-middle">{{ appName }}</h4>
<div class="school-bg"></div>
<div class="header-aside fr">
<img :src="userInfo.avatar" class="fl align-middle" @click="linkUserInfo" />
<a class="name fl align-middle text-ellipsis" href="javascript:void(0);" @click="linkUserInfo">
{{ userInfo.name }}
</a>
<div class="delimiter fl align-middle"></div>
<a href=" " @click="logout" class="logout fl align-middle" title="退出登陆">
                    <i class="iconfont icon-logout"></i>
                </a>
</div>
</header>
<div class="left-menu" v-if="!isAlone">
<div class="menu-top-box"></div>
<ul>
<template v-for="(item, index) in modArr">
<template v-if="item.children && item.children.length > 0">
<li>
<a href="javascript:;" @click="childChange(index)">
<i :class="item.childState ? 'arrow-down' : 'arrow-up'"></i>
<div v-if="item.icon" :class="['icon-box fl', item.icon]"></div>
<div v-else class="icon-box fl">
<svg
t="1615860353141"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="6624"
width="14"
height="14"
>
<path
d="M0 102.4h307.2v307.2H0zM0 614.4h307.2v307.2H0zM409.6 102.4h614.4v102.4H409.6zM409.6 307.2h614.4v102.4H409.6zM409.6 614.4h614.4v102.4H409.6zM409.6 819.2h614.4v102.4H409.6z"
p-id="6625"
fill="#656667"
></path>
</svg>
</div>
{{ item.name }}
</a>
</li>
<transition name="fade_child_ul">
<div class="child-item-wrap" v-show="item.childState">
<li
:class="citem.page == currPath ? 'active child-item' : 'child-item'"
v-for="citem in item.children"
>
<a href="javascript:;" @click="linkMenu(citem.page)">
{{ citem.name }}
</a>
</li>
</div>
</transition>
</template>
<li v-else :class="item.page == currPath ? 'active' : ''" :key="'menu_li_' + index">
<a href="javascript:;" @click="linkMenu(item.page)">
<div v-if="item.icon" :class="['icon-box fl', item.icon]"></div>
<div v-else class="icon-box fl">
<svg
t="1615860353141"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="6624"
width="14"
height="14"
>
<path
d="M0 102.4h307.2v307.2H0zM0 614.4h307.2v307.2H0zM409.6 102.4h614.4v102.4H409.6zM409.6 307.2h614.4v102.4H409.6zM409.6 614.4h614.4v102.4H409.6zM409.6 819.2h614.4v102.4H409.6z"
p-id="6625"
fill="#656667"
></path>
</svg>
</div>
{{ item.name }}
</a>
</li>
</template>
</ul>
<div class="clear-box"></div>
</div>
<div class="path-nav" v-if="!isAlone">
<Breadcrumb separator=">">
<a :href="baseHost + '/BschHome/front/index.html'">
<BreadcrumbItem>
<i class="iconfont icon-category"></i>
首页
</BreadcrumbItem>
</a>
<BreadcrumbItem to="/">{{ appName }}</BreadcrumbItem>
<BreadcrumbItem v-if="typeof navPath == 'string'">{{ navPath }}</BreadcrumbItem>
<template v-else v-for="(item, index) in navPath">
<BreadcrumbItem v-if="index != navPath.length - 1" :to="item.page">{{ item.name }}</BreadcrumbItem>
<BreadcrumbItem v-else>{{ item.name }}</BreadcrumbItem>
</template>
</Breadcrumb>
</div>
<transition name="fade_apps">
<div class="app-panel" ref="appPanel" v-if="isPanel">
<div class="tips-box"></div>
<ul class="fix">
<li class="fl" v-for="item in appList">
<a :href="item.url" target="_bank" rel="noopener noreferrer">
<div class="icon-box">
<img :src="item.icon" alt="" />
</div>
<p class="text-ellipsis">{{ item.name }}</p>
</a>
</li>
</ul>
<div class="link-panel">
<a :href="baseHost + '/BschHome/front/index.html#/apps'" target="_bank" rel="noopener noreferrer">
全部应用
<i>→</i>
</a>
</div>
</div>
</transition>
</div>
</template>
<script>
export default {
inject: ["reload"],
props: {
layoutData: {
appName: String,
appList: Array,
modList: Array,
userInfo: Object,
ssoHost: String,
baseHost: String,
isAlone: Boolean //是否只有一个选项卡
}
},
data() {
return {
color: "#fff",
isPanel: false,
themeColor: [],
currPath: "",
navPath: null,
modArr: [],
menuArr: []
};
},
created() {
const _this = this;
for (const key in _this.layoutData) _this[key] = _this.layoutData[key];
this.menuArr = this.flaten(this.modList);
_this.routeChange(_this.$route); //初次加载时手动触发route的change
this.$watch("$route", _this.routeChange);
this.modList.forEach(element => {
element.childState = true; //此处修改结点状态,默认打开
this.modArr.push(element);
});
this.$bus.$on("setNavPath", arr => (_this.navPath = arr));
this.$bus.$on("setMenuPath", modId => {
let find = this.menuArr.find(x => x.modId == modId);
!!find && (this.currPath = find.page);
});
},
mounted() {},
methods: {
showPanel() {
const _this = this;
if (_this.appList.length == 0) {
_this.$Message.info("暂无常用应用!");
return;
}
_this.isPanel = false;
setTimeout(() => {
_this.isPanel = true;
setTimeout(showPanel, 0);
}, 0);
var showPanel = () => {
_this.$refs["appPanel"].onclick = e => e.stopPropagation();
document.onclick = () => (_this.isPanel = false);
};
},
routeChange(router) {
const _this = this;
let { modId, title, parentId: routerPId } = router.meta,
navArr = [];
window.document.title = title || "弘育云平台";
var find = this.menuArr.find(x => x.modId == modId);
if (!find) {
let parentFind = this.menuArr.find(x => x.modId == routerPId);
if (!parentFind) {
// this.$router.push("/");
// throw "路由错误!";
let newmenuArr = this.menuArr.filter(x => x.pid>0);
if(newmenuArr.length>0){
if(newmenuArr.findIndex(x => x.page == this.$route.path)==-1){
this.$router.push(newmenuArr[0].page);
}
}else{
if(this.menuArr.findIndex(x => x.page == this.$route.path)==-1){
this.$router.push(this.menuArr[0].page);
}
}
parentFind=this.menuArr[0];
}
find = parentFind;
setTimeout(() => {
this.navPath.push({ name: title });
}, 0);
}
let { name, page, pid } = find;
this.currPath = page;
let pfind = this.menuArr.find(x => x.id == pid);
if (!!pfind) {
navArr.push({ name: pfind.name });
}
navArr.push({ name, page });
this.navPath = navArr;
},
linkMenu(page) {
const path = this.$route.path;
if (page === path) this.reload();
else this.$router.push(page);
},
linkUserInfo() {
top.location.href = this.baseHost + "/v/userinfo";
},
childChange(index) {
this.modArr[index].childState = !this.modArr[index].childState;
},
logout() {
let url = window.location.href;
url = "/logout?service=" + url;
top.location.href = this.ssoHost + url;
},
flaten(tree, pid = 0) {
var arr = [];
tree.forEach(element => {
let { id, name, page, modId } = element;
arr.push({ id, name, page, modId, pid });
if (element.children) {
arr.push(...this.flaten(element.children, id));
}
});
return arr;
}
}
};
</script>
+306
View File
@@ -0,0 +1,306 @@
<template>
<div class="layout_old">
<!-- 头部 -->
<header class="header">
<div class="header-logo">
<div class="header-logo-title">
<img :src="logoUrl" />
</div>
</div>
<div class="header-aside">
<div class="aside-box clearfix">
<p>
你好,
<a>{{ username }}</a>
</p>
<p class="aside-btn-list">
<Tooltip content="个人信息" placement="bottom-start">
<Button type="ghost" icon="android-happy" @click="linkUser()"></Button>
</Tooltip>
<Tooltip content="应用列表" placement="bottom">
<Button type="ghost" icon="android-apps" @click="linkApp()"></Button>
</Tooltip>
<Tooltip content="退出登录" placement="bottom-end">
<Button type="ghost" icon="power" @click="logout"></Button>
</Tooltip>
<!-- 如果是3.0.icon改成 -->
<!--
md-happy
md-apps
md-power
-->
</p>
</div>
</div>
</header>
<!-- 标题 -->
<div class="path-nav">
<h1>{{ pathName }}</h1>
</div>
<!-- 侧边 -->
<div class="leftmenu">
<div class="" style="height: 100%">
<div class="title-top">{{ appName }}</div>
<ul class="title-box">
<li
v-for="(item, index) in modList"
:key="index"
:class="{
active: item.page.toLowerCase() == currPath.toLowerCase(),
}"
>
<!-- <router-link :to="item.page" >{{item.name}}</router-link> -->
<a href="javascript:;" @click="flushCom(item.page)">{{ item.name }}</a>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
inject: ["reload"],
props: {
layoutData: {
appName: String,
appList: Array,
modList: Array,
userInfo: Object,
ssoHost: String,
baseHost: String,
isAlone: Boolean, //是否只有一个选项卡
},
},
data() {
return {
username: "",
pathName: "",
logoUrl: "",
currPath: "",
};
},
computed: {
listNumber: function () {
return this.pathList.sort(function (a, b) {
return a.modId - b.modId;
});
},
},
mounted() {
const _this = this;
for (const key in _this.layoutData) _this[key] = _this.layoutData[key];
this.username = this.userInfo.name;
this.logoUrl = this.baseHost + "/img/logo.png";
_this.routeChange(_this.$route); //初次加载时手动触发route的change
this.$watch("$route", _this.routeChange);
$(".base-wrap-main").css("padding-top", "145px");
},
methods: {
logout() {
//退出登陆
let url = window.location.href;
url = "/logout?service=" + url;
top.location.href = this.ssoHost + url;
},
routeChange(router) {
const _this = this;
window.document.title = router.meta.title || "弘育云平台";
let currentMod = router.meta.modId;
//时间有限,仅支持一层.暂时写死,有需求再改
_this.modList.forEach(element => {
let childIndex = element.children.findIndex(x => x.modId == currentMod);
if (currentMod == element.modId || childIndex > -1) {
_this.currPath = element.page;
_this.pathName = element.name;
}
});
},
linkApp() {
location.href = this.baseHost + "/v/home";
},
linkUser() {
location.href = this.baseHost + "/v/userinfo";
},
flushCom(page) {
//return;
const path = this.$route.path;
// console.log(page,path);
if (page === path) {
this.reload();
//this.$root.$children[0].reload()
} else {
this.$router.push(page);
}
},
},
};
</script>
<style lang="scss" >
.layout_old {
.header {
width: 100%;
height: 70px;
position: fixed;
top: 0;
left: 0;
z-index: 22;
background-color: #fff;
background-image: linear-gradient(143deg, #6c91f0 45%, #7863b4 81%, #7a5aa9);
.header-logo {
width: 200px;
height: 100%;
float: left;
position: relative;
}
.header-logo-title {
margin-left: 32px;
position: relative;
top: 20px;
.logo-box {
background: url(/static/image/logo.png) no-repeat;
width: 450px;
height: 70px;
}
}
.header-aside {
float: right;
margin: 0 30px 0 0;
position: relative;
color: #fff;
font-size: 14px;
.aside-box {
margin: 26px 0 22px 0;
.icon-box {
width: 34px;
font-size: 18px;
color: #fff;
text-align: center;
}
a {
color: #fff;
&:hover {
color: #2d8cf0;
}
}
}
.aside-btn-list {
.ivu-btn {
padding: 0px;
border: 0px;
width: 35px;
i {
color: #fff;
font-size: 18px;
}
}
}
p {
float: left;
color: #ffffff;
padding-right: 10px;
font-size: 14px;
}
}
}
.path-nav {
position: fixed;
z-index: 21;
top: 70px;
height: 54px;
left: 200px;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
background: #fff;
width: 100%;
padding: 0 15px;
box-sizing: border-box;
min-width: 767px;
h1 {
float: left;
font-size: 16px;
color: #464c5b;
margin: auto;
line-height: 56px;
font-weight: 400;
}
}
.leftmenu {
position: fixed;
z-index: 20;
-moz-overflow: auto;
overflow: auto\9;
overflow: overlay;
overflow-x: hidden;
background: #8791a5 !important;
height: 100%;
width: 200px;
padding-top: 70px;
box-sizing: border-box;
& > div {
overflow-y: auto;
}
.title-top {
height: 54px;
line-height: 54px;
background: #3e485a;
font-size: 16px;
font-weight: 400;
text-align: center;
width: 200px;
color: #ffffff;
}
.title-box {
background: #eaedf2;
width: 200px;
overflow: overlay;
border-right: 0px;
display: block;
margin: 0;
padding: 0;
outline: 0;
list-style: none;
color: #495060;
font-size: 14px;
position: relative;
li {
text-align: center;
background: #8791a5;
padding: 0px !important;
border-bottom: 1px solid #a5adbf;
transition: all 200ms;
&:hover {
border-left: 4px solid #6c8fed;
background: #f3f3f3;
}
&.active {
background: #f3f3f3;
a {
color: #333;
}
}
a {
color: #ffffff;
display: block;
padding: 16px 0px 14px 34px;
text-align: left;
color: #ffffff;
}
a:hover {
color: rgb(51, 51, 51);
}
}
a {
color: #495060;
}
}
}
.wrap-main {
padding: 124px 0 0 200px;
}
.nav-btn-wrap {
top: 78px;
}
}
</style>
+62
View File
@@ -0,0 +1,62 @@
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import api from "./api/index.js";
// 将API方法绑定到全局
Vue.prototype.$api = api;
import "@babel/polyfill";
// import "babel-polyfill";
// 引用工具文件
import utils from "./utils/index.js";
// 将工具方法绑定到全局
Vue.prototype.$utils = utils;
import APIConfig from "./config/apiConf.js";
window.APIConfig = APIConfig;
import "view-design/dist/styles/iview.css";
import ViewUI from "view-design";
Vue.use(ViewUI);
import conf from "./config/hostConf.js";
Vue.prototype.$conf = conf;
window.conf = conf;
import "./assets/styles/main.scss";
// 是否是IE内核
//代码高亮文件引入(IE暂不支持)
import hljs from "highlight.js";
//样式文件,这里我选的是sublime样式,文件里面还有其他样式可供选择
// import "highlight.js/styles/monokai-sublime.css";
// import "highlight.js/styles/vs2015.css";
import "highlight.js/styles/atelier-dune-light.css";
Vue.directive("highlight", function(el) {
let blocks = el.querySelectorAll("pre code");
blocks.forEach(block => {
hljs.highlightBlock(block);
});
});
import codeModal from "./mixins/code_modal";
Vue.mixin(codeModal);
var EventBus = new Vue(); //事件总线
Object.defineProperties(Vue.prototype, {
    $bus: {
        get: function() {
            return EventBus;
        }
    }
});
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App)
}).$mount("#app");
+8
View File
@@ -0,0 +1,8 @@
import codeModal from "../components/codeModal/index.js";
export default {
methods: {
showCode({ name, url, type = "vue" }) {
codeModal({ name, url, type });
}
}
};
+36
View File
@@ -0,0 +1,36 @@
export const self_viewport = {
data() {
return {
tableHeight: null
};
},
computed: {
useTableHeight() {
return (settings = {}) => {
let { isSearch = true, isPage = true, isTabs = false, isOperat = true, otherH = 0 } = settings;
let bodyH = this.tableHeight,
headerH = 112,
searchH = 52,
operatH = 48,
tabsH = 56,
pageH = 72,
paddingH = 8;
let tableH = bodyH - headerH - paddingH;
isOperat && (tableH -= operatH);
isTabs && (tableH -= tabsH);
isSearch && (tableH -= searchH);
isPage && (tableH -= pageH);
otherH > 0 && (tableH -= otherH);
return tableH;
};
}
},
mounted() {
this.tableHeight = document.body.clientHeight;
window.onresize = () => {
return (() => {
this.tableHeight = document.body.clientHeight;
})();
};
}
};
+32
View File
@@ -0,0 +1,32 @@
// 去掉export function,写内部方法
export default {
methods: {
treeRenderContent(h, params) {
const { root, node, data } = params;
const icon0 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAKJJREFUOE9jZKASYKSSOQzUN+j/QaH7DAwMCmgufMDw758jo+OHB4RcDnfR/4NC/3EovsDAyPgBh9wDRru3iSA5VIOkMxgYeHQIWQ6R/3CUgeHlcgZG+3dgMxAGnVT+zyAVz8DAIUecQT8eMTA8W8jAaH4XzaDrjri8htdgRs39owYRCPqhEUbYsgihNPWAUXO/IkqCJKSDkDz1cz8hGwnJAwCJjEAToczWhwAAAABJRU5ErkJggg==';
const iconOther = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAaVJREFUOE+t1D9v00AYx/HvpSFqqyDdgKqOqVBVIRBK3kGY6NIhqtQ1tXgBSV9B4ldAeAEoZmIB3kK8M4QljM0t7XxDhgjcXHXnP4odEoOER/vu4989z2ML/tMlypz22EhAhp5Qu9ZuhRKgh6FvIQRD4NM2cAPaAPIxFIIg9IRfTJdBJUBxn8Lgh+9EkD5wUHtsGhgmQKOsZoXnNmEr9IROIYlhBHT/CYqP6dk9uRolyWxRd4OCEPDWC++gydw0IhgLUHvg+6E74gBDO5cwBvxBG/UA74HmHrTenCRHm8yNjHA1aiYxgwS0UA/QKfDbMBQiSxxWoZNB6Vu/35nhcsVgGcV3BDjQQhH0BPQNyMMnqKc1/NdHha7ZTZefTXBxRvftKayAWw0pmL6oWkG/fIY6qNL8OoNgSkCFm2LXprb9x3W4eoXqvKBxv4jBaIV+LtFyH/lthvzyExa/HK+T9qt81z6aPsLVxIHdJpyfxnlcgh9rgOEDFUY2zUb7s+FccZ2Asl6LoSxBAchN9p+GsDBTmi1AKZQucCDo9AjbJr/0f/S3n8wj0GCqE4RTEr0AAAAASUVORK5CYII=';
return h('span', [
h('img', {
attrs: {
src: node.nodeKey == 0 ? icon0 : iconOther
},
style: 'width: 16px; height: 16px;vertical-align: sub;'
}),
h('span', {
style: {
display: 'inline-block',
cursor: "pointer",
backgroundColor: node.node.selected ? "#e5e5e5" : ""
},
on: {
click: (e) => {
e.stopPropagation()
this.$emit("on-tree-change", node.node);
}
}
}, data.title)
]);
}
}
}
+141
View File
@@ -0,0 +1,141 @@
import Vue from "vue";
import VueRouter from "vue-router";
const studySystemChoice = () => import("../views/studySystemChoice.vue");
const itemSet = () => import("../views/itemSet.vue");
const areaOperateDaily = () => import("../views/areaOperateDaily.vue");
const schoolSetting = () => import("../views/schoolSetting.vue");
const subjectManForZy = () => import("../views/subjectManForZy.vue");
const classManage = () => import("../views/classManage.vue");
const electiveClassManage = () => import("../views/electiveClassManage.vue");
const electiveClassManageDetail = () => import("../views/electiveClassManageDetail.vue");
const ChristianityDetail = () => import("../views/classManage/ChristianityDetail.vue");
const subjectSetzhi = () => import("../views/subjectSetzhi.vue");
const teacherManage = () => import("../views/teacherManage.vue");
const studentManage = () => import("../views/studentManage.vue");
const historySearch = () => import("../views/historySearch.vue");
const historySearchDetail = () => import("../views/historySearchDetail.vue");
const studentYDManage = () => import("../views/studentYDManage.vue");
const zhuanruinfo = () => import("../views/zhuanruinfo.vue");
const zhuanruinfostu = () => import("../views/zhuanruinfostu.vue");
const dailySearch = () => import("../views/dailySearch.vue");
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "studySystemChoice",
component: studySystemChoice,
meta: { title: "学制选择", modId: 1, icon: "icon-shezhi" }
},
{
path: '/itemSet',
name: "itemSet",
component: itemSet,
meta:{title:"时段设置",modId:2,icon: "icon-shezhi"}
},
{
path: '/areaOperateDaily',
name: "areaOperateDaily",
component: areaOperateDaily,
meta:{title:"区域操作日志",modId:3,icon: "icon-chaxun"}
},
{
path: '/schoolSetting',
name: "schoolSetting",
component: schoolSetting,
meta:{title:"学校设置",modId:4,icon: "icon-cshxd"}
},
{
path: '/subjectManForZy',
name: "subjectManForZy",
component: subjectManForZy,
meta:{title:"课程设置",modId:5,icon: "icon-xuekeshezhi"}
},
{
path: '/subjectSetzhi',
name: "subjectSetzhi",
component: subjectSetzhi,
meta:{title:"学科管理",modId:6,icon: "icon-xuekeshezhi"}
},
{
path: '/classManage',
name: "classManage",
component: classManage,
meta:{title:"班级管理",modId:7,icon: "icon-banji"}
},
{
path: "/ChristianityDetail",
name: "ChristianityDetail",
component: ChristianityDetail,
meta: { title: "班级管理", modId: 7,parentPath:"/classManage" }
},
{
path: '/electiveClassManage',
name: "electiveClassManage",
component: electiveClassManage,
meta:{title:"选修班管理",modId:8,icon: "icon-banji"}
},
{
path: "/electiveClassManageDetail",
name: "electiveClassManageDetail",
component: electiveClassManageDetail,
meta: { title: "选修班管理", modId: 8,parentPath:"/electiveClassManage" }
},
{
path: '/teacherManage',
name: "teacherManage",
component: teacherManage,
meta:{title:"教师管理",modId:9,icon: "icon-jiaoshi"}
},
{
path: '/studentManage',
name: "studentManage",
component: studentManage,
meta:{title:"学生管理",modId:10,icon: "icon-xuesheng"}
},
{
path: '/historySearch',
name: "historySearch",
component: historySearch,
meta:{title:"历史查询",modId:11,icon: "icon-chaxun"}
},
{
path: "/historySearchDetail",
name: "historySearchDetail",
component: historySearchDetail,
meta: { title: "历史查询", modId: 11,parentPath:"/historySearch" }
},
{
path: '/studentYDManage',
name: "studentYDManage",
component: studentYDManage,
meta:{title:"学生异动管理",modId:12,icon: "icon-xuesheng"}
},
{
path: '/zhuanruinfo',
name: "zhuanruinfo",
component: zhuanruinfo,
meta:{title:"教师转入管理",modId:13,icon: "icon-jiaoshi"}
},
{
path: '/zhuanruinfostu',
name: "zhuanruinfostu",
component: zhuanruinfostu,
meta:{title:"学生转入管理",modId:14,icon: "icon-xuesheng"}
},
{
path: '/dailySearch',
name: "dailySearch",
component: dailySearch,
meta:{title:"日志管理",modId:15,icon: "icon-chaxun"}
},
];
const router = new VueRouter({
base: process.env.BASE_URL,
routes
});
export default router;
+245
View File
@@ -0,0 +1,245 @@
export default {
goodTime (str) {
let now = new Date().getTime()
let oldTime = new Date(str).getTime()
let difference = now - oldTime
let result = ''
let minute = 1000 * 60
let hour = minute * 60
let day = hour * 24
let month = day * 30
let year = month * 12
let _year = difference / year
let _month = difference / month
let _week = difference / (7 * day)
let _day = difference / day
let _hour = difference / hour
let _min = difference / minute
if (_year >= 1) {
result = '发表于 ' + ~~(_year) + ' 年前'
} else if (_month >= 1) {
result = '发表于 ' + ~~(_month) + ' 个月前'
} else if (_week >= 1) {
result = '发表于 ' + ~~(_week) + ' 周前'
} else if (_day >= 1) {
result = '发表于 ' + ~~(_day) + ' 天前'
} else if (_hour >= 1) {
result = '发表于 ' + ~~(_hour) + ' 个小时前'
} else if (_min >= 1) {
result = '发表于 ' + ~~(_min) + ' 分钟前'
} else {
result = '刚刚'
}
return result
},
/**
* iView2 Tree 公用方法:选中指定ID节点,自动展开所有父节点
* @param {Array} treeData 树形原始数据
* @param {Number|String} targetId 要选中的节点id
* @param {Object} vueThis 当前Vue组件this实例
*/
selectTreeNodeById(treeData, targetId, vueThis) {
const loop = function (nodes, parentNode) {
for (let node of nodes) {
// 清空所有节点选中状态
vueThis.$set(node, 'selected', false)
if (node.id === targetId) {
// 目标节点设为选中
vueThis.$set(node, 'selected', true)
let p = parentNode
// 逐层向上展开父节点
while (p) {
vueThis.$set(p, 'expand', true)
p = p._parent
}
return true
}
if (node.children && node.children.length) {
// 绑定父节点引用
node.children.forEach(child => child._parent = node)
const res = loop(node.children, node)
if (res) return true
}
}
return false
}
loop(treeData, null)
},
strToHexCharCode(_str) {
if (_str === "") return "";
var hexCharCode = [];
for (var i = 0; i < _str.length; i++) {
var str = _str.charCodeAt(i).toString(16);
if (_str.length == 1) str += "0" + _str;
hexCharCode.push(str);
}
return hexCharCode.join("");
},
dateFormat(_date,fmt){
var date=new Date(_date);
var o = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"H+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
},
dataFilter(_key,arry) {
for (var key in arry) {
if (arry.hasOwnProperty(key)) {
var element = arry[key];
if(element.k==_key)
return element.v;
}
}
return '';
},
myUpload(url,fileObj,data,callback){
var FileController ="/api"+ url; // 接收上传文件的后台地址
if (!fileObj ||fileObj.length==0) {
console.error('请选择上传文件'); return;
}
// FormData 对象
var form = new FormData();
//表单数据
for (let key in data) {
const element = data[key];
form.append(key,element);
}
form.append("file", fileObj);// 文件对象
// XMLHttpRequest 对象
var xhr = new XMLHttpRequest();
xhr.open("post", FileController, true);
xhr.onload = function (resp) {
var obj = JSON.parse(resp.currentTarget.response);
if(callback)
callback(obj);
};
xhr.send(form);
},
comSetting(){//公共插件基础配置,如有需要,覆盖即可
const tree={
view: {
dblClickExpand: false,
showIcon: false,
},
check: {
enable: false,
},
callback:{}
}
return {
tree:tree,
}
},
comSetting1(){//公共插件基础配置,如有需要,覆盖即可
const tree={
view: {
dblClickExpand: false,
showIcon: false,
},
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: { prev: true, next: true,inner: false}
},
check: {
enable: false
},
data: {
simpleData: {
enable: true
}
},
callback: {
}
}
return {
tree:tree,
}
},
openFistNode(_zNodes){//展开一级节点
if(_zNodes&&_zNodes.length>0){
for (var key in _zNodes) {
if (_zNodes.hasOwnProperty(key)) {
var element = _zNodes[key];
element.open=true;
}
}
}
else{
_zNodes.open=true;
}
return _zNodes;
},
editorFormat(val){//富文本提取文字
return val.replace(/<[^>]+>/g, "");
},
mathFormat(val,number=2){//小数点截取,默认取2位
if(isNaN(val))return 0;
var num=Math.pow(10,number);
return Math.floor(val*num)/num;
},
fileSize(size){//返回文件大小
if(!size) return '';
if(size/1024<1024){
size=Math.ceil(size/1024);
return size+'KB';
}else if(size/(1024*1024)<1024){
size=Math.ceil(size/(1024*1024)*100)/100;
return size+'MB';
}else if(size/(1024*1024*1024)<1024){
size=Math.ceil(size/(1024*1024*1024)*100)/100;
return size+'GB';
}
},
handleValidate(_this,name){//表单验证
let isValid = false;
_this.$refs[name].validate(function (valid) {
if (valid) {
isValid = true;
} else {
// _this.$Message.info('表单验证失败!');
_this.$Message.info('请填写红色星号内容!');
}
})
return isValid;
},
handleReset(_this,resetName,name) {//表单重置
if(name&&_this.isModal&&_this.isModal[name]) _this.isModal[name] = false;
if (resetName) {
_this.$refs[resetName].resetFields();
}
},
downFile(id) {//下载文件
location.href = APIConfig.Base.FileDown + '?id=' + id;
},
localStorage:{
get(key){
return localStorage.getItem(key);
},
set(key,val){
localStorage.setItem(key,val);
}
}
}
+157
View File
@@ -0,0 +1,157 @@
<template>
<!-- 外层全屏容器,用于高度自适应 -->
<div class="page-wrap">
<!-- 顶部搜索栏 -->
<div class="search-wrap">
<div class="search-right">
<Input
v-model="keyword"
placeholder="请输入用户名、IP、模块或详细操作"
search
style="width:460px"
@on-search="searchData"
/>
</div>
</div>
<!-- 表格自适应滚动容器(核心滚动区域) -->
<div class="table-scroll-wrap">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
class="log-table"
/>
</div>
<!-- 底部分页:固定在容器底部,不会随表格滚动 -->
<div class="pagination-wrap">
<span class="total-text">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10, 20, 50, 100]"
show-sizer
@on-change="handlePageChange"
@on-page-size-change="handleSizeChange"
/>
</div>
</div>
</template>
<script>
export default {
name: 'OperateLog',
data() {
return {
keyword: '',
pageNum: 1,
pageSize: 10,
total: 0,
tableColumns: [
{ title: '序号', type: 'index', align: 'center', width: 70 },
{ title: '用户', key: 'operator', align: 'center', width: 130 },
{ title: 'IP', key: 'ip', align: 'center', width: 150 },
{ title: '模块', key: 'module', align: 'center', width: 160 },
{ title: '详细操作', key: 'content', align: 'left' },
{
title: '时间',
key: 'operateTime',
align: 'center',
width: 190,
render: (h, params) => {
return h("div", { style: "line-height: 56px" }, this.$utils.dateFormat(params.row.operateTime,'yyyy-MM-dd HH:mm:ss'));
}
}
],
tableData: []
}
},
mounted() {
this.loadLogData();
},
methods: {
// 请求日志列表
loadLogData() {
const _this = this;
var item = {
keyword:_this.keyword,
page:_this.pageNum-1,
size:_this.pageSize
};
_this.$api.get(APIConfig.BschClassSystem.systemSearch, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
searchData() {
this.pageNum = 1
this.loadLogData()
},
handlePageChange(page) {
this.pageNum = page
this.loadLogData()
},
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadLogData()
}
}
}
</script>
<style scoped>
/* 页面撑满父容器高度,必须设置父级html、body高度100% */
.page-wrap {
height: 100%;
display: flex;
flex-direction: column;
padding: 16px;
box-sizing: border-box;
background: #fff;
}
.search-wrap {
flex-shrink: 0; /* 固定高度,不压缩 */
background: #f7f7f7;
margin-bottom: 16px;
border-radius: 4px;
}
.search-right {
display: flex;
justify-content: flex-end;
align-items: center;
}
/* 表格容器:自动填充剩余高度,内容超出滚动 */
.table-scroll-wrap {
flex: 1;
overflow-y: auto;
margin-bottom: 16px;
}
.log-table {
width: 100%;
}
/* 分页区域:固定在底部,不参与滚动 */
.pagination-wrap {
flex-shrink: 0;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
}
.total-text {
font-size: 14px;
color: #666;
}
</style>
+39
View File
@@ -0,0 +1,39 @@
<template>
<div style="height:100%;">
<Christianity v-if="type==1"></Christianity>
<HigherEducation v-if="type==2"></HigherEducation>
</div>
</template>
<script>
import Christianity from "./classManage/Christianity";
import HigherEducation from "./classManage/HigherEducation";
export default {
name: 'ClassManage',
components: { Christianity,HigherEducation },
data() {
return {
type:0,
}
},
created(){
this.getType();
},
methods: {
//获取学制
getType:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getType, item, resp => {
if(resp.code==0)
{
_this.type = resp.data;
}else{
_this.type=1;
}
});
},
},
}
</script>
+786
View File
@@ -0,0 +1,786 @@
<template>
<div class="container-full-height">
<div class="layout-flex-row">
<!-- 左侧树形容器 高度撑满、内部滚动 -->
<div class="tree-wrap">
<my-tree :treeData="treeData" :selectId="selectnodeid" @on-tree-click="treeLink"></my-tree>
</div>
<!-- 右侧整体容器 高度撑满 -->
<div class="right-full-wrap">
<!-- 顶部按钮栏 固定高度不滚动 -->
<div class="top-btn-bar">
<Dropdown trigger="click" @on-click="changefieid">
<Button type="primary">
添加班级
<Icon type="ios-arrow-down"></Icon>
</Button>
<DropdownMenu slot="list">
<DropdownItem name="openAddSingleClass">添加单个班级</DropdownItem>
<DropdownItem name="openMultiNumModal">添加多个班级</DropdownItem>
</DropdownMenu>
</Dropdown>
<Button type="success" style="margin-left:10px" @click="openImportModal">班级信息导入</Button>
</div>
<!-- 表格容器:填充剩余高度,纵向滚动 -->
<div class="table-auto-scroll">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
></Table>
</div>
</div>
</div>
<!-- 弹窗1:添加多个班级 设置数量 -->
<Modal v-model="multiClassModal" title="设置添加班级数量" width="400">
<div style="padding:16px 8px;">
<div style="display:flex;align-items:center;">
<span style="width:80px;">班级数</span>
<Input v-model.number="addClassCount" type="number" min="1" />
</div>
</div>
<div slot="footer" align="right">
<Button @click="multiClassModal = false">取消</Button>
<Button type="primary" @click="submitBatchAdd">提交</Button>
</div>
</Modal>
<!-- 弹窗2:班级信息导入 -->
<Modal v-model="importModal" title="班级信息导入" width="560">
<!-- <div style="padding:18px 10px;display:flex;align-items:center;gap:20px;">
<Upload action="" :before-upload="beforeUpload">
<Button>选择要上传的文件</Button>
</Upload>
<a href="javascript:;" style="color:#2d7ff9" @click="downloadTemplate">点击链接下载[用户导入模板.xls]</a>
</div>
<div slot="footer" align="right">
<Button @click="importModal = false">取消</Button>
<Button type="primary" @click="confirmImport">确定</Button>
</div> -->
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<FormItem label="Excel上传:" style="margin-bottom:10px;width:100%;">
<i-input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></i-input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
:show-upload-list="false"
:headers="uploadHeader"
accept=".xls,.xlsx"
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.listData.data!=null" style="margin-bottom:10px">
<div v-if="UpParmas.listData.code==0" style="color:red">导入失败,请修改Excel文件中相关数据后再重新上传!</div>
<div v-if="UpParmas.listData.code!=0" style="color:green">导入成功!</div>
</FormItem>
</Form>
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[班级导入模板.xls]</a>
</template>
</Alert>
<Table :columns="UpParmas.columns" :data="UpParmas.listData.data" v-if="UpParmas.listData.data"></Table>
</div>
<Spin fix v-if="spinShow">
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</Modal>
<!-- 弹窗3:选择教师 -->
<Modal v-model="selectTeacherModal" title="选择教师" width="420">
<div style="padding:10px 5px;">
<div style="margin-bottom:16px;display:flex;align-items:center;">
<span style="width:75px;">科室</span>
<Select v-model="deptFilter" placeholder="按科室过滤" style="flex:1" @on-change="getteacherSelect" clearable>
<Option v-for="item in groupteacherSelectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
</div>
<div style="display:flex;align-items:center;">
<span style="width:75px;">教师</span>
<Select v-model="selectedTeacher" style="flex:1" filterable>
<Option v-for="item in teacherSelectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
</div>
</div>
<div slot="footer" align="right">
<Button @click="selectTeacherModal = false">取消</Button>
<Button type="primary" @click="confirmSelectTeacher">提交</Button>
</div>
</Modal>
<!-- 弹窗5:选择校区 -->
<Modal v-model="selectCampusModal" title="选择校区" width="420">
<div style="padding:10px 5px;">
<div style="display:flex;align-items:center;">
<span style="width:75px;">校区</span>
<Select v-model="selectedCampus" style="flex:1" placeholder="请选择">
<Option v-for="item in campusselectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
</div>
</div>
<div slot="footer" align="right">
<Button @click="selectCampusModal = false">取消</Button>
<Button type="primary" @click="confirmSelectCampus">提交</Button>
</div>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
export default {
components: { myTree},
name: 'ClassHeightFullPage',
data() {
return {
schsystemId:'',
selectnodeid:'',
treeData: [],
selectedTreeNode: null,
selectGrade: '预备',
multiClassModal: false,
addClassCount: 2,
importModal: false,
spinShow:false,
selectTeacherModal: false,
editRowIndex: null,
editField: '',
deptFilter: '',
selectedTeacher: '',
selectCampusModal: false,
campusEditIndex: null,
selectedCampus: '',
tableColumns: [],
tableData: [],
campusselectlist:[],
groupteacherSelectlist:[],
teacherSelectlist:[],
UpParmas:{
fileName:'',
listData:{},
datapre:{},
actionUrl:APIConfig.BschClassSystem.schclassImport,
columns:[
{
title: '行号',
key: 'rows',
align: 'center',
width:'80'
},
{
title: '内容',
key: 'columnValue',
align: 'center',
},
{
title: '错误详情',
key: 'errorMsg',
align: 'center',
}
],
},
}
},
computed: {
uploadHeader() { //附件上传header
let config = {};
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
config["currentRole"] = currentRole;
}
return config;
},
},
created() {
this.getGradeTree();
this.getgroupteacherSelect();
this.getteacherSelect();
this.getcampusselect();
this.initTableColumn();
},
mounted() {
},
methods: {
//获取年级树
getGradeTree:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getGradeTree, item, resp => {
if(resp.code==0)
{
_this.treeData = resp.data;
if(resp.data.children.length>0){
// 起始节点为 data
const pathArr = _this.getFirstPath(resp.data);
_this.selectnodeid=pathArr[pathArr.length-1].id;
_this.schsystemId=pathArr[pathArr.length-1].schsystemId;
_this.getsearcBaseSchClass();
}
}else{
_this.treeData=[];
}
});
},
//获取最后一级的map
getFirstPath(rootNode) {
const path = [];
let cur = rootNode;
while (cur) {
// 存入当前层级关键信息
path.push({
id: cur.id,
name: cur.name,
type: cur.type,
schsystemId:cur.schsystemId,
gradeId:cur.gradeId,
classId:cur.classId,
});
// 有子节点就取第一个,没有就终止
if (cur.children && cur.children.length > 0) {
cur = cur.children[0];
} else {
cur = null;
}
}
return path;
},
//获取班级列表
getsearcBaseSchClass:function(){
const _this = this;
var item={
schsystemId:this.schsystemId,
gradeId:this.selectnodeid
};
_this.$api.get(APIConfig.BschClassSystem.getsearcBaseSchClass, item, resp => {
if(resp.code==0)
{
_this.tableData = resp.data;
}else{
_this.tableData=[];
}
});
},
//教师组下拉
getgroupteacherSelect:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getgroupteacherSelect, item, resp => {
if(resp.code==0)
{
_this.groupteacherSelectlist= resp.data;
}else{
_this.groupteacherSelectlist=[];
}
});
},
//教师下拉
getteacherSelect:function(){
const _this = this;
var item={
groupId:_this.deptFilter
};
_this.$api.get(APIConfig.BschClassSystem.getteacherSelect, item, resp => {
if(resp.code==0)
{
_this.teacherSelectlist= resp.data;
}else{
_this.teacherSelectlist=[];
}
});
},
//校区下拉
getcampusselect:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getcampusselect, item, resp => {
if(resp.code==0)
{
_this.campusselectlist= resp.data;
}else{
_this.campusselectlist=[];
}
});
},
//节点点击事件
treeLink(treeNode){
this.schsystemId=treeNode.schsystemId;
this.selectnodeid=treeNode.id;
this.getsearcBaseSchClass();
},
initTableColumn() {
this.tableColumns = [
{ title: '序号', type: 'index', align: 'center', width: 70 },
{ title: '班级名称', key: 'name', align: 'center', width: 100 },
{
title: '班级别名',
align: 'center',
render: (h, params) => {
return h('Input', {
props: { value: params.row.cname },
style: { width: '150px' },
on: {
input: (val) => {
this.$set(params.row, 'classAlias', val)
this.cellChange(params.index, 'classAlias', val)
}
}
})
}
},
{
title: '班主任',
align: 'center',
width: 160,
render: (h, params) => {
return h('div', {
style: {
display: 'flex',
alignItems: 'center',
width: '140px',
height: '34px',
border: '1px solid #dcdcdc',
borderRadius: '4px',
cursor: 'pointer',
margin: '0 auto'
},
on: {
click: () => this.openTeacherSelect(params.index, 'headTeacher')
}
}, [
h('span', {
style: {
flex: 1,
textAlign: 'center',
fontSize: '14px',
paddingLeft: '6px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}, params.row.firstTeacherName || ''),
params.row.firstTeacherName ? h('span', {
style: {
width: '22px',
height: '22px',
lineHeight: '22px',
textAlign: 'center',
color: 'red',
borderLeft: '1px solid #eee',
flexShrink: 0,
fontSize: '14px'
},
on: {
click: (e) => {
e.stopPropagation()
var item={
id:params.row.id,
firstTeacherId:null
};
let url=APIConfig.BschClassSystem.editClassFirstTeacher;
this.$api.post(url, item, resp => {
if(resp.code==0)
{
this.$Message.success(`设置教师成功`);
this.getsearcBaseSchClass();
}
});
}
}
}, '×') : null
])
}
},
{
title: '副班主任',
align: 'center',
width: 160,
render: (h, params) => {
return h('div', {
style: {
display: 'flex',
alignItems: 'center',
width: '140px',
height: '34px',
border: '1px solid #dcdcdc',
borderRadius: '4px',
cursor: 'pointer',
margin: '0 auto'
},
on: {
click: () => this.openTeacherSelect(params.index, 'secondTeacherName')
}
}, [
h('span', {
style: {
flex: 1,
textAlign: 'center',
fontSize: '14px',
paddingLeft: '6px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}, params.row.secondTeacherName || ''),
params.row.secondTeacherName ? h('span', {
style: {
width: '22px',
height: '22px',
lineHeight: '22px',
textAlign: 'center',
color: 'red',
borderLeft: '1px solid #eee',
flexShrink: 0,
fontSize: '14px'
},
on: {
click: (e) => {
e.stopPropagation()
var item={
id:params.row.id,
secondTeacherId:null
};
let url=APIConfig.BschClassSystem.editClassSecondTeacher;
this.$api.post(url, item, resp => {
if(resp.code==0)
{
this.$Message.success(`设置教师成功`);
this.getsearcBaseSchClass();
}
});
}
}
}, '×') : null
])
}
},
{
title: '校区',
align: 'center',
width: 160,
render: (h, params) => {
return h('div', {
style: {
display: 'flex',
alignItems: 'center',
width: '140px',
height: '34px',
border: '1px solid #dcdcdc',
borderRadius: '4px',
cursor: 'pointer',
margin: '0 auto'
},
on: {
click: () => this.openCampusSelect(params.index)
}
}, [
h('span', {
style: {
flex: 1,
textAlign: 'center',
fontSize: '14px',
paddingLeft: '6px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}, params.row.campusName || ''),
params.row.campusName ? h('span', {
style: {
width: '22px',
height: '22px',
lineHeight: '22px',
textAlign: 'center',
color: 'red',
borderLeft: '1px solid #eee',
flexShrink: 0,
fontSize: '14px'
},
on: {
click: (e) => {
e.stopPropagation()
var item={
id:params.row.id,
campusId:null
};
let url=APIConfig.BschClassSystem.editClassCampus;
this.$api.post(url, item, resp => {
if(resp.code==0)
{
this.$Message.success(`校区设置成功`);
this.getsearcBaseSchClass();
}
});
}
}
}, '×') : null
])
}
},
{
title: '操作',
align: 'center',
width: 100,
render: (h, params) => {
return h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'center' } }, [
h('Button', {
props: { icon: 'ios-eye-outline', type: 'text', size: 'large' },
on: { click: () => this.viewDetail(params.row) }
}),
h('Button', {
props: { icon: 'ios-trash-outline', type: 'text', color: 'red', size: 'large',disabled:params.row.disableDel },
on: { click: () => this.deleteRow(params.index, params.row) }
})
])
}
}
]
},
loadTableData() {
console.log('当前筛选年级:', this.selectGrade)
// 替换后端请求
},
cellChange(rowIndex, field, value) {
var item={
id:this.tableData[rowIndex].id,
cname:value
};
this.$api.post(APIConfig.BschClassSystem.editClassName, item, resp => {
if(resp.code==0)
{
// this.$Message.success(`设置班级名称成功`);
this.getsearcBaseSchClass();
}
});
},
//自定义类型添加
changefieid:function(name){
var _this=this;
if(name=="openAddSingleClass")
{
var item={
gradeId:_this.selectnodeid,
number:1
};
_this.$api.post(APIConfig.BschClassSystem.addBaseSchClass, item, resp => {
if(resp.code==0)
{
this.$Message.success(`新增1个班级!`);
this.getsearcBaseSchClass();
}
});
}else if(name=="openMultiNumModal")
{
this.addClassCount = 2
this.multiClassModal = true
}
},
//批量增加班级
submitBatchAdd() {
if (this.addClassCount < 1) return this.$Message.warning('班级数量不能小于1');
var item={
gradeId:this.selectnodeid,
number:this.addClassCount
};
this.$api.post(APIConfig.BschClassSystem.addBaseSchClass, item, resp => {
if(resp.code==0)
{
this.$Message.success(`批量新增${this.addClassCount}个班级`);
this.getsearcBaseSchClass();
this.multiClassModal = false;
}
});
},
openImportModal() {
this.importModal = true
},
//导入成功返回
importFile(response, file, fileList){
const _this=this;
console.log(response);
console.log(file);
_this.spinShow=false;
_this.UpParmas.fileName=file.name;
_this.UpParmas.listData=response;
if(response.State)
{
_this.$Message.info('导入成功!');
_this.getsearcBaseSchClass();
}else{
_this.$Message.info(response.Message);
}
},
//附件上传之前的方法
handleUpload:function(file){
const _this=this;
_this.spinShow=true;
},
MoBanExceldaochu() {
var _this = this;
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
var url=APIConfig.BschClassSystem.schclassdownloadTemplate+'?schId='+schId;
window.location.href=url;
}else{
this.$Message.info('获取不了学校!');
}
},
//附件上传模态框关闭事件
UpfileModalcloseclick:function(){
this.importModal=false;
this.UpParmas={
fileName:'',
listData:{},
datapre:{},
}
},
openTeacherSelect(index, field) {
this.editRowIndex = index
this.editField = field
this.deptFilter = ''
this.selectedTeacher =field=='headTeacher'? this.tableData[index].firstTeacherId:this.tableData[index].secondTeacherId;
this.selectTeacherModal = true
},
confirmSelectTeacher() {
if (!this.selectedTeacher) return this.$Message.warning('请选择教师');
var item={
id:this.tableData[this.editRowIndex].id
};
if(this.editField=='headTeacher'){
item.firstTeacherId=this.selectedTeacher;
}else{
item.secondTeacherId=this.selectedTeacher;
}
let url=this.editField=='headTeacher'?APIConfig.BschClassSystem.editClassFirstTeacher:APIConfig.BschClassSystem.editClassSecondTeacher;
this.$api.post(url, item, resp => {
if(resp.code==0)
{
this.$Message.success(`设置教师成功`);
this.getsearcBaseSchClass();
this.selectTeacherModal = false;
}
});
},
openCampusSelect(index) {
this.campusEditIndex = index
this.selectedCampus = this.tableData[index].campusId;
this.selectCampusModal = true
},
confirmSelectCampus() {
var item={
id:this.tableData[this.campusEditIndex].id,
campusId:this.selectedCampus
};
let url=APIConfig.BschClassSystem.editClassCampus;
this.$api.post(url, item, resp => {
if(resp.code==0)
{
this.$Message.success(`校区设置成功`);
this.getsearcBaseSchClass();
this.selectCampusModal = false;
}
});
},
viewDetail(row) {
this.$router.push('/ChristianityDetail?gradeId='+row.gradeId+'&classId='+row.classId+'&gradeclassName='+row.gradeName+row.name);
},
deleteRow(index, row) {
this.$Modal.confirm({
title: '确认删除',
content: `确定删除班级「${row.name}」吗?`,
onOk: () => {
var url=APIConfig.BschClassSystem.deleteBaseSchClass+this.tableData[index].id;
this.$api.post(url,{},(resp)=>{
this.$Message.success('删除成功!');
this.getsearcBaseSchClass();
});
}
})
}
}
}
</script>
<style scoped>
/* 根容器占满父高度 */
.container-full-height {
width: 100%;
height: 100%;
box-sizing: border-box;
border: 1px solid #e5e7eb;
border-radius: 6px;
padding: 12px;
background: #fff;
display: flex;
flex-direction: column;
}
.layout-flex-row {
display: flex;
flex: 1;
gap: 16px;
min-height: 0;
}
/* 左侧树形 压到底部、内部滚动 */
.tree-wrap {
width: 220px;
border: 1px solid #e8e8e8;
padding: 8px;
overflow-y: auto;
flex-shrink: 0;
}
/* 右侧整体容器 高度填充 */
.right-full-wrap {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
}
/* 顶部按钮栏 固定不滚动 */
.top-btn-bar {
padding-bottom: 14px;
flex-shrink: 0;
}
/* 表格区域:自动填充剩余高度,纵向滚动 */
.table-auto-scroll {
flex: 1;
overflow: auto;
min-height: 0;
}
</style>
<style>
.ivu-alert-desc{
font-size: 12px;
}
</style>
@@ -0,0 +1,363 @@
<template>
<div class="outer-wrap">
<!-- 顶部栏:Tab + 班级名称 + 返回 -->
<div class="header-bar">
<Tabs v-model="activeTab" type="line">
<TabPane label="任教信息" name="teacher"></TabPane>
<TabPane label="学生列表" name="student"></TabPane>
</Tabs>
<div class="class-title-box">
<span class="class-name">{{ $route.query.gradeclassName }}</span>
<Button text @click="backPage">
<Icon type="ios-arrow-back"></Icon>
</Button>
</div>
</div>
<!-- 任教信息页面 -->
<div v-if="activeTab === 'teacher'" class="teacher-wrap">
<div class="subject-container">
<div
v-for="(item, idx) in subjectList"
:key="idx"
class="subject-card"
@click="openSetTeacher(idx)"
>
<div class="subject-name">{{ item.subName }}</div>
<div class="teacher-name-wrap">
<span class="teacher-name">{{ item.teacherName || '点击设置老师' }}</span>
<span
v-if="item.teacherId"
class="close-btn"
@click.stop="clearTeacher(idx)"
>×</span>
</div>
</div>
</div>
</div>
<!-- 学生列表页面 -->
<div v-if="activeTab === 'student'" class="student-wrap">
<div class="table-scroll-box">
<Table
border
stripe
:columns="studentColumns"
:data="studentData"
:height="useTableHeight()+82"
class="student-table"
></Table>
</div>
<!-- <div class="pagination-bottom">
<span class="total-text">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10,20,50]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div> -->
</div>
<!-- 设置教师弹窗 -->
<Modal v-model="modalVisible" title="选择教师" width="400">
<Form ref="form" :model="formData" :rules="formRules" label-width="90">
<FormItem label="科室">
<Select v-model="formData.dept" placeholder="按科室过滤" style="width:100%" @on-change="getteacherSelect" clearable transfer>
<Option v-for="d in groupteacherSelectlist" :key="d.id" :value="d.id">{{ d.name }}</Option>
</Select>
</FormItem>
<FormItem label="教师">
<Select v-model="formData.teacher" placeholder="请选择" style="width:100%" filterable transfer>
<Option v-for="t in teacherSelectlist" :key="t.id" :value="t.id">{{ t.name }}</Option>
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="modalVisible = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitSelectTeacher">提交</Button>
</div>
</Modal>
</div>
</template>
<script>
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'ChristianityDetail',
data() {
return {
activeTab: 'teacher',
// 当前编辑科目下标
currentEditIndex: null,
// 科目列表
subjectList: [],
// 科室、教师数据源
groupteacherSelectlist: [],
teacherSelectlist: [],
modalVisible: false,
formData: { dept: '', teacher: '' },
formRules: {
dept: [{ required: true, message: '请选择科室', trigger: 'change' }],
teacher: [{ required: true, message: '请选择教师', trigger: 'change' }]
},
// 学生列表分页
pageNum: 1,
pageSize: 10,
total: 0,
studentColumns: [
{ title: '序号', type: 'index', align: 'center', width: 70 },
{ title: '姓名', key: 'name', align: 'center' },
{
title: '性别',
key: 'gender',
align: 'center',
width: 70,
render: (h, params) => {
return h('div', [
params.row.gender==0?'女':params.row.gender==1?'男':''
]);
},
},
{ title: '年级', key: 'gradeName', align: 'center' },
{ title: '班级', key: 'className', align: 'center' },
{ title: '身份证', key: 'idCard', align: 'center' }
],
studentData: []
}
},
watch: {
// 切换标签页重新初始化拖拽
activeTab(val) {
if (val === 'teacher') {
this.getSchClassTeach();
} else{
this.searchSchClassStudent();
}
}
},
mixins: [self_viewport],
created(){
this.getSchClassTeach();
this.getgroupteacherSelect();
this.getteacherSelect();
},
methods: {
// 返回上一页
backPage() {
this.$router.back()
},
//通过班级ID获取任教
getSchClassTeach(){
const _this = this;
var item={
gradeId:_this.$route.query.gradeId,
classId:_this.$route.query.classId
};
_this.$api.get(APIConfig.BschClassSystem.getSchClassTeach, item, resp => {
if(resp.code==0)
{
_this.subjectList = resp.data;
}else{
_this.subjectList=[];
}
});
},
//通过班级ID获取学生
searchSchClassStudent(){
const _this = this;
var item={
gradeId:_this.$route.query.gradeId,
classId:_this.$route.query.classId
};
_this.$api.get(APIConfig.BschClassSystem.searchSchClassStudent, item, resp => {
if(resp.code==0)
{
_this.studentData = resp.data;
}else{
_this.studentData=[];
}
});
},
// 教师组下拉
getgroupteacherSelect() {
const _this = this;
_this.$api.get(APIConfig.BschClassSystem.getgroupteacherSelect, {}, resp => {
if (resp.code == 0) {
_this.groupteacherSelectlist = resp.data;
} else {
_this.groupteacherSelectlist = [];
}
});
},
// 教师下拉
getteacherSelect() {
const _this = this;
const item = { groupId: _this.selectDept };
_this.$api.get(APIConfig.BschClassSystem.getteacherSelect, item, resp => {
if (resp.code == 0) {
_this.teacherSelectlist = resp.data;
if (_this.oldTeacherId) {
const findT = _this.teacherSelectlist.find(t => t.id === _this.oldTeacherId);
if (findT) {
_this.selectTeacher = findT.id;
}
}
} else {
_this.teacherSelectlist = [];
}
});
},
// 打开设置教师弹窗
openSetTeacher(index) {
this.currentEditIndex = index
this.modalVisible = true
this.formData = { dept: '', teacher:this.subjectList[index].teacherId?this.subjectList[index].teacherId:'' }
},
// 移除当前科目教师
clearTeacher(index) {
this.$Modal.confirm({
title: '提示',
content: '确定移除该任课教师?',
onOk: () => {
this.$api.post(APIConfig.BschClassSystem.teachsdelete+this.subjectList[index].id, {}, res => {
if (res.code === 0) {
this.$Message.success('移除教师成功');
this.getSchClassTeach();
}
});
}
})
},
// 弹窗提交分配教师
submitSelectTeacher() {
if (!this.formData.teacher) {
this.$Message.warning('请选择教师');
return;
}
const row = this.subjectList[this.currentEditIndex];
const params = {
id: row.id,
gradeId:this.$route.query.gradeId,
classId:this.$route.query.classId,
subId: row.subId,
teaId: this.formData.teacher
};
this.$api.post(APIConfig.BschClassSystem.teachssave, JSON.stringify(params), res => {
if (res.code === 0) {
this.$Message.success('分配教师成功');
this.modalVisible = false;
this.getSchClassTeach();
}
});
},
}
}
</script>
<style scoped>
.outer-wrap {
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 16px;
width: 100%;
height: 100%;
box-sizing: border-box;
background: #fff;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.class-title-box {
display: flex;
align-items: center;
gap: 8px;
}
.class-name {
font-size: 18px;
font-weight: 500;
}
/* 任教信息卡片布局 */
.teacher-wrap {
flex: 1;
}
.subject-container {
display: flex;
flex-wrap: wrap;
gap: 12px;
row-gap: 16px;
}
.subject-card {
width: 180px;
border: 1px solid #cad6f0;
border-radius: 4px;
padding: 12px 14px;
cursor: pointer;
}
.subject-name {
font-size: 14px;
color: #333;
margin-bottom: 6px;
}
.teacher-name-wrap {
display: flex;
justify-content: space-between;
align-items: center;
}
.teacher-name {
font-size: 14px;
color: #4080ff;
}
.close-btn {
width: 18px;
height: 18px;
line-height: 18px;
text-align: center;
border-radius: 50%;
background: #eee;
font-size: 14px;
color: #666;
}
/* 学生列表布局 */
.student-wrap {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.table-scroll-box {
flex: 1;
overflow-y: auto;
}
.student-table {
width: 100%;
}
.pagination-bottom {
margin-top: 16px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
}
.total-text {
font-size: 14px;
color: #666;
}
</style>
+663
View File
@@ -0,0 +1,663 @@
<template>
<div class="outer-wrap">
<div class="page-container">
<!-- 左侧年级树形 -->
<div class="tree-left">
<my-tree :treeData="treeData" :selectId="selectnodeid" @on-tree-click="treeLink"></my-tree>
</div>
<!-- 右侧主体区域 -->
<div class="right-main">
<!-- 顶部操作筛选栏 -->
<div class="top-bar">
<div class="btn-group">
<Button type="primary" @click="openAddModal">添加班级</Button>
<Button type="success" @click="handleImport">导入班级</Button>
</div>
<div class="filter-group">
<Select v-model="filterCampus" placeholder="选择所属校区" style="width:150px;margin-right:10px" @on-change="searchList" clearable>
<Option v-for="item in campusselectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<Select v-model="filterDept" placeholder="选择所属系部" style="width:150px;margin-right:10px" @on-change="searchList" clearable>
<Option v-for="item in divisionselectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<Select v-model="filterMajor" placeholder="选择所属专业" style="width:150px;margin-right:10px" @on-change="searchList" clearable filterable>
<Option v-for="item in majorsselectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<Input
v-model="keyword"
placeholder="输入代码或名称搜索"
style="width:200px"
@keyup.enter="searchList"
>
<Icon slot="suffix" type="ios-search"></Icon>
</Input>
</div>
</div>
<!-- 自适应表格容器 -->
<div class="table-adapt-wrap">
<div class="table-scroll-box">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
class="class-table"
></Table>
</div>
<!-- 分页固定底部 -->
<div class="pagination-bottom">
<span class="total-text">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10,20,50]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
</div>
</div>
<!-- 新增/编辑班级弹窗 -->
<Modal v-model="modalVisible" :title="modalTitle" width="520">
<Form ref="form" :model="formData" :rules="formRules" label-width="110">
<FormItem label="班级代码">
<Input v-model="formData.classCode" placeholder="请输入班级代码"></Input>
</FormItem>
<FormItem label="班级名称" prop="className">
<Input v-model="formData.className" placeholder="请输入班级名称"></Input>
</FormItem>
<FormItem label="班级别名">
<Input v-model="formData.alias" placeholder="请输入班级别名"></Input>
</FormItem>
<FormItem label="所在校区">
<Select v-model="formData.campus" placeholder="请选择校区" style="width:100%" transfer>
<Option v-for="item in campusselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="所属系部" prop="dept">
<Select v-model="formData.dept" placeholder="请选择系部" style="width:100%" transfer>
<Option v-for="item in divisionselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="所属专业" prop="major">
<Select v-model="formData.major" placeholder="请选择专业" style="width:100%" transfer>
<Option v-for="item in majorsselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="班主任">
<Select v-model="formData.teacher" placeholder="请选择班主任" style="width:100%" transfer>
<Option v-for="item in teacherSelectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="modalVisible = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitForm">提交</Button>
</div>
</Modal>
<!-- 班级信息导入 -->
<Modal v-model="importModal" title="班级信息导入" width="560">
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<FormItem label="Excel上传:" style="margin-bottom:10px;width:100%;">
<i-input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></i-input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
:show-upload-list="false"
:headers="uploadHeader"
accept=".xls,.xlsx"
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.listData.data!=null" style="margin-bottom:10px">
<div v-if="UpParmas.listData.code==0" style="color:red">导入失败,请修改Excel文件中相关数据后再重新上传!</div>
<div v-if="UpParmas.listData.code!=0" style="color:green">导入成功!</div>
</FormItem>
</Form>
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[班级导入模板.xls]</a>
</template>
</Alert>
<Table :columns="UpParmas.columns" :data="UpParmas.listData.data" v-if="UpParmas.listData.data"></Table>
</div>
<Spin fix v-if="spinShow">
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
export default {
name: 'HigherEducation',
components: { myTree},
data() {
return {
// 左侧树形数据
treeData: [],
selectnodeid: new Date().getFullYear()-1,
// 顶部筛选
keyword: '',
filterCampus: '',
filterDept: '',
filterMajor: '',
// 分页
pageNum: 1,
pageSize: 10,
total: 0,
// 下拉选项数据源
campusselectlist: [],
divisionselectlist: [],
majorsselectlist: [],
teacherSelectlist: [],
// iView2 表格列配置(无TableColumn,columns标准写法)
tableColumns: [
{ title: '序号', type: 'index', align: 'center', width: 70 },
{ title: '名称', key: 'name', align: 'center' },
{ title: '编号', key: 'code', align: 'center' },
{ title: '专业', key: 'majorsName', align: 'center' },
{ title: '系部', key: 'divisionName', align: 'center' },
{ title: '班主任', key: 'firstTeacherName', align: 'center' },
{ title: '校区', key: 'campusName', align: 'center' },
{ title: '别名', key: 'cname', align: 'center' },
{ title: '年级', key: 'gradeName', align: 'center' },
{
title: '操作',
key: 'action',
align: 'center',
width: 130,
render: (h, params) => {
return h('div', { style: 'display:flex;gap:8px;justify-content:center' }, [
h('Button', {
props: { type: 'text', icon: 'ios-eye', size: 'large' },
on: { click: () => this.viewDetail(params.row) }
}),
h('Button', {
props: { type: 'text', icon: 'ios-create', size: 'large' },
on: { click: () => this.editRow(params.row, params.index) }
}),
h('Button', {
props: { type: 'text', icon: 'ios-trash', size: 'large' },
on: { click: () => this.delRow(params.index) }
})
])
}
}
],
// 表格模拟数据
tableData: [],
UpParmas:{
fileName:'',
listData:{},
datapre:{},
actionUrl:APIConfig.BschClassSystem.importHeightClasses,
columns:[
{
title: '行号',
key: 'rows',
align: 'center',
width:'80'
},
{
title: '内容',
key: 'columnValue',
align: 'center',
},
{
title: '错误详情',
key: 'errorMsg',
align: 'center',
}
],
},
// 弹窗控制
modalVisible: false,
importModal:false,
modalTitle: '',
editIndex: null,
formData: {
classCode: '',
className: '',
alias: '',
campus: '',
dept: '',
major: '',
teacher: ''
},
formRules: {
classCode: [{ required: true, message: '请输入班级代码', trigger: 'blur' }],
className: [{ required: true, message: '请输入班级名称', trigger: 'blur' }],
alias: [{ required: true, message: '请输入班级别名', trigger: 'blur' }],
campus: [{ required: true, message: '请选择所在校区', trigger: 'change' }],
dept: [{ required: true, message: '请选择所属系部', trigger: 'change' }],
major: [{ required: true, message: '请选择所属专业', trigger: 'change' }],
teacher: [{ required: true, message: '请选择班主任', trigger: 'change' }]
}
}
},
computed: {
uploadHeader() { //附件上传header
let config = {};
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
config["currentRole"] = currentRole;
}
return config;
},
},
created(){
this.getGradeTree();
this.getcampusselect();
this.getdivisionselect();
this.getmajorsselect();
this.getteacherSelect();
this.getsearcHigherSchClass();
},
methods: {
//获取年级树
getGradeTree:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getHigherSchGradeTree, item, resp => {
if(resp.code==0)
{
_this.treeData = resp.data;
if(resp.data.children.length>0){
// 起始节点为 data
const pathArr = _this.getFirstPath(resp.data);
_this.selectnodeid=pathArr[pathArr.length-1].id;
}
}else{
_this.treeData=[];
}
});
},
//获取最后一级的map
getFirstPath(rootNode) {
const path = [];
let cur = rootNode;
while (cur) {
// 存入当前层级关键信息
path.push({
id: cur.id,
name: cur.name,
type: cur.type,
schsystemId:cur.schsystemId,
gradeId:cur.gradeId,
classId:cur.classId,
});
// 有子节点就取第一个,没有就终止
if (cur.children && cur.children.length > 0) {
cur = cur.children[0];
} else {
cur = null;
}
}
return path;
},
//节点点击事件
treeLink(treeNode){
this.selectnodeid=treeNode.id;
this.getsearcHigherSchClass();
},
//校区下拉
getcampusselect() {
const _this = this;
var item = {};
_this.$api.get(APIConfig.BschClassSystem.getcampusselect, item, resp => {
if (resp.code == 0) {
_this.campusselectlist = resp.data;
} else {
_this.campusselectlist = [];
}
});
},
//系部下拉
getdivisionselect() {
const _this = this;
var item = {};
_this.$api.get(APIConfig.BschClassSystem.getdivisionselect, item, resp => {
if (resp.code == 0) {
_this.divisionselectlist = resp.data;
} else {
_this.divisionselectlist = [];
}
});
},
//专业下拉
getmajorsselect() {
const _this = this;
var item = {};
_this.$api.get(APIConfig.BschClassSystem.getmajorsselect, item, resp => {
if (resp.code == 0) {
_this.majorsselectlist = resp.data;
} else {
_this.majorsselectlist = [];
}
});
},
//教师下拉
getteacherSelect:function(){
const _this = this;
var item={
groupId:_this.deptFilter
};
_this.$api.get(APIConfig.BschClassSystem.getteacherSelect, item, resp => {
if(resp.code==0)
{
_this.teacherSelectlist= resp.data;
}else{
_this.teacherSelectlist=[];
}
});
},
//获取班级列表
getsearcHigherSchClass() {
const _this = this;
var item = {
year:_this.selectnodeid,
campusId:_this.filterCampus,
divisionId:_this.filterDept,
majorsId:_this.filterMajor,
keyword:_this.keyword,
page:_this.pageNum-1,
size:_this.pageSize
};
_this.$api.get(APIConfig.BschClassSystem.getsearcHigherSchClass, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
// 搜索
searchList() {
this.pageNum = 1
this.getsearcHigherSchClass();
},
// 分页
pageChange(page) {
this.pageNum = page;
this.getsearcHigherSchClass();
},
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.getsearcHigherSchClass();
},
// 导入班级
handleImport() {
this.importModal = true
},
//导入成功返回
importFile(response, file, fileList){
const _this=this;
console.log(response);
console.log(file);
_this.spinShow=false;
_this.UpParmas.fileName=file.name;
_this.UpParmas.listData=response;
if(response.State)
{
_this.$Message.info('导入成功!');
_this.getsearcHigherSchClass();
}else{
_this.$Message.info(response.Message);
}
},
//附件上传之前的方法
handleUpload:function(file){
const _this=this;
_this.spinShow=true;
},
MoBanExceldaochu() {
var _this = this;
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
var url=APIConfig.BschClassSystem.downloadHeightTemplate+'?schId='+schId;
window.location.href=url;
}else{
this.$Message.info('获取不了学校!');
}
},
//附件上传模态框关闭事件
UpfileModalcloseclick:function(){
this.importModal=false;
this.UpParmas={
fileName:'',
listData:{},
datapre:{},
}
},
// 新增弹窗
openAddModal() {
this.editIndex = null
this.modalTitle = '新增班级信息'
this.formData = {
classCode: '',
className: '',
alias: '',
campus: '',
dept: '',
major: '',
teacher: ''
}
this.modalVisible = true
},
// 查看详情
viewDetail(row) {
this.$router.push('/ChristianityDetail?id='+row.id);
},
// 编辑回填
editRow(row, idx) {
this.editIndex = idx
this.modalTitle = '编辑班级信息'
this.formData = {
classCode: row.code,
className: row.name,
alias: row.cname,
campus: row.campusId?row.campusId+'':'',
dept: row.divisionId?row.divisionId+'':'',
major: row.majorsId?row.majorsId+'':'',
teacher:row.firstTeacherId?row.firstTeacherId+'':''
}
this.modalVisible = true
},
// 删除
delRow(idx) {
this.$Modal.confirm({
title:'提示',
content:'确定删除该班级?',
onOk:()=>{
var url=APIConfig.BschClassSystem.deleteHigherSchClass+this.tableData[index].id;
this.$api.post(url,{},(resp)=>{
this.$Message.success('删除成功!');
this.getsearcHigherSchClass();
});
}
})
},
// 提交表单
submitForm() {
this.$refs.form.validate(valid=>{
if(!valid) return
const form = this.formData
if(this.editIndex === null) {
// 新增
const newItem = {
code: form.classCode,
name: form.className,
cname: form.alias,
campusId: form.campus,
divisionId: form.dept,
majorsId: form.major,
firstTeacherId: form.teacher,
enrollYear: this.selectnodeid
}
const url =APIConfig.BschClassSystem.saveHigherSchClass;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("新增班级成功!");
this.getsearcHigherSchClass();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
} else {
// 编辑
const newItem = {
id:this.tableData[this.editIndex].id,
code: form.classCode,
name: form.className,
cname: form.alias,
campusId: form.campus,
divisionId: form.dept,
majorsId: form.major,
firstTeacherId: form.teacher,
enrollYear: this.selectnodeid
}
const url =APIConfig.BschClassSystem.saveHigherSchClass;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("编辑班级成功!");
this.getsearcHigherSchClass();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
this.modalVisible = false
})
}
}
}
</script>
<style scoped>
/* 外层统一边框,和前面所有管理页面样式统一 */
.outer-wrap {
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 16px;
width: 100%;
height: 100%;
box-sizing: border-box;
background: #fff;
display: flex;
flex-direction: column;
}
.page-container {
display: flex;
flex: 1;
gap: 16px;
min-height: 0;
}
/* 左侧树形区域 */
.tree-left {
width: 220px;
border: 1px solid #e8e8e8;
padding: 8px;
overflow-y: auto;
}
/* 右侧整体 */
.right-main {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
/* 顶部筛选栏 */
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: #f7f7f7;
margin-bottom: 16px;
flex-shrink: 0;
}
.btn-group {
display: flex;
gap: 10px;
}
.filter-group {
display: flex;
align-items: center;
}
/* 表格自适应容器 */
.table-adapt-wrap {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.table-scroll-box {
flex: 1;
overflow-y: auto;
}
.class-table {
width: 100%;
}
/* 分页固定底部 */
.pagination-bottom {
margin-top: 16px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.total-text {
font-size: 14px;
color: #666;
}
</style>
<style>
.ivu-alert-desc{
font-size: 12px;
}
</style>
+179
View File
@@ -0,0 +1,179 @@
<template>
<div class="oper-log-wrap">
<!-- 顶部搜索栏 -->
<div class="top-search-bar">
<div></div>
<div class="search-box">
<Input
v-model="searchKeyword"
placeholder="请输入用户名、IP、模块或详细操作"
search
style="width:460px"
@on-search="handleSearch"
/>
</div>
</div>
<!-- 自适应滚动表格容器 -->
<div class="table-scroll-wrap">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
:height="useTableHeight()+32"
></Table>
</div>
<!-- 底部分栏:分页固定底部 -->
<div class="page-bottom-bar">
<div></div>
<div class="page-wrap">
<span style="margin-right:12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,50,100]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
</div>
</template>
<script>
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'OperationLogPage',
data() {
return {
// 搜索关键词
searchKeyword: '',
// 分页参数
pageNum: 1,
pageSize: 10,
total: 0,
// 表格配置列
tableColumns: [],
// 模拟表格数据
tableData: []
}
},
mixins: [self_viewport],
created() {
this.initTableColumn()
this.loadLogData()
},
methods: {
// 初始化配置式表格列
initTableColumn() {
this.tableColumns = [
{ title: '序号', type: 'index', align: 'center', width: 70 },
{ title: '用户', key: 'operator', align: 'center', width: 130 },
{ title: 'IP', key: 'ip', align: 'center', width: 150 },
{ title: '模块', key: 'module', align: 'center', width: 160 },
{ title: '详细操作', key: 'content', align: 'left' },
{
title: '时间',
key: 'operateTime',
align: 'center',
width: 190,
render: (h, params) => {
return h("div", { style: "line-height: 56px" }, this.$utils.dateFormat(params.row.operateTime,'yyyy-MM-dd HH:mm:ss'));
}
}
]
},
// 请求日志列表
loadLogData() {
const _this = this;
var item = {
keyword:_this.searchKeyword,
page:_this.pageNum-1,
size:_this.pageSize
};
_this.$api.get(APIConfig.BschClassSystem.getlogsearch, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
// 搜索
handleSearch() {
this.pageNum = 1
this.loadLogData()
},
// 页码切换
pageChange(page) {
this.pageNum = page
this.loadLogData()
},
// 每页条数修改
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadLogData()
}
}
}
</script>
<style scoped>
.oper-log-wrap {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 12px;
}
/* 顶部搜索栏 */
.top-search-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 14px;
flex-shrink: 0;
}
.search-box {
width: 100%;
background: #f7f7f7;
padding: 10px 16px;
height: 52px;
text-align: right;
}
/* 表格自适应滚动区域 */
.table-scroll-wrap {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
height: 100%;
}
/* 底部分页固定栏 */
.page-bottom-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 14px;
flex-shrink: 0;
}
.page-wrap {
display: flex;
align-items: center;
}
</style>
+778
View File
@@ -0,0 +1,778 @@
<template>
<div class="page-container">
<!-- 顶部标签栏 + 新增分组按钮 + 校本Tab编辑按钮 -->
<div class="tab-header">
<div class="tabs-wrap">
<!-- 前两个固定Tab,无编辑按钮 -->
<div
class="tab-item fixed-tab"
:class="{active: activeTab === '0'}"
@click="activeTab = '0'"
>
拓展课选修班
</div>
<div
class="tab-item fixed-tab"
:class="{active: activeTab === '1'}"
@click="activeTab = '1'"
>
走班
</div>
<!-- 动态可编辑校本Tab -->
<div
v-for="(tab, idx) in dynamicTabList"
:key="tab.id"
class="tab-item edit-tab"
:class="{active: activeTab === tab.id}"
@click="activeTab = tab.id"
>
{{ tab.name }}
<Icon type="ios-create-outline" class="edit-icon" @click.stop="openEditGroupModal(tab, idx)"></Icon>
</div>
</div>
<!-- 加号新增校本分组 -->
<Button icon="ios-add-circle-outline" class="add-tab-btn" @click="openAddGroupModal"></Button>
</div>
<!-- Tab下方全局分割线 -->
<div class="tab-divider"></div>
<!-- 顶部操作按钮栏 -->
<div class="btn-toolbar">
<div class="left-btns">
<Button type="primary" class="btn-normal" @click="openAddClassModal">添加班级</Button>
<Button type="primary" class="btn-normal" @click="handleSyncClass">同步班级</Button>
<Button type="success" class="btn-normal" @click="openImportClassModal">班级信息导入</Button>
<Button type="success" class="btn-normal" @click="openImportStudentModal">学生导入</Button>
</div>
<div class="right-filter">
<Select v-model="filterType" style="width:120px" class="filter-select">
<Option value="other">其它</Option>
</Select>
<DatePicker
placeholder="请选择开班时间"
style="width:180px;margin-left:10px"
v-model="filterOpenTime"
></DatePicker>
</div>
</div>
<!-- 表格主体区域:自适应高度滚动,分页永久固定底部 -->
<div class="table-wrap">
<div class="table-scroll-box">
<Table
stripe
:columns="tableColumns"
:data="tableData"
class="elective-table"
@on-selection-change="onSelectionChange"
height="100%"
:border="false"
></Table>
</div>
<!-- 底部分页+批量操作按钮 -->
<div class="page-footer">
<div class="batch-btns">
<Button type="error" class="btn-bottom" @click="batchDelete" icon="ios-trash">删除</Button>
<Button class="btn-bottom" style="margin-left:10px" @click="reSyncAll">重新同步</Button>
</div>
<div class="page-pagination">
<Page
:current.sync="pageNum"
:page-size.sync="pageSize"
:total="total"
:page-size-opts="[10,20,50]"
show-sizer
show-total
@on-change="loadTableData"
@on-page-size-change="loadTableData"
></Page>
</div>
</div>
</div>
<!-- 弹窗1:新增校本分组 -->
<Modal v-model="addGroupModal" title="添加组" width="420">
<Form ref="groupForm" :model="groupForm" :rules="groupRules" label-width="80">
<FormItem label="*组名称" prop="groupName">
<Input v-model="groupForm.groupName" placeholder="请输入组名称"></Input>
</FormItem>
</Form>
<div slot="footer" align="left">
<Button type="error" @click="addGroupModal=false">删除</Button>
<div style="float:right">
<Button @click="addGroupModal=false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitAddGroup">提交</Button>
</div>
</div>
</Modal>
<!-- 弹窗2:编辑校本分组(对标截图红框弹窗) -->
<Modal v-model="editGroupModal" title="编辑组" width="420">
<Form ref="editGroupForm" :model="editGroupForm" :rules="editGroupRules" label-width="80">
<FormItem label="*组名称" prop="groupName">
<Input v-model="editGroupForm.groupName"></Input>
</FormItem>
</Form>
<div slot="footer" align="left">
<Button type="error" @click="confirmDelGroup">删除</Button>
<div style="float:right">
<Button @click="editGroupModal=false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitEditGroup">提交</Button>
</div>
</div>
</Modal>
<!-- 弹窗3:新增选修班 -->
<Modal v-model="addClassModal" title="添加选修班" width="560">
<Form ref="addClassForm" :model="addClassForm" :rules="addClassRules" label-width="100">
<FormItem label="*班级名称" prop="className">
<Input v-model="addClassForm.className" placeholder="请输入班级名称"></Input>
</FormItem>
<FormItem label="*开班时间" prop="openTime">
<DatePicker v-model="addClassForm.openTime" placeholder="请选择开班时间" style="width:100%"></DatePicker>
</FormItem>
<FormItem label="关班时间">
<DatePicker v-model="addClassForm.closeTime" placeholder="请选择关班时间" style="width:100%"></DatePicker>
</FormItem>
<FormItem label="*选择教师" prop="teacherIds">
<div style="display:flex;gap:8px;align-items:center;margin-bottom:8px;">
<Select v-model="teacherQuery.subject" style="width:120px">
<Option value="math">数学</Option>
<Option value="chinese">语文</Option>
<Option value="english">英语</Option>
</Select>
<Input v-model="teacherQuery.keyword" placeholder="请输入关键字" style="flex:1"></Input>
<Button type="primary" @click="queryTeacherList">查询</Button>
</div>
<div class="teacher-btn-group">
<Button
v-for="item in teacherList"
:key="item.id"
size="small"
:type="addClassForm.teacherIds.includes(item.id) ? 'primary' : 'default'"
style="margin:4px"
@click="toggleTeacherSelect(item)"
>
{{ item.name }}
</Button>
</div>
<div v-if="addClassForm.teacherIds.length>0" style="margin-top:8px">
已选:
<Tag
v-for="tid in addClassForm.teacherIds"
:key="tid"
closable
@on-close="removeTeacherTag(tid)"
>
{{ getTeacherNameById(tid) }}
</Tag>
</div>
</FormItem>
<FormItem label="班级封面">
<Upload action="" :before-upload="()=>false">
<div class="upload-box">
<Icon type="ios-add" size="24"></Icon>
<p>+点击上传图片</p>
</div>
</Upload>
</FormItem>
<FormItem label="班级简介">
<Input v-model="addClassForm.intro" type="textarea" rows="4" placeholder="请输入班级简介"></Input>
</FormItem>
<FormItem label="计划招生人数">
<Input v-model.number="addClassForm.studentNum" placeholder="请输入计划招生人数"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="addClassModal=false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitAddClass">提交</Button>
</div>
</Modal>
<!-- 弹窗4:编辑选修班(完全匹配截图布局) -->
<Modal v-model="editClassModal" title="编辑选修班" width="560">
<Form ref="editClassForm" :model="editClassForm" :rules="addClassRules" label-width="100">
<FormItem label="*班级名称" prop="className">
<Input v-model="editClassForm.className"></Input>
</FormItem>
<FormItem label="*开班时间" prop="openTime">
<DatePicker v-model="editClassForm.openTime" style="width:100%"></DatePicker>
</FormItem>
<!-- 已选教师标签行 -->
<div style="margin-bottom:8px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
<span v-for="tid in editClassForm.teacherIds" :key="tid" style="display:flex;align-items:center;border:1px solid #ccc;padding:4px 8px;border-radius:4px;">
:{{ getEditTeacherNameById(tid) }}
<Icon type="ios-close" style="margin-left:6px;cursor:pointer;color:#666" @click="removeEditTeacherTag(tid)"/>
</span>
</div>
<FormItem label="*任课教师" prop="teacherIds">
<div style="display:flex;gap:8px;align-items:center;margin-bottom:8px;">
<Select v-model="editTeacherQuery.subject" style="width:120px">
<Option value="math">数学</Option>
<Option value="chinese">语文</Option>
<Option value="english">英语</Option>
</Select>
<Input v-model="editTeacherQuery.keyword" placeholder="请输入关键字" style="flex:1"></Input>
<Button type="primary" @click="queryEditTeacherList">查询</Button>
</div>
<div class="teacher-btn-group">
<Button
v-for="item in editTeacherList"
:key="item.id"
size="small"
:type="editClassForm.teacherIds.includes(item.id) ? 'primary' : 'default'"
style="margin:4px"
@click="toggleEditTeacherSelect(item)"
>
{{ item.name }}
</Button>
</div>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="editClassModal=false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitEditClass">提交</Button>
</div>
</Modal>
<!-- 弹窗5:班级信息导入 -->
<Modal v-model="importClassModal" title="班级信息导入" width="440">
<div style="margin-bottom:16px">
<a href="javascript:;" style="color:#2d78f4">点击链接下载[班级导入模板.xls]</a>
</div>
<Upload action="" :before-upload="()=>false">
<Button icon="ios-cloud-upload-outline">选择要上传的文件</Button>
</Upload>
<div slot="footer" align="right">
<Button @click="importClassModal=false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmImportClass">确定</Button>
</div>
</Modal>
<!-- 弹窗6:学生导入 -->
<Modal v-model="importStudentModal" title="学生导入" width="440">
<div style="margin-bottom:16px">
<a href="javascript:;" style="color:#2d78f4">点击链接下载[学生批量导入模板.xls]</a>
</div>
<Upload action="" :before-upload="()=>false">
<Button icon="ios-cloud-upload-outline">选择要上传的文件</Button>
</Upload>
<div slot="footer" align="right">
<Button @click="importStudentModal=false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmImportStudent">确定</Button>
</div>
</Modal>
<!-- 批量删除确认弹窗 -->
<Modal v-model="delConfirmModal" title="提示" width="360">
<Alert type="warning" show-icon>确认批量删除选中班级?删除后不可恢复</Alert>
<div slot="footer" align="right">
<Button @click="delConfirmModal=false" style="margin-right:10px">取消</Button>
<Button type="error" @click="confirmBatchDel">确定删除</Button>
</div>
</Modal>
<!-- 删除分组二次确认弹窗 -->
<Modal v-model="delGroupConfirmModal" title="删除分组提示" width="380">
<Alert type="error" show-icon>确定删除该分组?分组下所有班级将会一并删除,不可恢复!</Alert>
<div slot="footer" align="right">
<Button @click="delGroupConfirmModal=false" style="margin-right:10px">取消</Button>
<Button type="error" @click="doDelGroup">确认删除</Button>
</div>
</Modal>
</div>
</template>
<script>
export default {
name: "CurriculumElectiveClass",
data() {
return {
// 激活选中项:0固定拓展课,1固定走班,其余为动态tab的id
activeTab: "2",
// 动态可编辑校本课程Tab列表,初始默认一条
dynamicTabList: [
{ id: "2", name: "校本课程" }
],
// 当前正在编辑的tab下标与对象
editTabIndex: null,
editTabItem: null,
// 筛选条件
filterType: "other",
filterOpenTime: null,
// 分页基础
pageNum: 1,
pageSize: 10,
total: 70,
selectedRows: [], // 表格多选行
// 表格列配置:操作第一个改为编辑图标按钮
tableColumns: [
{ type: "selection", width: 50 },
{ title: "#", type: "index", align: "center", width: 70 },
{ title: "班级名称", key: "className", align: "left" },
{ title: "开班时间", key: "openTime", align: "center", width: 160 },
{ title: "教师", key: "teacherName", align: "center", width: 220 },
{
title: "操作",
key: "action",
align: "center",
width: 160,
render: (h, params) => {
return h("div", { style: "display:flex;gap:6px;justify-content:center" }, [
// 第一个:编辑图标 打开编辑弹窗
h("Button", {
props: { type: "text", icon: "ios-create", size: "large" },
on: { click: () => this.openEditClass(params.row) }
}),
// 学生管理
h("Button", {
props: { type: "text", icon: "ios-people", size: "large" },
on: { click: () => this.openStudentManage(params.row) }
}),
// 同步刷新
h("Button", {
props: { type: "text", icon: "ios-refresh", size: "large" },
on: { click: () => this.singleSync(params.row) }
})
])
}
}
],
// 模拟表格数据
tableData: [
{ id: 1, className: "高一化学培优班", openTime: "2026-02-10", teacherName: "县晗", teacherIdList: [1] },
{ id: 2, className: "排球", openTime: "2026-02-11", teacherName: "阮琳玮", teacherIdList: [2] },
{ id: 3, className: "辩论与口才", openTime: "2026-02-12", teacherName: "蔡韵怡", teacherIdList: [3] },
{ id: 4, className: "银行实务&经济学原理简析", openTime: "2026-02-13", teacherName: "任蕾,吕婷", teacherIdList: [4,5] },
{ id: 5, className: "叩问与回响:经典短篇小说的细读与鉴赏", openTime: "2026-02-14", teacherName: "韩佳慧", teacherIdList: [6] },
{ id: 6, className: "叙事的镜子:文学与其影视改编", openTime: "2026-02-15", teacherName: "李嘉萱", teacherIdList: [7] },
{ id: 7, className: "寻迹\"唐宋八大家\"", openTime: "2026-02-16", teacherName: "苏煦雯", teacherIdList: [8] },
{ id: 8, className: "高二数学基础班", openTime: "2026-02-17", teacherName: "蓝怡", teacherIdList: [9] }
],
// ========== 添加分组弹窗 ==========
addGroupModal: false,
groupForm: { groupName: "" },
groupRules: { groupName: [{ required: true, message: "请输入组名称", trigger: "blur" }] },
// ========== 编辑分组弹窗(对标截图) ==========
editGroupModal: false,
editGroupForm: { groupName: "" },
editGroupRules: { groupName: [{ required: true, message: "请输入组名称", trigger: "blur" }] },
delGroupConfirmModal: false,
// ========== 新增班级弹窗 ==========
addClassModal: false,
addClassForm: {
className: "",
openTime: null,
closeTime: null,
teacherIds: [],
intro: "",
studentNum: null
},
addClassRules: {
className: [{ required: true, message: "请填写班级名称", trigger: "blur" }],
openTime: [{ required: true, message: "请选择开班时间", trigger: "change" }],
teacherIds: [{ required: true, message: "请至少选择一位任课教师", trigger: "change" }]
},
teacherQuery: { subject: "math", keyword: "" },
teacherList: [],
// ========== 编辑班级弹窗 ==========
editClassModal: false,
editCurrentRow: null,
editClassForm: {
className: "",
openTime: null,
teacherIds: []
},
editTeacherQuery: { subject: "math", keyword: "" },
editTeacherList: [],
// ========== 导入弹窗 ==========
importClassModal: false,
importStudentModal: false,
// 批量删除确认
delConfirmModal: false
}
},
mounted() {
this.loadTableData();
},
methods: {
// 加载表格分页数据
loadTableData() {
console.log("分页查询参数", {
page: this.pageNum,
size: this.pageSize,
tabId: this.activeTab,
filter: this.filterType,
time: this.filterOpenTime
});
// 此处替换后端接口赋值tableData、total
},
// 表格多选
onSelectionChange(rows) {
this.selectedRows = rows;
},
// 打开【新增校本分组】弹窗
openAddGroupModal() {
this.addGroupModal = true;
this.groupForm.groupName = "";
this.$nextTick(() => this.$refs.groupForm.clearValidate());
},
// 提交新增分组
submitAddGroup() {
this.$refs.groupForm.validate(valid => {
if (!valid) return;
const newId = Date.now().toString();
this.dynamicTabList.push({
id: newId,
name: this.groupForm.groupName
})
this.activeTab = newId;
this.$Message.success("分组新增成功");
this.addGroupModal = false;
this.loadTableData();
})
},
// 打开【编辑组】弹窗(点击Tab右上角铅笔图标)
openEditGroupModal(tabItem, index) {
this.editTabItem = tabItem;
this.editTabIndex = index;
this.editGroupForm.groupName = tabItem.name;
this.editGroupModal = true;
this.$nextTick(() => this.$refs.editGroupForm.clearValidate());
},
// 提交编辑分组名称
submitEditGroup() {
this.$refs.editGroupForm.validate(valid => {
if (!valid) return;
this.dynamicTabList[this.editTabIndex].name = this.editGroupForm.groupName;
this.$Message.success("分组名称修改成功");
this.editGroupModal = false;
})
},
// 点击弹窗删除按钮,弹出二次确认
confirmDelGroup() {
this.editGroupModal = false;
this.delGroupConfirmModal = true;
},
// 真正执行删除分组
doDelGroup() {
this.dynamicTabList.splice(this.editTabIndex, 1);
// 如果删的是当前激活tab,默认切到第一个动态tab
if (this.activeTab === this.editTabItem.id && this.dynamicTabList.length) {
this.activeTab = this.dynamicTabList[0].id;
} else if (!this.dynamicTabList.length) {
this.activeTab = "0";
}
this.delGroupConfirmModal = false;
this.$Message.success("分组已删除");
this.loadTableData();
},
// 2. 添加班级
openAddClassModal() {
this.addClassModal = true;
this.addClassForm = {
className: "", openTime: null, closeTime: null, teacherIds: [], intro: "", studentNum: null
};
this.teacherList = [];
this.teacherQuery = { subject: "math", keyword: "" };
this.$nextTick(() => this.$refs.addClassForm.clearValidate());
},
// 查询教师列表
queryTeacherList() {
// 模拟接口返回教师数组
this.teacherList = [
{ id: 1, name: "陶为玉" }, { id: 2, name: "丁雪芬" }, { id: 3, name: "贺少君" },
{ id: 4, name: "金添玉" }, { id: 5, name: "田雪婷" }, { id: 6, name: "金慧" },
{ id: 7, name: "翁海红" }, { id: 8, name: "钱李铭" }, { id: 9, name: "邱侯晗" },
{ id: 10, name: "沈裴裴" }, { id: 11, name: "左惊喜" }, { id: 12, name: "东昌测试1" },
{ id: 13, name: "苏煦雯" }, { id: 14, name: "李嘉萱" }, { id: 15, name: "王心洁" },
{ id: 16, name: "蓝怡" }, { id: 17, name: "鄢泓" }, { id: 18, name: "颜国连" }
];
},
// 切换教师勾选
toggleTeacherSelect(item) {
const idx = this.addClassForm.teacherIds.indexOf(item.id);
if (idx > -1) {
this.addClassForm.teacherIds.splice(idx, 1);
} else {
this.addClassForm.teacherIds.push(item.id);
}
},
removeTeacherTag(tid) {
const i = this.addClassForm.teacherIds.indexOf(tid);
if (i > -1) this.addClassForm.teacherIds.splice(i, 1);
},
getTeacherNameById(tid) {
const t = this.teacherList.find(s => s.id === tid);
return t ? t.name : "";
},
submitAddClass() {
this.$refs.addClassForm.validate(valid => {
if (!valid) return;
this.$Message.success("新增选修班成功");
this.addClassModal = false;
this.loadTableData();
})
},
// 3. 编辑班级(行编辑按钮触发)
openEditClass(row) {
this.editCurrentRow = row;
this.editClassModal = true;
this.editClassForm = {
className: row.className,
openTime: row.openTime,
teacherIds: row.teacherIdList || []
};
this.editTeacherList = [];
this.editTeacherQuery = { subject: "math", keyword: "" };
this.$nextTick(() => this.$refs.editClassForm.clearValidate());
},
queryEditTeacherList() {
// 匹配截图教师列表
this.editTeacherList = [
{ id: 101, name: "薛志明" }, { id: 102, name: "金瑜" }, { id: 103, name: "向荣" }, { id: 104, name: "张锦花" },
{ id: 105, name: "周慧敏" }, { id: 106, name: "张凌敏" }, { id: 107, name: "孙丽杰" }, { id: 108, name: "乔瑜" },
{ id: 109, name: "潘悦" }, { id: 110, name: "强熙" }, { id: 111, name: "张静漪" }, { id: 112, name: "杨雪" },
{ id: 113, name: "陈梦舟" }, { id: 114, name: "解颖" }, { id: 115, name: "施瑞安" }, { id: 116, name: "殷继宗" },
{ id: 117, name: "王恩跃" }, { id: 118, name: "徐丽丽" }, { id: 119, name: "吴萍" }, { id: 120, name: "王妍婷" }
];
},
toggleEditTeacherSelect(item) {
const idx = this.editClassForm.teacherIds.indexOf(item.id);
if (idx > -1) this.editClassForm.teacherIds.splice(idx, 1);
else this.editClassForm.teacherIds.push(item.id);
},
removeEditTeacherTag(tid) {
const i = this.editClassForm.teacherIds.indexOf(tid);
if (i > -1) this.editClassForm.teacherIds.splice(i, 1);
},
getEditTeacherNameById(tid) {
const t = this.editTeacherList.find(s => s.id === tid);
return t ? t.name : "";
},
submitEditClass() {
this.$refs.editClassForm.validate(valid => {
if (!valid) return;
this.$Message.success("编辑班级成功");
this.editClassModal = false;
this.loadTableData();
})
},
// 导入弹窗
openImportClassModal() { this.importClassModal = true; },
confirmImportClass() {
this.$Message.success("班级导入完成");
this.importClassModal = false;
this.loadTableData();
},
openImportStudentModal() { this.importStudentModal = true; },
confirmImportStudent() {
this.$Message.success("学生导入完成");
this.importStudentModal = false;
},
// 顶部按钮:同步班级
handleSyncClass() {
this.$Message.success("同步班级操作完成");
this.loadTableData();
},
// 底部:重新同步全部
reSyncAll() {
this.$Message.success("全局重新同步完成");
this.loadTableData();
},
// 单条行内同步
singleSync(row) {
this.$Message.success(`【${row.className}】同步成功`);
},
// 学生管理
openStudentManage(row) {
this.$router.push({path:'/electiveClassManageDetail'});
},
// 批量删除
batchDelete() {
if (!this.selectedRows.length) return this.$Message.warning("请先勾选要删除的班级");
this.delConfirmModal = true;
},
confirmBatchDel() {
this.$Message.success("批量删除成功");
this.delConfirmModal = false;
this.selectedRows = [];
this.loadTableData();
}
}
}
</script>
<style scoped>
.page-container {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
padding: 16px;
gap: 14px;
}
/* 顶部Tab栏整体布局 */
.tab-header {
display: flex;
align-items: center;
gap: 12px;
}
.tabs-wrap {
display: flex;
align-items: flex-end;
}
/* Tab核心样式:仅底部下划线选中,无边框无背景 */
.tab-item {
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
border: none;
border-bottom: 2px solid transparent;
background: transparent;
margin-right: 4px;
border-top-left-radius: 0;
border-top-right-radius: 0;
position: relative;
display: flex;
align-items: center;
gap: 6px;
}
.tab-item.active {
background: transparent;
border-top: none;
border-bottom: 2px solid #3377dd;
font-weight: 500;
}
.fixed-tab {
cursor: default;
}
.edit-icon {
font-size: 16px;
color: #666;
cursor: pointer;
}
.add-tab-btn {
width: 32px;
height: 32px;
padding: 0;
}
/* Tab下方通栏分割线 */
.tab-divider {
width: 100%;
border-bottom: 1px solid #dcdcdc;
}
/* 顶部按钮工具栏 */
.btn-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: #f7f8fa;
border-radius: 4px;
flex-shrink: 0;
}
.left-btns {
display: flex;
gap: 12px;
align-items: center;
}
.btn-normal {
padding: 6px 14px;
}
.filter-select {
margin-right: 8px;
}
/* 表格自适应容器,移除外层边框 */
.table-wrap {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
border: none;
border-radius: 0;
}
.table-scroll-box {
flex: 1;
min-height: 0;
overflow: hidden;
}
.elective-table {
width: 100%;
}
/* 底部分页与按钮 */
.page-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 0;
flex-shrink: 0;
}
.batch-btns {
display: flex;
align-items: center;
}
.btn-bottom {
padding: 6px 16px;
}
/* 教师多选按钮组 */
.teacher-btn-group {
max-height: 180px;
overflow-y: auto;
border: 1px solid #e5e7eb;
padding: 8px;
border-radius: 4px;
}
/* 上传封面框 */
.upload-box {
width: 120px;
height: 120px;
border: 1px dashed #ccc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #999;
border-radius: 4px;
}
</style>
<style>
/* 全局穿透修改iView表格,完全匹配截图:无外框、无竖线、仅行底横线 */
/deep/ .ivu-table {
border: none !important;
}
/deep/ .ivu-table-header th {
border: none;
border-bottom: 1px solid #eee;
text-align: center;
background: #fff;
}
/deep/ .ivu-table td {
border-left: none !important;
border-right: none !important;
border-bottom: 1px solid #f0f0f0;
}
/deep/ .ivu-table-row {
border: none;
}
/deep/ .ivu-table-stripe .ivu-table-row:nth-child(even) {
background-color: #fafafa;
}
</style>
+345
View File
@@ -0,0 +1,345 @@
<template>
<div class="student-class-page">
<!-- 顶部标题栏 -->
<div class="page-top-bar">
<span class="current-class-txt">当前班级:{{ currentClassName }}</span>
<Button type="primary" @click="backPrevPage">返回上一级</Button>
</div>
<!-- 工具栏 -->
<div class="tool-bar">
<span class="list-title">学生列表</span>
<div class="btn-group">
<Button type="primary" @click="openTransferModal">调班</Button>
<Button type="success" @click="openStudentImportModal">学生导入</Button>
</div>
</div>
<!-- 表格区域 自适应高度滚动 -->
<div class="table-wrap">
<div class="table-scroll-box">
<Table
stripe
:columns="tableColumns"
:data="studentTableData"
height="100%"
:border="false"
@on-selection-change="onTableSelect"
></Table>
</div>
<!-- 底栏:批量删除 + 分页 -->
<div class="page-footer">
<Button type="error" @click="batchDelConfirm">删除</Button>
<div class="pagination-box">
<Page
:current.sync="pageNum"
:page-size.sync="pageSize"
:total="totalCount"
:page-size-opts="[10,20,50]"
show-sizer
show-total
@on-change="loadStudentData"
@on-page-size-change="loadStudentData"
></Page>
</div>
</div>
</div>
<!-- 弹窗1:调班弹窗 -->
<Modal v-model="transferModal" title="调班" width="680">
<Form label-width="100">
<FormItem label="当前班级">
<Input v-model="currentClassName" disabled></Input>
</FormItem>
<FormItem label="*目标班级">
<div class="transfer-search-row">
<Select v-model="transferQuery.grade" placeholder="请选择" style="width:140px">
<Option value="g1">高一</Option>
<Option value="g2">高二</Option>
<Option value="g3">高三</Option>
</Select>
<Input v-model="transferQuery.keyword" placeholder="请输入关键字" style="flex:1;margin:0 10px"></Input>
<Button type="primary" @click="queryTargetClassList">查询</Button>
</div>
<div class="class-btn-box">
<Button
v-for="item in targetClassList"
:key="item.id"
size="small"
:type="selectedTargetId === item.id ? 'primary' : 'default'"
style="margin:6px"
@click="selectedTargetId = item.id"
>
{{ item.name }}
</Button>
</div>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="transferModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmTransferStudent">确定</Button>
</div>
</Modal>
<!-- 弹窗2:学生信息导入 -->
<Modal v-model="importModal" title="学生信息导入" width="460">
<div style="margin-bottom:16px;">
<a href="javascript:;" style="color:#2d78f4">点击链接下载[用户导入模板.xls]</a>
</div>
<Upload action="" :before-upload="()=>false">
<Button icon="ios-cloud-upload-outline">选择要上传的文件</Button>
</Upload>
<div slot="footer" align="right">
<Button @click="importModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmImport">确定</Button>
</div>
</Modal>
<!-- 批量删除二次确认弹窗 -->
<Modal v-model="delConfirmModal" title="提示" width="360">
<Alert type="warning" show-icon>确认删除选中学生?删除后不可恢复</Alert>
<div slot="footer" align="right">
<Button @click="delConfirmModal=false" style="margin-right:10px">取消</Button>
<Button type="error" @click="doBatchDelete">确定删除</Button>
</div>
</Modal>
</div>
</template>
<script>
export default {
name: "ClassStudentManage",
props: {
// 父页面传入当前班级名称与班级ID
classId: {
type: String,
default: ""
},
className: {
type: String,
default: "高一化学培优班"
}
},
data() {
return {
// 班级信息
currentClassName: this.className,
// 分页
pageNum: 1,
pageSize: 10,
totalCount: 19,
// 表格多选
selectedStudentRows: [],
// 表格列配置
tableColumns: [
{ type: "selection", width: 50 },
{ title: "序号", type: "index", align: "center", width: 70 },
{ title: "姓名", key: "name", align: "left" },
{ title: "年级", key: "grade", align: "center" },
{ title: "班级", key: "originClass", align: "center" }
],
// 模拟学生列表数据
studentTableData: [
{ id: 1, name: "潘程", grade: "高一", originClass: "2班" },
{ id: 2, name: "张译匀", grade: "高一", originClass: "9班" },
{ id: 3, name: "宋浩扬", grade: "高一", originClass: "1班" },
{ id: 4, name: "裴劲丰", grade: "高一", originClass: "8班" },
{ id: 5, name: "余知新", grade: "高一", originClass: "8班" },
{ id: 6, name: "刘翰城", grade: "高一", originClass: "14班" },
{ id: 7, name: "于释涵", grade: "高一", originClass: "11班" },
{ id: 8, name: "王梦菡", grade: "高一", originClass: "13班" }
],
// 调班弹窗
transferModal: false,
transferQuery: {
grade: "",
keyword: ""
},
targetClassList: [],
selectedTargetId: null,
// 导入弹窗
importModal: false,
// 删除确认弹窗
delConfirmModal: false
}
},
mounted() {
this.loadStudentData()
},
methods: {
// 返回上一级(父级选修班列表页)
backPrevPage() {
this.$router.back()
},
// 加载学生分页列表
loadStudentData() {
console.log('请求参数', {
classId: this.classId,
page: this.pageNum,
size: this.pageSize
})
// 此处替换后端接口赋值 studentTableData、totalCount
},
// 表格多选事件
onTableSelect(rows) {
this.selectedStudentRows = rows
},
// 打开调班弹窗
openTransferModal() {
if (!this.selectedStudentRows.length) {
return this.$Message.warning("请先勾选需要调班的学生")
}
this.transferModal = true
this.transferQuery = { grade: "", keyword: "" }
this.selectedTargetId = null
this.targetClassList = []
},
// 查询目标可选班级
queryTargetClassList() {
// 模拟接口返回下拉班级列表
this.targetClassList = [
{ id: 1, name: "高三网球1班(2024学年)" },
{ id: 2, name: "高三排球1班(2024学年)" },
{ id: 3, name: "高三女篮1班 (2024学年)" },
{ id: 4, name: "高三男篮1班 (2024学年)" },
{ id: 5, name: "高三羽毛球1班 (2024学年)" },
{ id: 6, name: "高三足球1班 (2024学年)" },
{ id: 7, name: "高三乒乓球1班(2024学年)" },
{ id: 8, name: "高三网球2班(2024学年)" },
{ id: 9, name: "高三排球2班(2024学年)" },
{ id: 10, name: "高三足球2班(2024学年)" }
]
},
// 确认执行调班
confirmTransferStudent() {
if (!this.selectedTargetId) {
return this.$Message.warning("请选择目标班级")
}
this.$Message.success("学生调班完成")
this.transferModal = false
this.loadStudentData()
},
// 打开导入弹窗
openStudentImportModal() {
this.importModal = true
},
confirmImport() {
this.$Message.success("学生导入成功")
this.importModal = false
this.loadStudentData()
},
// 批量删除
batchDelConfirm() {
if (!this.selectedStudentRows.length) {
return this.$Message.warning("请勾选要删除的学生")
}
this.delConfirmModal = true
},
doBatchDelete() {
this.$Message.success("批量删除成功")
this.delConfirmModal = false
this.selectedStudentRows = []
this.loadStudentData()
}
}
}
</script>
<style scoped>
.student-class-page {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
padding: 16px;
gap: 16px;
}
/* 顶部标题栏 */
.page-top-bar {
display: flex;
justify-content: space-between;
align-items: center;
}
.current-class-txt {
font-size: 16px;
font-weight: 500;
}
/* 工具栏 */
.tool-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
background: #f7f8fa;
}
.list-title {
font-size: 15px;
font-weight: 500;
}
.btn-group {
display: flex;
gap: 10px;
}
/* 表格容器 自适应高度 */
.table-wrap {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.table-scroll-box {
flex: 1;
overflow: hidden;
}
/* 底部分页栏 */
.page-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
flex-shrink: 0;
}
/* 调班弹窗查询行 */
.transfer-search-row {
display: flex;
align-items: center;
}
.class-btn-box {
max-height: 220px;
overflow-y: auto;
border: 1px solid #e5e7eb;
padding: 8px;
margin-top: 8px;
border-radius: 4px;
}
</style>
<style>
/* 全局穿透 表格样式 与截图一致:无外框无竖线,仅行底横线 */
/deep/ .ivu-table {
border: none !important;
}
/deep/ .ivu-table-header th {
border: none;
border-bottom: 1px solid #eee;
text-align: center;
background: #fff;
}
/deep/ .ivu-table td {
border-left: none !important;
border-right: none !important;
border-bottom: 1px solid #f0f0f0;
}
/deep/ .ivu-table-row {
border: none;
}
/deep/ .ivu-table-stripe .ivu-table-row:nth-child(even) {
background-color: #fafafa;
}
</style>
+338
View File
@@ -0,0 +1,338 @@
<template>
<div class="class-history-wrap">
<!-- 左侧树形区域:固定高度纵向滚动、单选、默认首班级选中 -->
<div class="left-tree-box">
<Tree
:data="treeData"
ref="tree"
single
:selected.sync="selectedTreeNode"
@on-select="treeNodeChange"
></Tree>
</div>
<!-- 右侧主内容区域 -->
<div class="right-main">
<!-- 顶部基础信息栏 -->
<div class="top-info-bar">
<span class="info-item">学级:{{ currentGrade }}</span>
<span class="info-item">班级:{{ currentClassName }}</span>
<span class="info-item">入学时间:{{ enrollDate }}</span>
</div>
<!-- 标签页切换:历史任教 / 历史学生 -->
<Tabs v-model="activeTab">
<TabPane label="历史任教" name="teach"></TabPane>
<TabPane label="历史学生" name="student"></TabPane>
</Tabs>
<!-- 筛选头部行 -->
<div class="filter-bar">
<span>日期:{{ dateRange }} &nbsp;&nbsp;年级:{{ currentGradeShort }}</span>
<span v-if="activeTab === 'student'" style="margin-left:20px">学生数:{{ studentTotal }}</span>
</div>
<!-- 滚动内容容器:高度自适应溢出滚动 -->
<div class="scroll-content-box">
<!-- 历史任教布局 -->
<div v-if="activeTab === 'teach'" class="teach-wrap">
<div class="item-card" v-for="(item,idx) in teachList" :key="idx">
<div class="card-title">{{ item.subject }}</div>
<div class="card-value">{{ item.teacher }}</div>
</div>
</div>
<!-- 历史学生布局 -->
<div v-if="activeTab === 'student'" class="student-wrap">
<div class="item-card student-card" style="cursor: pointer;" v-for="(item,idx) in studentList" :key="idx" @click="tolink(item)">
{{ item.name }}<span v-if="item.idCard">({{ item.idCard }})</span>
</div>
</div>
</div>
<!-- 底部分页固定区域(预留分页逻辑) -->
<div class="page-bottom-bar">
<span>共 {{ activeTab === 'teach' ? teachList.length : studentTotal }} 条</span>
<Page
v-if="needPage"
:current.sync="pageNum"
:page-size="pageSize"
:total="activeTab === 'teach' ? teachList.length : studentTotal"
:page-size-opts="[20,40,60]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ClassHistoryPage',
data() {
return {
// 树形配置
treeData: [
{
title: '上海市进才中学北校',
expand: true,
children: [
{
title: '2025级(预备)',
expand: true,
children: [
{ title: '1班' },{ title: '2班' },{ title: '3班' },{ title: '4班' },{ title: '5班' },
{ title: '6班' },{ title: '7班' },{ title: '8班' },{ title: '9班' },{ title: '10班' },
{ title: '11班' },{ title: '12班' },{ title: '13班' },{ title: '14班' },{ title: '15班' },
{ title: '16班' },{ title: '17班' },{ title: '18班' },{ title: '19班' },{ title: '20班' },
{ title: '21班' },{ title: '22班' },{ title: '23班' },
]
},
{ title: '2024级(初一)', expand: true, children: [] },
{ title: '2023级(初二)', expand: true, children: [] },
]
}
],
selectedTreeNode: null,
currentClassName: '1班',
currentGrade: '2025级',
currentGradeShort: '预备',
enrollDate: '2025-07-11',
dateRange: '2025-07-11 - 2026-07-15',
// 标签页
activeTab: 'teach',
// 任教数据
teachList: [
{ subject: '班主任', teacher: '伍芳芳' },
{ subject: '语文', teacher: '卫红' },
{ subject: '数学', teacher: '伍芳芳' },
{ subject: '英语', teacher: '俞莉玉' },
{ subject: '体育', teacher: '郑浩淼' },
{ subject: '美术', teacher: '蔡婷婷' },
{ subject: '科学', teacher: '游宝玲' },
{ subject: '劳技', teacher: '牛乐' },
{ subject: '地理', teacher: '沈晓燕' },
{ subject: '音乐', teacher: '李海昆' },
{ subject: '心理', teacher: '展望' },
{ subject: '道德与法治', teacher: '郭雨佳' },
{ subject: '班主任', teacher: '伍芳芳' },
{ subject: '劳动', teacher: '牛乐' },
{ subject: '信息科技', teacher: '张颖' },
{ subject: '副班主任', teacher: '展望' },
{ subject: '校本(劳动教育)', teacher: '牛乐' },
{ subject: '体育与健康', teacher: '郑浩淼' },
{ subject: '二外', teacher: '季思飞' },
{ subject: '校本(心理+二外)', teacher: '数字化中心09' },
],
// 学生数据
studentTotal: 48,
studentList: [
{ name: '金俊辰', idCard: '' },
{ name: '周泽恒', idCard: '310115201404031836' },
{ name: '杨城宁', idCard: '310115201406110407' },
{ name: '李琪琪', idCard: '310115201403072281' },
{ name: '浦森隽', idCard: '310115201311290011' },
{ name: '沈奕欣', idCard: '310115201402286384' },
{ name: '李赛乐', idCard: '31011520131110037X' },
{ name: '林欣宜', idCard: '310109201403261560' },
{ name: '胡艺涵', idCard: '310115201403251167' },
{ name: '周维哲', idCard: '310115201311182221' },
{ name: '曹维德', idCard: '310115201311182213' },
{ name: '陈茂萌', idCard: '310115201406020516' },
{ name: '施锐朗', idCard: '31011520140412491X' },
{ name: '袁唯格', idCard: '310115201401270022' },
{ name: '徐睿辰', idCard: '310115201406284490' },
{ name: '姚欣蕊', idCard: '310115201402161848' },
{ name: '王立仁', idCard: '310115201312030377' },
{ name: '朱思媛', idCard: '310115201407220026' },
{ name: '李文心', idCard: '310115201312165167' },
{ name: '刘烁恒', idCard: '310115201407273873' },
{ name: '严潇宇', idCard: '310115201312120372' },
{ name: '唐羿', idCard: '310107201401013958' },
{ name: '于楚涵', idCard: '230110201408063729' },
{ name: '肖梦晞', idCard: '340102201401081524' },
{ name: '郑聿铭', idCard: '310114201401071633' },
{ name: '宗柠', idCard: '310115201310094481' },
{ name: '张子煜', idCard: '310115201401292619' },
{ name: '刘孜宸', idCard: '430602201312280205' },
{ name: '潘思羽', idCard: '310115201312310280' },
],
// 分页配置
needPage: false,
pageNum: 1,
pageSize: 40,
total: 0
}
},
created() {
// 页面初始化:自动选中最后一级第一个班级(2025级(预备) -> 1班)
this.initDefaultSelectTree()
this.loadClassDetail()
},
methods: {
// 默认选中叶子第一个班级
initDefaultSelectTree() {
const schoolNode = this.treeData[0]
const targetGrade = schoolNode.children.find(g => g.title === '2025级(预备)')
if (targetGrade && targetGrade.children.length) {
this.selectedTreeNode = targetGrade.children[0]
this.currentClassName = this.selectedTreeNode.title
this.currentGrade = '2025级'
this.currentGradeShort = '预备'
}
},
// 树节点点击切换
treeNodeChange(node) {
// 只响应叶子班级节点,过滤年级、学校父节点
if (!node.children || node.children.length === 0) {
this.currentClassName = node.title
this.pageNum = 1
this.loadClassDetail()
}
},
// 加载班级对应任教/学生数据
loadClassDetail() {
console.log('当前选中班级:', this.currentGrade + this.currentClassName)
// 此处可替换后端接口请求
/*
this.$axios.get('/api/class/history', {
params: {
grade: this.currentGrade,
className: this.currentClassName,
tabType: this.activeTab
}
}).then(res => {
this.teachList = res.data.teachList
this.studentList = res.data.studentList
this.studentTotal = res.data.studentTotal
this.total = this.activeTab === 'teach' ? this.teachList.length : this.studentTotal
})
*/
},
tolink(item){
this.$router.push({path:'/historySearchDetail'});
},
// 分页切换
pageChange(p) {
this.pageNum = p
this.loadClassDetail()
},
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadClassDetail()
}
}
}
</script>
<style scoped>
.class-history-wrap {
width: 100%;
height: 100%;
display: flex;
gap: 16px;
box-sizing: border-box;
padding: 12px;
}
/* 左侧树固定宽度+固定高度滚动 */
.left-tree-box {
width: 260px;
border: 1px solid #e5e7eb;
overflow-y: auto;
flex-shrink: 0;
}
/* 右侧整体弹性布局 */
.right-main {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
/* 顶部班级信息行 */
.top-info-bar {
font-size: 20px;
font-weight: 500;
padding: 8px 0 12px;
}
.info-item {
margin-right: 30px;
}
.filter-bar {
padding: 10px 12px;
background: #f5f7fa;
margin: 8px 0;
border-radius: 4px;
}
/* 核心滚动容器:自适应剩余高度,内部纵向滑动 */
.scroll-content-box {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 10px 0;
}
/* 任教方块流式布局 */
.teach-wrap {
display: flex;
flex-wrap: wrap;
gap: 14px;
}
.item-card {
width: 160px;
border-radius: 6px;
overflow: hidden;
border: 1px solid #e2e8f0;
}
.card-title {
background: #4080ff;
color: #fff;
text-align: center;
padding: 8px 0;
font-size: 15px;
}
.card-value {
text-align: center;
padding: 10px 0;
background: #f9fafc;
font-size: 15px;
}
/* 学生方块布局 */
.student-wrap {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.student-card {
width: 320px;
padding: 10px 8px;
background: #4080ff;
color: #fff;
text-align: center;
border-radius: 6px;
}
/* 分页永久固定底部 */
.page-bottom-bar {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 14px;
gap: 12px;
flex-shrink: 0;
}
</style>
+153
View File
@@ -0,0 +1,153 @@
<template>
<div class="student-teach-detail-wrap">
<!-- 顶部标题栏 -->
<div class="top-header-bar">
<div class="left-info">
<span class="label">当前学生:</span>
<span class="text">{{ studentName }}</span>
<span class="label" style="margin-left: 24px;">当前班级:</span>
<span class="text">{{ className }}</span>
</div>
<Button type="primary" @click="goBack">返回上一级</Button>
</div>
<!-- 时间段+学校班级栏 -->
<div class="time-school-bar">
<span>日期:{{ dateRange }}</span>
<span style="margin-left: 20px;">{{ schoolName }} &nbsp;&nbsp;{{ className }}</span>
</div>
<!-- 科目教师卡片流式容器,超出纵向滚动 -->
<div class="card-scroll-wrap">
<div class="card-container">
<div class="subject-card" v-for="(item, index) in subjectList" :key="index">
<div class="card-title">{{ item.subject }}</div>
<div class="card-teacher">{{ item.teacher }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'StudentTeachDetail',
data() {
return {
// 头部信息
studentName: '朱思媛',
className: '预备1班',
schoolName: '上海市进才中学北校',
dateRange: '2025-07-11~2026-07-15',
// 科目+教师数据源(完全匹配截图)
subjectList: [
{ subject: '班主任', teacher: '伍芳芳' },
{ subject: '副班主任', teacher: '未设置' },
{ subject: '语文', teacher: '卫红' },
{ subject: '数学', teacher: '伍芳芳' },
{ subject: '英语', teacher: '俞莉玉' },
{ subject: '体育', teacher: '郑浩淼' },
{ subject: '美术', teacher: '蔡婷婷' },
{ subject: '科学', teacher: '游宝玲' },
{ subject: '劳技', teacher: '牛乐' },
{ subject: '地理', teacher: '沈晓燕' },
{ subject: '音乐', teacher: '李海昆' },
{ subject: '心理', teacher: '展望' },
{ subject: '道德与法治', teacher: '郭雨佳' },
{ subject: '班主任', teacher: '伍芳芳' },
{ subject: '劳动', teacher: '牛乐' },
{ subject: '信息科技', teacher: '张颖' },
{ subject: '副班主任', teacher: '展望' },
{ subject: '校本(劳动教育)', teacher: '牛乐' },
{ subject: '体育与健康', teacher: '郑浩淼' },
{ subject: '二外', teacher: '季思飞' },
{ subject: '校本(心理+二外)', teacher: '数字化中心09' }
]
}
},
methods: {
// 返回上一页
goBack() {
this.$router.go(-1)
// 也可指定路由跳转
// this.$router.push('/对应路由地址')
}
}
}
</script>
<style scoped>
.student-teach-detail-wrap {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 16px;
}
/* 顶部头部行 */
.top-header-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 16px;
border-bottom: 1px solid #e5e7eb;
margin-bottom: 16px;
}
.left-info {
font-size: 22px;
}
.label {
color: #333;
}
.text {
color: #2d7ff9;
}
/* 日期学校栏 */
.time-school-bar {
padding: 12px 16px;
background-color: #f5f7fa;
border-radius: 4px;
font-size: 16px;
margin-bottom: 16px;
flex-shrink: 0;
}
/* 卡片滚动容器:占剩余高度,内容溢出纵向滚动 */
.card-scroll-wrap {
flex: 1;
min-height: 0;
overflow-y: auto;
}
.card-container {
display: flex;
flex-wrap: wrap;
gap: 14px;
}
/* 单个科目卡片样式 */
.subject-card {
width: 162px;
border: 1px solid #e2e8f0;
border-radius: 6px;
overflow: hidden;
}
.card-title {
background-color: #4080ff;
color: #ffffff;
text-align: center;
padding: 9px 4px;
font-size: 15px;
}
.card-teacher {
text-align: center;
padding: 11px 4px;
background-color: #f9fafc;
font-size: 15px;
color: #333;
}
</style>
+570
View File
@@ -0,0 +1,570 @@
<template>
<div class="page-container">
<div class="btn-top-wrap">
<Button type="primary" @click="openAddModal">添加学年学期</Button>
</div>
<!-- 表格 -->
<Table
border
:columns="tableColumns"
:data="yeartermlist"
stripe
:height="useTableHeight()+112"
class="term-table"
/>
<!-- 新增弹窗 -->
<Modal v-model="addModalVisible" title="添加学年学期" width="720" class="modalform">
<Form
:model="addForm"
ref="addFormRef"
:rules="formRules"
label-width="120px"
class="modal-form"
>
<div class="form-block-title">学年设置</div>
<FormItem label="新学年">
<Input disabled :value="addForm.year + '学年'" />
</FormItem>
<Row gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="yearStartDate">
<DatePicker
disabled
:value="strToDate(addForm.yearStartDate)"
format="yyyy-MM-dd"
placeholder="请选择日期"
></DatePicker>
</FormItem>
</Col>
<Col span="12">
<FormItem label="结束日期" prop="yearEndDate">
<DatePicker
:value="strToDate(addForm.yearEndDate)"
@on-change="(date) => addForm.yearEndDate = dateToStr(date)"
format="yyyy-MM-dd"
placeholder="请选择日期"
></DatePicker>
</FormItem>
</Col>
</Row>
<div class="form-block-title">学期设置</div>
<FormItem label="升学日期" prop="upgradeDate">
<DatePicker
:value="strToDate(addForm.upgradeDate)"
@on-change="(date) => addForm.upgradeDate = dateToStr(date)"
format="yyyy-MM-dd"
placeholder="请选择日期"
></DatePicker>
</FormItem>
<div class="form-block-title">第一学期设置</div>
<Row gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="firstTermStartDate">
<DatePicker
type="date"
:value="strToDate(addForm.firstTermStartDate)"
@on-change="(date) => addForm.firstTermStartDate = dateToStr(date)"
format="yyyy-MM-dd"
placeholder="请选择日期"
></DatePicker>
</FormItem>
</Col>
<Col span="12">
<FormItem label="结束日期" prop="firstTermEndDate">
<DatePicker
type="date"
:value="strToDate(addForm.firstTermEndDate)"
@on-change="(date) => addForm.firstTermEndDate = dateToStr(date)"
format="yyyy-MM-dd"
placeholder="请选择日期"
></DatePicker>
</FormItem>
</Col>
</Row>
<div class="form-block-title">第二学期设置</div>
<Row gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="secondTermStartDate">
<DatePicker
:value="strToDate(addForm.secondTermStartDate)"
@on-change="(date) => addForm.secondTermStartDate = dateToStr(date)"
format="yyyy-MM-dd"
placeholder="请选择日期"
></DatePicker>
</FormItem>
</Col>
<Col span="12">
<FormItem label="结束日期" prop="secondTermEndDate">
<DatePicker
:value="strToDate(addForm.secondTermEndDate)"
@on-change="(date) => addForm.secondTermEndDate = dateToStr(date)"
format="yyyy-MM-dd"
placeholder="请选择日期"
></DatePicker>
</FormItem>
</Col>
</Row>
</Form>
<div slot="footer" class="modal-footer">
<Button @click="addModalVisible = false">取消</Button>
<Button type="primary" @click="submitAdd">提交</Button>
</div>
</Modal>
<!-- 编辑弹窗 -->
<Modal v-model="editModalVisible" title="修改学年学期" width="720" class="modalform">
<Form
:model="editForm"
ref="editFormRef"
:rules="formRules"
label-width="120px"
class="modal-form"
>
<div class="form-block-title">学年设置</div>
<FormItem label="新学年">
<Input disabled :value="editForm.year + '学年'" />
</FormItem>
<Row gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="yearStartDate">
<DatePicker
disabled
:value="strToDate(editForm.yearStartDate)"
format="yyyy-MM-dd"
></DatePicker>
</FormItem>
</Col>
<Col span="12">
<FormItem label="结束日期" prop="yearEndDate">
<DatePicker
:value="strToDate(editForm.yearEndDate)"
@on-change="(date) => editForm.yearEndDate = dateToStr(date)"
format="yyyy-MM-dd"
></DatePicker>
</FormItem>
</Col>
</Row>
<div class="form-block-title">学期设置</div>
<FormItem label="升学日期" prop="upgradeDate">
<DatePicker
:value="strToDate(editForm.upgradeDate)"
@on-change="(date) => editForm.upgradeDate = dateToStr(date)"
format="yyyy-MM-dd"
></DatePicker>
</FormItem>
<div class="form-block-title">第一学期设置</div>
<Row gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="firstTermStartDate">
<DatePicker
:value="strToDate(editForm.firstTermStartDate)"
@on-change="(date) => editForm.firstTermStartDate = dateToStr(date)"
format="yyyy-MM-dd"
></DatePicker>
</FormItem>
</Col>
<Col span="12">
<FormItem label="结束日期" prop="firstTermEndDate">
<DatePicker
:value="strToDate(editForm.firstTermEndDate)"
@on-change="(date) => editForm.firstTermEndDate = dateToStr(date)"
format="yyyy-MM-dd"
></DatePicker>
</FormItem>
</Col>
</Row>
<div class="form-block-title">第二学期设置</div>
<Row gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="secondTermStartDate">
<DatePicker
:value="strToDate(editForm.secondTermStartDate)"
@on-change="(date) => editForm.secondTermStartDate = dateToStr(date)"
format="yyyy-MM-dd"
></DatePicker>
</FormItem>
</Col>
<Col span="12">
<FormItem label="结束日期" prop="secondTermEndDate">
<DatePicker
:value="strToDate(editForm.secondTermEndDate)"
@on-change="(date) => editForm.secondTermEndDate = dateToStr(date)"
format="yyyy-MM-dd"
></DatePicker>
</FormItem>
</Col>
</Row>
</Form>
<div slot="footer" class="modal-footer">
<Button @click="editModalVisible = false">取消</Button>
<Button type="primary" @click="submitEdit">保存</Button>
</div>
</Modal>
</div>
</template>
<script>
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: "AcademicYearTerm",
data() {
return {
tableColumns: [
{
title: "学年",
key: "year",
align: "center",
render: (h, params) => {
const row = params.row;
const text = row.nowYear ? `${row.year}学年(当前学年)` : `${row.year}学年`;
return h("div", { style: "line-height: 56px" }, text);
}
},
{
title: "学年开始日期",
key: "yearStartDate",
align: "center",
render: (h, params) => h("div", { style: "line-height: 56px" }, params.row.yearStartDate)
},
{
title: "学年结束日期",
key: "yearEndDate",
align: "center",
render: (h, params) => h("div", { style: "line-height: 56px" }, params.row.yearEndDate)
},
{
title: "学期名称",
key: "termName",
align: "center",
render: (h, params) => {
return h("div", [
h("div", "第一学期"),
h("div", { style: "margin-top: 8px" }, "第二学期")
]);
}
},
{
title: "起始日期",
key: "startDate",
align: "center",
render: (h, params) => {
return h("div", [
h("div", params.row.firstTermStartDate),
h("div", { style: "margin-top: 8px" }, params.row.secondTermStartDate)
]);
}
},
{
title: "截止日期",
key: "endDate",
align: "center",
render: (h, params) => {
return h("div", [
h("div", params.row.firstTermEndDate),
h("div", { style: "margin-top: 8px" }, params.row.secondTermEndDate)
]);
}
},
{
title: "操作",
key: "action",
width: 90,
align: "center",
render: (h, params) => {
return h(
"Button",
{
props: {
icon: "ios-create",
shape: "circle"
},
on: {
click: () => {
this.openEditModal(params.row);
}
}
}
);
}
}
],
yeartermlist: [],
addModalVisible: false,
addForm: {
year: new Date().getFullYear(),
yearStartDate: new Date().getFullYear() + "-07-15",
yearEndDate: "",
upgradeDate: "",
firstTermStartDate: "",
firstTermEndDate: "",
secondTermStartDate: "",
secondTermEndDate: ""
},
editModalVisible: false,
editForm: {
id: null,
year: "",
yearStartDate: "",
yearEndDate: "",
upgradeDate: "",
firstTermStartDate: "",
firstTermEndDate: "",
secondTermStartDate: "",
secondTermEndDate: ""
},
// 修复formRules语法错误
formRules: {
year: [{ required: true, message: "请填写学年", trigger: "blur" }],
yearStartDate: [{ required: true, message: "请选择学年开始日期", trigger: "change" }],
yearEndDate: [{ required: true, message: "请选择学年结束日期", trigger: "change" }],
upgradeDate: [{ required: true, message: "请选择升学日期", trigger: "change" }],
firstTermStartDate: [{ required: true, message: "请选择第一学期开始日期", trigger: "change" }],
firstTermEndDate: [{ required: true, message: "请选择第一学期结束日期", trigger: "change" }],
secondTermStartDate: [{ required: true, message: "请选择第二学期开始日期", trigger: "change" }],
secondTermEndDate: [{ required: true, message: "请选择第二学期结束日期", trigger: "change" }]
}
};
},
mixins: [self_viewport],
mounted() {
this.getschsystemlist();
},
methods: {
// 日期转换:Date对象 → yyyy-MM-dd 标准字符串
dateToStr(date) {
if (!date) return "";
const y = new Date(date).getFullYear();
const m = String(new Date(date).getMonth() + 1).padStart(2, "0");
const d = String(new Date(date).getDate()).padStart(2, "0");
return `${y}-${m}-${d}`;
},
// 日期转换:字符串/后端ISO时间 → Date对象 给DatePicker回填
strToDate(inputStr) {
if (!inputStr) return null;
// 处理后端带T的ISO格式
let str = inputStr.includes("T") ? inputStr.split("T")[0] : inputStr;
const arr = str.split("-");
if (arr.length !== 3) return null;
const year = arr[0];
const month = arr[1].padStart(2, "0");
const day = arr[2].padStart(2, "0");
const standard = `${year}-${month}-${day}`;
const date = new Date(standard);
// 非法日期返回空
return isNaN(date.getTime()) ? null : date;
},
//获取学校绑定学制列表
getschsystemlist() {
const _this = this;
var item = {};
_this.$api.get(APIConfig.BschClassSystem.getyeartermlist, item, resp => {
if (resp.code == 0) {
_this.yeartermlist = resp.data;
} else {
_this.yeartermlist = [];
}
});
},
openAddModal() {
this.$nextTick(() => {
this.$refs.addFormRef?.resetFields();
});
const curYear = new Date().getFullYear();
this.addForm = {
year: curYear,
yearStartDate: curYear + "-07-15",
yearEndDate: "",
upgradeDate: "",
firstTermStartDate: "",
firstTermEndDate: "",
secondTermStartDate: "",
secondTermEndDate: ""
};
this.addModalVisible = true;
},
submitAdd() {
this.$refs.addFormRef.validate(valid => {
if (!valid) return;
const form = this.addForm;
// 1、学年数字校验(对齐后端 @Min(2000) @Positive)
const yearNum = Number(form.year);
if (!Number.isInteger(yearNum) || yearNum <= 0) {
this.$Message.error("学年必须为正整数");
return;
}
if (yearNum < 2000) {
this.$Message.error("学年不能小于2000");
return;
}
// 快速转日期
const getDate = (s) => new Date(s);
const ys = getDate(form.yearStartDate);
const ye = getDate(form.yearEndDate);
const t1s = getDate(form.firstTermStartDate);
const t1e = getDate(form.firstTermEndDate);
const t2s = getDate(form.secondTermStartDate);
const t2e = getDate(form.secondTermEndDate);
const up = getDate(form.upgradeDate);
if (ys > ye) {
this.$Message.error("学年开始日期不能晚于学年结束日期");
return;
}
if (t1s > t1e) {
this.$Message.error("第一学期开始日期不能晚于第一学期结束日期");
return;
}
if (t2s > t2e) {
this.$Message.error("第二学期开始日期不能晚于第二学期结束日期");
return;
}
if (t1e > t2s) {
this.$Message.error("第一学期结束日期不能晚于第二学期开始日期");
return;
}
if (up < ye) {
this.$Message.error("毕业日期不能早于学年结束日期");
return;
}
// 组装提交参数,year传数字,日期传标准yyyy-MM-dd字符串
const submitData = {
year: yearNum,
yearStartDate: form.yearStartDate,
yearEndDate: form.yearEndDate,
firstTermStartDate: form.firstTermStartDate,
firstTermEndDate: form.firstTermEndDate,
secondTermStartDate: form.secondTermStartDate,
secondTermEndDate: form.secondTermEndDate,
upgradeDate: form.upgradeDate
};
const url = APIConfig.BschClassSystem.yeartermadd;
this.$api.post(url, JSON.stringify(submitData), resp => {
if (resp.code == 0) {
this.$Message.success("添加成功!");
this.addModalVisible = false;
this.getschsystemlist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
});
},
openEditModal(row) {
this.$nextTick(() => {
this.$refs.editFormRef?.resetFields();
});
this.editForm = {
id: row.id,
year: row.year,
yearStartDate: row.yearStartDate,
yearEndDate: row.yearEndDate,
upgradeDate: row.upgradeDate,
firstTermStartDate: row.firstTermStartDate,
firstTermEndDate: row.firstTermEndDate,
secondTermStartDate: row.secondTermStartDate,
secondTermEndDate: row.secondTermEndDate
};
this.editModalVisible = true;
},
submitEdit() {
this.$refs.editFormRef.validate(valid => {
if (!valid) return;
const form = this.editForm;
console.log(JSON.stringify(form));
// 学年数字校验
const yearNum = Number(form.year);
if (!Number.isInteger(yearNum) || yearNum <= 0) {
this.$Message.error("学年必须为正整数");
return;
}
if (yearNum < 2000) {
this.$Message.error("学年不能小于2000");
return;
}
const getDate = (s) => new Date(s);
const ys = getDate(form.yearStartDate);
const ye = getDate(form.yearEndDate);
const t1s = getDate(form.firstTermStartDate);
const t1e = getDate(form.firstTermEndDate);
const t2s = getDate(form.secondTermStartDate);
const t2e = getDate(form.secondTermEndDate);
const up = getDate(form.upgradeDate);
if (ys > ye) {
this.$Message.error("学年开始日期不能晚于学年结束日期");
return;
}
if (t1s > t1e) {
this.$Message.error("第一学期开始日期不能晚于第一学期结束日期");
return;
}
if (t2s > t2e) {
this.$Message.error("第二学期开始日期不能晚于第二学期结束日期");
return;
}
if (t1e > t2s) {
this.$Message.error("第一学期结束日期不能晚于第二学期开始日期");
return;
}
if (up < ye) {
this.$Message.error("毕业日期不能早于学年结束日期");
return;
}
// 编辑提交参数
const submitData = {
id: form.id,
year: yearNum,
yearStartDate: form.yearStartDate,
yearEndDate: form.yearEndDate,
firstTermStartDate: form.firstTermStartDate,
firstTermEndDate: form.firstTermEndDate,
secondTermStartDate: form.secondTermStartDate,
secondTermEndDate: form.secondTermEndDate,
upgradeDate: form.upgradeDate
};
const url = APIConfig.BschClassSystem.yeartermedit;
this.$api.post(url, JSON.stringify(submitData), resp => {
if (resp.code == 0) {
this.$Message.success("修改成功!");
this.editModalVisible = false;
this.getschsystemlist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
});
}
}
};
</script>
<style scoped>
.page-container {
padding:0 16px;
background: #fff;
}
.page-title {
font-size: 24px;
font-weight: 500;
margin: 0 0 12px;
}
.btn-top-wrap {
background: #f7f8fa;
border-radius: 4px;
padding: 10px;
/* margin: 10px 0 15px; */
}
.term-table /deep/ .ivu-table-row td {
padding: 6px 0;
}
.form-block-title {
font-size: 16px;
font-weight: 500;
margin: 10px 0 10px;
padding-left: 6px;
border-left: 4px solid #2d8cf0;
}
.modal-footer {
text-align: right;
}
.modalform /deep/ .ivu-modal-body{
padding:10px;
}
</style>
+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>
+39
View File
@@ -0,0 +1,39 @@
<template>
<div style="height:100%;">
<Christianity v-if="type==1"></Christianity>
<HigherEducation v-if="type==2"></HigherEducation>
</div>
</template>
<script>
import Christianity from "./studentManage/Christianity";
import HigherEducation from "./studentManage/HigherEducation";
export default {
name: 'studentManage',
components: { Christianity,HigherEducation },
data() {
return {
type:0,
}
},
created(){
this.getType();
},
methods: {
//获取学制
getType:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getType, item, resp => {
if(resp.code==0)
{
_this.type = resp.data;
}else{
_this.type=1;
}
});
},
},
}
</script>
+905
View File
@@ -0,0 +1,905 @@
<template>
<div class="student-manage-wrap">
<!-- 左侧树形区域 固定高度滚动 -->
<div class="left-tree-box">
<my-tree :treeData="treeData" :selectId="selectnodeid" @on-tree-click="treeLink"></my-tree>
</div>
<!-- 右侧主体区域 -->
<div class="right-main-box">
<!-- 顶部按钮+搜索栏 -->
<div class="top-bar">
<div class="search-wrap" style="width: 100%;">
<div class="btn-left-group fl">
<Button type="primary" @click="openAddStudent">添加学生</Button>
<Button type="success" style="margin-left:8px" @click="openImportModal">学生导入</Button>
<Button type="success" style="margin-left:8px" @click="openStuIdImportModal">学号导入</Button>
<Button type="primary" style="margin-left:8px" @click="batchTransferStudent">转出学生</Button>
<Button type="primary" style="margin-left:8px" @click="openMoveClassModal">学生调班</Button>
<Button type="error" style="margin-left:8px" @click="openChangeStatusModal">学生异动</Button>
</div>
<Input
class="fr"
v-model="searchKey"
placeholder="姓名,身份证,账号"
search
style="width:300px"
@on-search="handleSearch"
/>
</div>
</div>
<!-- 表格容器:自适应剩余高度滚动 -->
<div class="table-scroll-wrap">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
:height="useTableHeight()+22"
@on-selection-change="selectionChange"
></Table>
</div>
<!-- 左下角批量删除 + 底部分页固定 -->
<div class="bottom-bar">
<Button type="error" @click="batchDelete">删除</Button>
<div class="page-right">
<span style="margin-right:12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40,50]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
</div>
<!-- 弹窗1:添加学生 -->
<Modal v-model="addModalVisible" title="添加学生" width="560">
<Form ref="addForm" :model="addForm" :rules="formRules" label-width="90">
<FormItem label="年级" prop="grade">
<Select v-model="addForm.grade" style="width:100%" @on-change="getschclassselect">
<Option v-for="item in gradeselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="班级" prop="classId">
<Select v-model="addForm.classId" style="width:100%">
<Option v-for="item in schclassselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="学生姓名" prop="name">
<Input v-model="addForm.name" placeholder="请输入学生姓名"></Input>
</FormItem>
<FormItem label="学号">
<Input v-model="addForm.stuClassNo" placeholder="请输入当前学号"></Input>
</FormItem>
<FormItem label="身份证号">
<Input v-model="addForm.idCard" placeholder="请输入身份证号"></Input>
</FormItem>
<FormItem label="账号" prop="account">
<Input v-model="addForm.account" placeholder="请输入账号"></Input>
</FormItem>
<FormItem label="密码" prop="pwd">
<Input v-model="addForm.pwd" type="password" placeholder="请输入密码"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="addModalVisible=false">取消</Button>
<Button type="primary" @click="submitAddStudent">提交</Button>
</div>
</Modal>
<!-- 弹窗2:编辑学生 -->
<Modal v-model="editModalVisible" title="编辑学生" width="560">
<Form ref="editForm" :model="editForm" :rules="formRules" label-width="90">
<FormItem label="当前年级" prop="grade">
<Select disabled v-model="editForm.grade" style="width:100%" @on-change="getschclassselect">
<Option v-for="item in gradeselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="当前班级" prop="classId">
<Select disabled v-model="editForm.classId" style="width:100%">
<Option v-for="item in schclassselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="学生姓名" prop="name">
<Input v-model="editForm.name"></Input>
</FormItem>
<FormItem label="学号">
<Input v-model="editForm.stuClassNo"></Input>
</FormItem>
<FormItem label="身份证号">
<Input v-model="editForm.idCard"></Input>
</FormItem>
<FormItem label="账号" prop="account">
<Input v-model="editForm.account"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="editModalVisible=false">取消</Button>
<Button type="primary" @click="submitEditStudent">提交</Button>
</div>
</Modal>
<!-- 弹窗3:学生信息导入 -->
<Modal v-model="importModalVisible" title="学生信息导入" width="520">
<!-- <div style="padding:10px 0;">
<div style="display:flex;align-items:center;">
<Upload action="" :before-upload="beforeFileUpload">
<Button>选择要上传的文件</Button>
</Upload>
<a href="javascript:;" style="margin-left:20px;color:#2d7ff9" @click="downloadStuTemplate">点击链接下载[用户导入模板.xls]</a>
</div>
</div>
<div slot="footer" align="right">
<Button @click="importModalVisible=false">取消</Button>
<Button type="primary" @click="confirmImport">确定</Button>
</div> -->
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<FormItem label="Excel上传:" style="margin-bottom:10px;width:100%;">
<i-input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></i-input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
:show-upload-list="false"
:headers="uploadHeader"
accept=".xls,.xlsx"
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.listData.data!=null" style="margin-bottom:10px">
<div v-if="UpParmas.listData.code==0" style="color:red">导入失败,请修改Excel文件中相关数据后再重新上传!</div>
<div v-if="UpParmas.listData.code!=0" style="color:green">导入成功!</div>
</FormItem>
</Form>
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[学生信息导入模板.xls]</a>
</template>
</Alert>
<Table :columns="UpParmas.columns" :data="UpParmas.listData.data" v-if="UpParmas.listData.data"></Table>
</div>
<Spin fix v-if="spinShow">
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</Modal>
<!-- 弹窗4:学号导入 -->
<Modal v-model="stuIdImportModalVisible" title="学生学号导入" width="520">
<div class="main-contents">
<Form ref="UpParmas2.datapre" :model="UpParmas2.datapre" :label-width="100" inline>
<FormItem label="Excel上传:" style="margin-bottom:10px;width:100%;">
<i-input type="text" style="width:90%;" v-model="UpParmas2.fileName" class="import-input" placeholder="未选中任何文件.." readonly></i-input>
<Upload class="fr btn"
:action="UpParmas2.actionUrl"
:data="UpParmas2.datapre"
:show-upload-list="false"
:headers="uploadHeader"
accept=".xls,.xlsx"
:before-upload="handleUpload2"
:on-success="importFile2"
>
<i-button icon="md-add"></i-button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas2.listData.data!=null" style="margin-bottom:10px">
<div v-if="UpParmas2.listData.code==0" style="color:red">导入失败,请修改Excel文件中相关数据后再重新上传!</div>
<div v-if="UpParmas2.listData.code!=0" style="color:green">导入成功!</div>
</FormItem>
</Form>
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu2" style="font-size:12px;">点击链接下载[学生学号信息导入模板.xls]</a>
</template>
</Alert>
<Table :columns="UpParmas2.columns" :data="UpParmas2.listData.data" v-if="UpParmas2.listData.data"></Table>
</div>
<Spin fix v-if="spinShow">
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<i-Button @click="UpfileModalcloseclick2" style="margin-left: 8px">关闭</i-Button>
</div>
</Modal>
<!-- 弹窗5:学生调班 -->
<Modal v-model="moveClassModalVisible" title="学生调班" width="500">
<div style="padding:10px 0;">
<div style="margin-bottom:16px;display:flex;align-items:center;">
<span style="width:90px;">当前班级</span>
<Input v-model="currentSelectClass" disabled style="flex:1;"></Input>
</div>
<div>
<span style="width:90px;display:inline-block;margin-bottom:10px;">目标班级</span>
<div class="class-btn-wrap">
<Button
v-for="item in filterschclassselectlist"
:key="item.id"
:type="targetClass===item.id?'primary':'default'"
style="margin:6px"
@click="targetClass=item.id"
size="small"
>{{ item.name }}</Button>
</div>
</div>
</div>
<div slot="footer" align="right">
<Button @click="moveClassModalVisible=false">取消</Button>
<Button type="primary" @click="submitMoveClass">提交</Button>
</div>
</Modal>
<!-- 弹窗6:学生异动 -->
<Modal v-model="changeStatusModalVisible" title="学生异动" width="520">
<div style="padding:10px 0;">
<div style="margin-bottom:16px;display:flex;align-items:center;">
<span style="width:90px;">当前学生</span>
<Input v-model="currentStuName" disabled style="flex:1;"></Input>
</div>
<div>
<span style="width:90px;display:inline-block;margin-bottom:10px;">异动情况</span>
<div class="status-btn-wrap">
<Button
v-for="item in studentabnormallist"
:key="item.id"
:type="selectedStatus===item.id?'primary':'default'"
style="margin:6px"
@click="selectedStatus=item.id"
size="small"
>{{ item.name }}</Button>
</div>
</div>
</div>
<div slot="footer" align="right">
<Button @click="changeStatusModalVisible=false">取消</Button>
<Button type="primary" @click="submitChangeStatus">确定</Button>
</div>
</Modal>
<!-- 弹窗7:转出学生弹窗 -->
<Modal v-model="transferStudentModal" title="转出学生" width="420">
<p>确认将选中学生做转出操作?</p>
<div slot="footer" align="right">
<Button @click="transferStudentModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmTransferStudent">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'StudentManage',
components: { myTree },
data() {
return {
// 左侧树形数据
treeData: [],
selectnodeid:'',
schsystemId:'',
gradeId:'',
classId:'',
gradeselectlist:[],
classOptionList: [],
// 搜索
searchKey: '',
// 分页
pageNum: 1,
pageSize: 40,
total: 0,
selectedRows: [],
// 表格列配置
tableColumns: [],
tableData: [],
spinShow:false,
UpParmas:{
fileName:'',
listData:{},
datapre:{},
actionUrl:APIConfig.BschClassSystem.importStudents,
columns:[
{
title: '行号',
key: 'rows',
align: 'center',
width:'80'
},
{
title: '内容',
key: 'columnValue',
align: 'center',
},
{
title: '错误详情',
key: 'errorMsg',
align: 'center',
}
],
},
UpParmas2:{
fileName:'',
listData:{},
datapre:{},
actionUrl:APIConfig.BschClassSystem.importStudentClassNo,
columns:[
{
title: '行号',
key: 'rows',
align: 'center',
width:'80'
},
{
title: '内容',
key: 'columnValue',
align: 'center',
},
{
title: '错误详情',
key: 'errorMsg',
align: 'center',
}
],
},
// 弹窗开关
addModalVisible: false,
editModalVisible: false,
importModalVisible: false,
stuIdImportModalVisible: false,
moveClassModalVisible: false,
changeStatusModalVisible: false,
transferStudentModal:false,
// 新增学生表单
addForm: { grade:'', classId:'', name:'', stuClassNo:'', idCard:'', account:'', pwd:'' },
// 编辑学生表单
editForm: { grade:'', classId:'', name:'', stuClassNo:'', idCard:'', account:'', id:null,stuId:0,status:1 },
// 调班弹窗
currentSelectClass: '',
targetClass: '',
// 异动弹窗
currentStuName: '',
selectedStatus: 1,
studentabnormallist: [],
formRules: {
grade: [{ required: true, message: '请选择年级', trigger: 'change' }],
classId: [{ required: true, message: '请选择班级', trigger: 'change' }],
name: [{ required: true, message: '请输入学生姓名', trigger: 'blur' }],
account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
pwd: [{ required: true, message: '请输入密码', trigger: 'blur' }],
}
}
},
mixins: [self_viewport],
computed: {
uploadHeader() { //附件上传header
let config = {};
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
config["currentRole"] = currentRole;
}
return config;
},
},
created() {
this.getgradeselect();
this.getClassTree();
this.initTableColumn()
},
methods: {
//获取年级下拉
getgradeselect:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getgradeselect, item, resp => {
if(resp.code==0)
{
this.gradeselectlist=resp.data;
}
});
},
//获取班级下拉
getschclassselect:function(){
const _this = this;
var item={
gradeId:_this.gradeId
};
_this.$api.get(APIConfig.BschClassSystem.getschclassselect, item, resp => {
if(resp.code==0)
{
this.schclassselectlist=resp.data;
}
});
},
//获取学生状态列表
getstudentabnormal:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getstudentabnormal, item, resp => {
if(resp.code==0)
{
this.studentabnormallist=resp.data;
}
});
},
//获取班级树
getClassTree:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getClassTree, item, resp => {
if(resp.code==0)
{
this.treeData=resp.data;
if(resp.data.children.length>0){
// 起始节点为 data
const pathArr = _this.getFirstPath(resp.data);
_this.schsystemId=pathArr[pathArr.length-1].schsystemId;
_this.gradeId=pathArr[pathArr.length-1].gradeId+'';
_this.classId=pathArr[pathArr.length-1].classId+'';
_this.selectnodeid=pathArr[pathArr.length-1].id;
_this.getschclassselect();
_this.loadData();
}
}
});
},
//获取最后一级的map
getFirstPath(rootNode) {
const path = [];
let cur = rootNode;
while (cur) {
// 存入当前层级关键信息
path.push({
id: cur.id,
name: cur.name,
type: cur.type,
schsystemId:cur.schsystemId,
gradeId:cur.gradeId,
classId:cur.classId,
});
// 有子节点就取第一个,没有就终止
if (cur.children && cur.children.length > 0) {
cur = cur.children[0];
} else {
cur = null;
}
}
return path;
},
// 初始化表格列 配置式columns
initTableColumn() {
this.tableColumns = [
{type: 'selection',width: 55,align: 'center'},
{ title: '姓名', key: 'name', align: 'center' },
{ title: '用户名', key: 'account', align: 'center' },
{ title: '学号', key: 'stuClassNo', align: 'center', width: 140 },
{ title: '身份证', key: 'idCard', align: 'center' },
{ title: '班级', key: 'className', align: 'center' },
{ title: '状态', key: 'statusName', align: 'center', width: 90 },
{
title: '操作',
align: 'center',
width: 80,
render: (h, params) => {
return h('Button', {
props: { type: 'text', icon: 'md-create', size: 'large' },
on: { click: () => this.openEditStudent(params.row) }
})
}
}
]
},
// 树形选中切换
treeLink(treeNode) {
this.schsystemId=treeNode.schsystemId;
this.gradeId=treeNode.gradeId?treeNode.gradeId+'':'';
this.classId=treeNode.classId?treeNode.classId+'':'';
this.selectnodeid=treeNode.id;
this.pageNum = 1
this.loadData()
},
// 多选行回调
selectionChange(rows) {
this.selectedRows = rows
},
// 加载列表数据
loadData() {
const _this = this;
var item = {
schsystemId:_this.schsystemId,
gradeId:_this.gradeId,
classId:_this.classId,
keyword:_this.searchKey,
page:_this.pageNum-1,
size:_this.pageSize
};
_this.$api.get(APIConfig.BschClassSystem.getstudentsearch, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
// 搜索
handleSearch() {
this.pageNum = 1;
this.loadData();
},
// 分页切换
pageChange(p) {
this.pageNum = p
this.loadData()
},
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadData()
},
// 新增学生
openAddStudent() {
this.addModalVisible = true;
this.addForm = { grade:this.gradeId+'', classId:this.classId+'', name:'', stuClassNo:'', idCard:'', account:'', pwd:'' }
},
submitAddStudent() {
this.$refs.addForm.validate(valid => {
if (!valid) return;
// 新增
const newItem = {
classId:this.addForm.classId,
name:this.addForm.name,
stuClassNo:this.addForm.stuClassNo,
idCard:this.addForm.idCard,
account:this.addForm.account,
pwd:this.addForm.pwd,
stuId:0,
status:1
}
const url = APIConfig.BschClassSystem.studentsave;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("新增学生成功!");
this.addModalVisible = false
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
})
},
// 编辑学生
openEditStudent(row) {
this.editModalVisible = true
this.editForm = {
id: row.id,
grade:row.gradeId+'',
classId: row.classId+'',
name: row.name,
stuId: row.stuId,
idCard: row.idCard,
stuClassNo:row.stuClassNo,
account: row.account,
status:row.status
}
},
submitEditStudent() {
this.$refs.editForm.validate(valid => {
if (!valid) return;
// 编辑
const newItem = {
id: this.editForm.id,
classId:this.editForm.classId,
name:this.editForm.name,
stuClassNo:this.editForm.stuClassNo,
idCard:this.editForm.idCard,
account:this.editForm.account,
stuId:this.editForm.stuId,
status:this.editForm.status
}
const url = APIConfig.BschClassSystem.studentsave;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("编辑保存成功!");
this.editModalVisible = false
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
})
},
// 批量删除
batchDelete() {
if (!this.selectedRows.length) {
return this.$Message.warning('请先勾选学生')
}
this.$Modal.confirm({
title: '提示',
content: '确定删除选中学生?删除不可恢复',
onOk: () => {
let idlist=this.selectedRows.map(a=>a.id);
const url = APIConfig.BschClassSystem.studentdelete+idlist.join(',');
this.$api.post(url,{}, resp => {
if (resp.code == 0) {
this.$Message.success("删除成功!");
this.selectedRows = []
this.loadData()
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
})
},
// 学生导入
openImportModal() { this.importModalVisible = true },
//导入成功返回
importFile(response, file, fileList){
const _this=this;
console.log(response);
console.log(file);
_this.spinShow=false;
_this.UpParmas.fileName=file.name;
_this.UpParmas.listData=response;
if(response.State)
{
_this.$Message.info('导入成功!');
_this.loadData()
}else{
_this.$Message.info(response.Message);
}
},
//附件上传之前的方法
handleUpload:function(file){
const _this=this;
_this.spinShow=true;
},
MoBanExceldaochu() {
var _this = this;
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
var url=APIConfig.BschClassSystem.studentdownloadTemplate+'?schId='+schId;
window.location.href=url;
}else{
this.$Message.info('获取不了学校!');
}
},
//附件上传模态框关闭事件
UpfileModalcloseclick:function(){
this.importModalVisible=false;
this.UpParmas={
fileName:'',
listData:{},
datapre:{},
}
},
//按照学生学号导入
openStuIdImportModal() { this.stuIdImportModalVisible = true },
//导入成功返回
importFil2(response, file, fileList){
const _this=this;
console.log(response);
console.log(file);
_this.spinShow=false;
_this.UpParmas2.fileName=file.name;
_this.UpParmas2.listData=response;
if(response.State)
{
_this.$Message.info('导入成功!');
_this.loadData()
}else{
_this.$Message.info(response.Message);
}
},
//附件上传之前的方法
handleUpload2:function(file){
const _this=this;
_this.spinShow=true;
},
MoBanExceldaochu2() {
var _this = this;
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
var url=APIConfig.BschClassSystem.studentClassNoTemplate+'?schId='+schId;
window.location.href=url;
}else{
this.$Message.info('获取不了学校!');
}
},
//附件上传模态框关闭事件
UpfileModalcloseclick2:function(){
this.stuIdImportModalVisible=false;
this.UpParmas2={
fileName:'',
listData:{},
datapre:{},
}
},
//学生转出
batchTransferStudent(){
if (!this.selectedRows.length) return this.$Message.warning('请勾选学生');
this.transferStudentModal = true
},
//转出事件
confirmTransferStudent() {
const idlist = this.selectedRows.map(i => i.id);
const url = APIConfig.BschClassSystem.studenttransferOut+idlist.join(',');
this.$api.post(url,{}, resp => {
if (resp.code == 0) {
this.$Message.success("转出操作完成!");
this.selectedRows = [];
this.transferStudentModal = false;
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
},
// 学生调班
openMoveClassModal() {
if (!this.selectedRows.length) return this.$Message.warning('请勾选学生');
this.moveClassModalVisible = true;
this.currentSelectClass = this.selectedRows[0].className;
this.filterschclassselectlist=this.schclassselectlist.filter(item => item.id != this.classId);
this.targetClass = '';
},
submitMoveClass() {
if (!this.targetClass) return this.$Message.warning('请选择目标班级')
const newItem = {
transferClassId: this.targetClass,
stuIds:this.selectedRows.map(a=>a.id)
}
const url = APIConfig.BschClassSystem.studenttransferClass;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("调班成功!");
this.moveClassModalVisible = false
this.selectedRows = []
this.loadData()
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
},
// 学生异动
openChangeStatusModal() {
if (!this.selectedRows.length) return this.$Message.warning('请勾选学生');
this.changeStatusModalVisible = true;
this.currentStuName = this.selectedRows[0].name;
this.getstudentabnormal();
this.selectedStatus = 1;
},
submitChangeStatus() {
const newItem = {
abnormalStatus: this.selectedStatus,
stuIds:this.selectedRows.map(a=>a.id)
}
const url = APIConfig.BschClassSystem.studentsetAbnormalStatus;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success('异动状态修改完成');
this.changeStatusModalVisible = false;
this.selectedRows = [];
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
}
}
</script>
<style scoped>
.student-manage-wrap {
width: 100%;
height: 100%;
display: flex;
gap: 16px;
box-sizing: border-box;
padding: 12px;
}
/* 左侧树固定高度 */
.left-tree-box {
width: 260px;
border: 1px solid #e6e6e6;
overflow-y: auto;
flex-shrink: 0;
}
/* 右侧整体弹性布局 */
.right-main-box {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
/* 顶部栏 */
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
margin-bottom: 10px;
flex-shrink: 0;
}
.btn-left-group {
display: flex;
align-items: center;
}
/* 表格自适应滚动容器 */
.table-scroll-wrap {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
height: 100%;
}
/* 底部栏:分页固定底部 */
.bottom-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
flex-shrink: 0;
}
.page-right {
display: flex;
align-items: center;
}
/* 调班、异动弹窗按钮排版 */
.class-btn-wrap, .status-btn-wrap {
display: flex;
flex-wrap: wrap;
}
</style>
<style>
.ivu-alert-desc{
font-size: 12px;
}
</style>
+890
View File
@@ -0,0 +1,890 @@
<template>
<div class="student-manage-wrap">
<!-- 左侧树形区域 固定高度滚动 -->
<div class="left-tree-box">
<my-tree :treeData="treeData" :selectId="selectnodeid" @on-tree-click="treeLink"></my-tree>
</div>
<!-- 右侧主体区域 -->
<div class="right-main-box">
<!-- 顶部按钮+搜索栏 -->
<div class="top-bar">
<div class="search-wrap" style="width: 100%;">
<div class="btn-left-group fl">
<Button type="primary" @click="openAddStudent">添加学生</Button>
<Button type="success" style="margin-left:8px" @click="openImportModal">学生导入</Button>
<Button type="success" style="margin-left:8px" @click="openStuIdImportModal">学号导入</Button>
<Button type="primary" style="margin-left:8px" @click="batchTransferStudent">转出学生</Button>
<Button type="primary" style="margin-left:8px" @click="openMoveClassModal">学生调班</Button>
<Button type="error" style="margin-left:8px" @click="openChangeStatusModal">学生异动</Button>
</div>
<Input
class="fr"
v-model="searchKey"
placeholder="姓名,身份证,账号"
search
style="width:300px"
@on-search="handleSearch"
/>
</div>
</div>
<!-- 表格容器:自适应剩余高度滚动 -->
<div class="table-scroll-wrap">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
:height="useTableHeight()+22"
@on-selection-change="selectionChange"
></Table>
</div>
<!-- 左下角批量删除 + 底部分页固定 -->
<div class="bottom-bar">
<Button type="error" @click="batchDelete">删除</Button>
<div class="page-right">
<span style="margin-right:12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40,50]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
</div>
<!-- 弹窗1:添加学生 -->
<Modal v-model="addModalVisible" title="添加学生" width="560">
<Form ref="addForm" :model="addForm" :rules="formRules" label-width="90">
<FormItem label="年级" prop="grade">
<Select v-model="addForm.grade" style="width:100%" @on-change="getschclassselect">
<Option v-for="item in gradeselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="班级" prop="classId">
<Select v-model="addForm.classId" style="width:100%">
<Option v-for="item in schclassselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="学生姓名" prop="name">
<Input v-model="addForm.name" placeholder="请输入学生姓名"></Input>
</FormItem>
<FormItem label="学号">
<Input v-model="addForm.stuClassNo" placeholder="请输入当前学号"></Input>
</FormItem>
<FormItem label="身份证号">
<Input v-model="addForm.idCard" placeholder="请输入身份证号"></Input>
</FormItem>
<FormItem label="账号" prop="account">
<Input v-model="addForm.account" placeholder="请输入账号"></Input>
</FormItem>
<FormItem label="密码" prop="pwd">
<Input v-model="addForm.pwd" type="password" placeholder="请输入密码"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="addModalVisible=false">取消</Button>
<Button type="primary" @click="submitAddStudent">提交</Button>
</div>
</Modal>
<!-- 弹窗2:编辑学生 -->
<Modal v-model="editModalVisible" title="编辑学生" width="560">
<Form ref="editForm" :model="editForm" :rules="formRules" label-width="90">
<FormItem label="当前年级" prop="grade">
<Select disabled v-model="editForm.grade" style="width:100%" @on-change="getschclassselect">
<Option v-for="item in gradeselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="当前班级" prop="classId">
<Select disabled v-model="editForm.classId" style="width:100%">
<Option v-for="item in schclassselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="学生姓名" prop="name">
<Input v-model="editForm.name"></Input>
</FormItem>
<FormItem label="学号">
<Input v-model="editForm.stuClassNo"></Input>
</FormItem>
<FormItem label="身份证号">
<Input v-model="editForm.idCard"></Input>
</FormItem>
<FormItem label="账号" prop="account">
<Input v-model="editForm.account"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="editModalVisible=false">取消</Button>
<Button type="primary" @click="submitEditStudent">提交</Button>
</div>
</Modal>
<!-- 弹窗3:学生信息导入 -->
<Modal v-model="importModalVisible" title="学生信息导入" width="520">
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<FormItem label="Excel上传:" style="margin-bottom:10px;width:100%;">
<i-input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></i-input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
:show-upload-list="false"
:headers="uploadHeader"
accept=".xls,.xlsx"
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.listData.data!=null" style="margin-bottom:10px">
<div v-if="UpParmas.listData.code==0" style="color:red">导入失败,请修改Excel文件中相关数据后再重新上传!</div>
<div v-if="UpParmas.listData.code!=0" style="color:green">导入成功!</div>
</FormItem>
</Form>
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[学生信息导入模板.xls]</a>
</template>
</Alert>
<Table :columns="UpParmas.columns" :data="UpParmas.listData.data" v-if="UpParmas.listData.data"></Table>
</div>
<Spin fix v-if="spinShow">
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</Modal>
<!-- 弹窗4:学号导入 -->
<Modal v-model="stuIdImportModalVisible" title="学生学号导入" width="520">
<div class="main-contents">
<Form ref="UpParmas2.datapre" :model="UpParmas2.datapre" :label-width="100" inline>
<FormItem label="Excel上传:" style="margin-bottom:10px;width:100%;">
<i-input type="text" style="width:90%;" v-model="UpParmas2.fileName" class="import-input" placeholder="未选中任何文件.." readonly></i-input>
<Upload class="fr btn"
:action="UpParmas2.actionUrl"
:data="UpParmas2.datapre"
:show-upload-list="false"
:headers="uploadHeader"
accept=".xls,.xlsx"
:before-upload="handleUpload2"
:on-success="importFile2"
>
<i-button icon="md-add"></i-button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas2.listData.data!=null" style="margin-bottom:10px">
<div v-if="UpParmas2.listData.code==0" style="color:red">导入失败,请修改Excel文件中相关数据后再重新上传!</div>
<div v-if="UpParmas2.listData.code!=0" style="color:green">导入成功!</div>
</FormItem>
</Form>
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu2" style="font-size:12px;">点击链接下载[学生学号信息导入模板.xls]</a>
</template>
</Alert>
<Table :columns="UpParmas2.columns" :data="UpParmas2.listData.data" v-if="UpParmas2.listData.data"></Table>
</div>
<Spin fix v-if="spinShow">
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<i-Button @click="UpfileModalcloseclick2" style="margin-left: 8px">关闭</i-Button>
</div>
</Modal>
<!-- 弹窗5:学生调班 -->
<Modal v-model="moveClassModalVisible" title="学生调班" width="500">
<div style="padding:10px 0;">
<div style="margin-bottom:16px;display:flex;align-items:center;">
<span style="width:90px;">当前班级</span>
<Input v-model="currentSelectClass" disabled style="flex:1;"></Input>
</div>
<div>
<span style="width:90px;display:inline-block;margin-bottom:10px;">目标班级</span>
<div class="class-btn-wrap">
<Button
v-for="item in filterschclassselectlist"
:key="item.id"
:type="targetClass===item.id?'primary':'default'"
style="margin:6px"
@click="targetClass=item.id"
size="small"
>{{ item.name }}</Button>
</div>
</div>
</div>
<div slot="footer" align="right">
<Button @click="moveClassModalVisible=false">取消</Button>
<Button type="primary" @click="submitMoveClass">提交</Button>
</div>
</Modal>
<!-- 弹窗6:学生异动 -->
<Modal v-model="changeStatusModalVisible" title="学生异动" width="520">
<div style="padding:10px 0;">
<div style="margin-bottom:16px;display:flex;align-items:center;">
<span style="width:90px;">当前学生</span>
<Input v-model="currentStuName" disabled style="flex:1;"></Input>
</div>
<div>
<span style="width:90px;display:inline-block;margin-bottom:10px;">异动情况</span>
<div class="status-btn-wrap">
<Button
v-for="item in studentabnormallist"
:key="item.id"
:type="selectedStatus===item.id?'primary':'default'"
style="margin:6px"
@click="selectedStatus=item.id"
size="small"
>{{ item.name }}</Button>
</div>
</div>
</div>
<div slot="footer" align="right">
<Button @click="changeStatusModalVisible=false">取消</Button>
<Button type="primary" @click="submitChangeStatus">确定</Button>
</div>
</Modal>
<!-- 弹窗7:转出学生弹窗 -->
<Modal v-model="transferStudentModal" title="转出学生" width="420">
<p>确认将选中学生做转出操作?</p>
<div slot="footer" align="right">
<Button @click="transferStudentModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmTransferStudent">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'StudentManage',
components: { myTree },
data() {
return {
// 左侧树形数据
treeData: [],
selectnodeid:'',
enrollYear:'',
classId:'',
gradeselectlist:[],
classOptionList: [],
// 搜索
searchKey: '',
// 分页
pageNum: 1,
pageSize: 40,
total: 0,
selectedRows: [],
// 表格列配置
tableColumns: [],
tableData: [],
spinShow:false,
UpParmas:{
fileName:'',
listData:{},
datapre:{},
actionUrl:APIConfig.BschClassSystem.importStudents,
columns:[
{
title: '行号',
key: 'rows',
align: 'center',
width:'80'
},
{
title: '内容',
key: 'columnValue',
align: 'center',
},
{
title: '错误详情',
key: 'errorMsg',
align: 'center',
}
],
},
UpParmas2:{
fileName:'',
listData:{},
datapre:{},
actionUrl:APIConfig.BschClassSystem.importStudentClassNo,
columns:[
{
title: '行号',
key: 'rows',
align: 'center',
width:'80'
},
{
title: '内容',
key: 'columnValue',
align: 'center',
},
{
title: '错误详情',
key: 'errorMsg',
align: 'center',
}
],
},
// 弹窗开关
addModalVisible: false,
editModalVisible: false,
importModalVisible: false,
stuIdImportModalVisible: false,
moveClassModalVisible: false,
changeStatusModalVisible: false,
transferStudentModal:false,
// 新增学生表单
addForm: { grade:'', classId:'', name:'', stuClassNo:'', idCard:'', account:'', pwd:'' },
// 编辑学生表单
editForm: { grade:'', classId:'', name:'', stuClassNo:'', idCard:'', account:'', id:null,stuId:0,status:1 },
// 调班弹窗
currentSelectClass: '',
targetClass: '',
filterschclassselectlist: [],
// 异动弹窗
currentStuName: '',
selectedStatus: 1,
studentabnormallist: [],
formRules: {
grade: [{ required: true, message: '请选择年级', trigger: 'change' }],
classId: [{ required: true, message: '请选择班级', trigger: 'change' }],
name: [{ required: true, message: '请输入学生姓名', trigger: 'blur' }],
account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
pwd: [{ required: true, message: '请输入密码', trigger: 'blur' }],
}
}
},
mixins: [self_viewport],
computed: {
uploadHeader() { //附件上传header
let config = {};
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
config["currentRole"] = currentRole;
}
return config;
},
},
created() {
this.getgradeselect();
this.getClassTree();
this.initTableColumn()
},
methods: {
//获取年级下拉
getgradeselect:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getgradeselect, item, resp => {
if(resp.code==0)
{
this.gradeselectlist=resp.data;
}
});
},
//获取班级下拉
getschclassselect:function(){
const _this = this;
var item={
gradeId:_this.enrollYear
};
_this.$api.get(APIConfig.BschClassSystem.getschclassselect, item, resp => {
if(resp.code==0)
{
this.schclassselectlist=resp.data;
}
});
},
//获取学生状态列表
getstudentabnormal:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getstudentabnormal, item, resp => {
if(resp.code==0)
{
this.studentabnormallist=resp.data;
}
});
},
//获取班级树
getClassTree:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getHigherSchClassTree, item, resp => {
if(resp.code==0)
{
this.treeData=resp.data;
if(resp.data.children.length>0){
// 起始节点为 data
const pathArr = _this.getFirstPath(resp.data);
_this.enrollYear=pathArr[pathArr.length-1].enrollYear+'',
_this.classId=pathArr[pathArr.length-1].classId;
_this.selectnodeid=pathArr[pathArr.length-1].id;
_this.getschclassselect();
_this.loadData();
}
}
});
},
//获取最后一级的map
getFirstPath(rootNode) {
const path = [];
let cur = rootNode;
while (cur) {
// 存入当前层级关键信息
path.push({
id: cur.id,
name: cur.name,
type: cur.type,
enrollYear:cur.enrollYear,
classId:cur.classId
});
// 有子节点就取第一个,没有就终止
if (cur.children && cur.children.length > 0) {
cur = cur.children[0];
} else {
cur = null;
}
}
return path;
},
// 初始化表格列 配置式columns
initTableColumn() {
this.tableColumns = [
{
type: 'selection',
width: 55,
align: 'center'
},
{ title: '姓名', key: 'name', align: 'center', width: 110 },
{ title: '用户名', key: 'account', align: 'center', width: 90 },
{ title: '学号', key: 'stuClassNo', align: 'center' },
{ title: '身份证', key: 'idCard', align: 'center' },
{ title: '班级', key: 'className', align: 'center' },
{ title: '状态', key: 'statusName', align: 'center', width: 90 },
{
title: '操作',
align: 'center',
width: 80,
render: (h, params) => {
return h('Button', {
props: { type: 'text', icon: 'md-create',type:'large' },
on: { click: () => this.openEditStudent(params.row) }
})
}
}
]
},
// 树形选中切换
treeLink(treeNode) {
this.enrollYear=treeNode.enrollYear?treeNode.enrollYear+'':'';
this.classId=treeNode.classId?treeNode.classId+'':'';
this.selectnodeid=treeNode.id;
this.pageNum = 1
this.loadData()
},
// 多选行回调
selectionChange(rows) {
this.selectedRows = rows
},
// 加载列表数据
loadData() {
const _this = this;
var item = {
year:_this.enrollYear,
classId:_this.classId,
keyword:_this.keyword,
page:_this.pageNum-1,
size:_this.pageSize
};
_this.$api.get(APIConfig.BschClassSystem.gethigherSchSearch, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
// 搜索
handleSearch() {
this.pageNum = 1
this.loadData()
},
// 分页切换
pageChange(p) {
this.pageNum = p
this.loadData()
},
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadData()
},
// 新增学生
openAddStudent() {
this.addModalVisible = true;
this.addForm = { grade:this.enrollYear+'', classId:this.classId+'', name:'', stuClassNo:'', idCard:'', account:'', pwd:'' }
},
submitAddStudent() {
this.$refs.addForm.validate(valid => {
if (!valid) return;
// 新增
const newItem = {
classId:this.addForm.classId,
name:this.addForm.name,
stuClassNo:this.addForm.stuClassNo,
idCard:this.addForm.idCard,
account:this.addForm.account,
pwd:this.addForm.pwd,
stuId:0,
status:1
}
const url = APIConfig.BschClassSystem.studentsave;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("新增学生成功!");
this.addModalVisible = false
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
})
},
// 编辑学生
openEditStudent(row) {
this.editModalVisible = true
this.editForm = {
id: row.id,
grade:row.gradeId+'',
classId: row.classId+'',
name: row.name,
stuId: row.stuId,
idCard: row.idCard,
stuClassNo:row.stuClassNo,
account: row.account,
status:row.status
}
},
submitEditStudent() {
this.$refs.editForm.validate(valid => {
if (!valid) return;
// 编辑
const newItem = {
id: this.editForm.id,
classId:this.editForm.classId,
name:this.editForm.name,
stuClassNo:this.editForm.stuClassNo,
idCard:this.editForm.idCard,
account:this.editForm.account,
stuId:this.editForm.stuId,
status:this.editForm.status
}
const url = APIConfig.BschClassSystem.studentsave;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("编辑保存成功!");
this.editModalVisible = false
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
})
},
// 批量删除
batchDelete() {
if (!this.selectedRows.length) {
return this.$Message.warning('请先勾选学生')
}
this.$Modal.confirm({
title: '提示',
content: '确定删除选中学生?删除不可恢复',
onOk: () => {
let idlist=this.selectedRows.map(a=>a.id);
const url = APIConfig.BschClassSystem.studentdelete+idlist.join(',');
this.$api.post(url,{}, resp => {
if (resp.code == 0) {
this.$Message.success("删除成功!");
this.selectedRows = []
this.loadData()
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
})
},
// 学生导入
openImportModal() { this.importModalVisible = true },
//导入成功返回
importFile(response, file, fileList){
const _this=this;
console.log(response);
console.log(file);
_this.spinShow=false;
_this.UpParmas.fileName=file.name;
_this.UpParmas.listData=response;
if(response.State)
{
_this.$Message.info('导入成功!');
_this.loadData()
}else{
_this.$Message.info(response.Message);
}
},
//附件上传之前的方法
handleUpload:function(file){
const _this=this;
_this.spinShow=true;
},
MoBanExceldaochu() {
var _this = this;
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
var url=APIConfig.BschClassSystem.studentdownloadTemplate+'?schId='+schId;
window.location.href=url;
}else{
this.$Message.info('获取不了学校!');
}
},
//附件上传模态框关闭事件
UpfileModalcloseclick:function(){
this.importModalVisible=false;
this.UpParmas={
fileName:'',
listData:{},
datapre:{},
}
},
//按照学生学号导入
openStuIdImportModal() { this.stuIdImportModalVisible = true },
//导入成功返回
importFil2(response, file, fileList){
const _this=this;
console.log(response);
console.log(file);
_this.spinShow=false;
_this.UpParmas2.fileName=file.name;
_this.UpParmas2.listData=response;
if(response.State)
{
_this.$Message.info('导入成功!');
_this.loadData()
}else{
_this.$Message.info(response.Message);
}
},
//附件上传之前的方法
handleUpload2:function(file){
const _this=this;
_this.spinShow=true;
},
MoBanExceldaochu2() {
var _this = this;
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
var url=APIConfig.BschClassSystem.studentClassNoTemplate+'?schId='+schId;
window.location.href=url;
}else{
this.$Message.info('获取不了学校!');
}
},
//附件上传模态框关闭事件
UpfileModalcloseclick2:function(){
this.stuIdImportModalVisible=false;
this.UpParmas2={
fileName:'',
listData:{},
datapre:{},
}
},
//学生转出
batchTransferStudent(){
if (!this.selectedRows.length) return this.$Message.warning('请勾选学生');
this.transferStudentModal = true
},
//转出事件
confirmTransferStudent() {
const idlist = this.selectedRows.map(i => i.id);
const url = APIConfig.BschClassSystem.studenttransferOut+idlist.join(',');
this.$api.post(url,{}, resp => {
if (resp.code == 0) {
this.$Message.success("转出操作完成!");
this.selectedRows = [];
this.transferStudentModal = false;
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
},
// 学生调班
openMoveClassModal() {
if (!this.selectedRows.length) return this.$Message.warning('请勾选学生');
this.filterschclassselectlist=[...this.schclassselectlist].filter(item => item.id != this.classId);
this.moveClassModalVisible = true;
this.currentSelectClass = this.selectedRows[0].className;
this.targetClass = '';
},
submitMoveClass() {
if (!this.targetClass) return this.$Message.warning('请选择目标班级');
const newItem = {
transferClassId: this.targetClass,
stuIds:this.selectedRows.map(a=>a.id)
}
const url = APIConfig.BschClassSystem.studenttransferClass;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("调班成功!");
this.moveClassModalVisible = false
this.selectedRows = []
this.loadData()
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
},
// 学生异动
openChangeStatusModal() {
if (!this.selectedRows.length) return this.$Message.warning('请勾选学生');
this.changeStatusModalVisible = true;
this.currentStuName = this.selectedRows[0].name;
this.getstudentabnormal();
this.selectedStatus = 1;
},
submitChangeStatus() {
const newItem = {
abnormalStatus: this.selectedStatus,
stuIds:this.selectedRows.map(a=>a.id)
}
const url = APIConfig.BschClassSystem.studentsetAbnormalStatus;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success('异动状态修改完成');
this.changeStatusModalVisible = false;
this.selectedRows = [];
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
}
}
</script>
<style scoped>
.student-manage-wrap {
width: 100%;
height: 100%;
display: flex;
gap: 16px;
box-sizing: border-box;
padding: 12px;
}
/* 左侧树固定高度 */
.left-tree-box {
width: 260px;
border: 1px solid #e6e6e6;
overflow-y: auto;
flex-shrink: 0;
}
/* 右侧整体弹性布局 */
.right-main-box {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
/* 顶部栏 */
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
margin-bottom: 10px;
flex-shrink: 0;
}
.btn-left-group {
display: flex;
align-items: center;
}
/* 表格自适应滚动容器 */
.table-scroll-wrap {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
height: 100%;
}
/* 底部栏:分页固定底部 */
.bottom-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
flex-shrink: 0;
}
.page-right {
display: flex;
align-items: center;
}
/* 调班、异动弹窗按钮排版 */
.class-btn-wrap, .status-btn-wrap {
display: flex;
flex-wrap: wrap;
}
</style>
<style>
.ivu-alert-desc{
font-size: 12px;
}
</style>
+39
View File
@@ -0,0 +1,39 @@
<template>
<div style="height:100%;">
<Christianity v-if="type==1"></Christianity>
<HigherEducation v-if="type==2"></HigherEducation>
</div>
</template>
<script>
import Christianity from "./studentYDManage/Christianity";
import HigherEducation from "./studentYDManage/HigherEducation";
export default {
name: 'studentYDManage',
components: { Christianity,HigherEducation },
data() {
return {
type:0,
}
},
created(){
this.getType();
},
methods: {
//获取学制
getType:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getType, item, resp => {
if(resp.code==0)
{
_this.type = resp.data;
}else{
_this.type=1;
}
});
},
},
}
</script>
+348
View File
@@ -0,0 +1,348 @@
<template>
<div class="student-manage-wrap">
<!-- 左侧树形区域 固定高度滚动 -->
<div class="left-tree-box">
<my-tree :treeData="treeData" :selectId="selectnodeid" @on-tree-click="treeLink"></my-tree>
</div>
<!-- 右侧主体区域 -->
<div class="right-main-box">
<!-- 顶部按钮+搜索栏 -->
<div class="top-bar">
<div class="btn-left-group"></div>
<div class="search-wrap" style="width: 100%;text-align: right;">
<Input
v-model="searchKey"
placeholder="请输入学生姓名"
search
style="width:300px"
@on-search="handleSearch"
/>
</div>
</div>
<!-- 表格容器:自适应剩余高度滚动 -->
<div class="table-scroll-wrap">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
:height="useTableHeight()-12"
@on-selection-change="selectionChange"
></Table>
</div>
<!-- 左下角批量删除 + 底部分页固定 -->
<div class="bottom-bar">
<Button type="default" @click="selectedStatus=1;submitChangeStatus()">还原</Button>
<div class="page-right">
<span style="margin-right:12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40,50]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
</div>
<!-- 弹窗6:学生异动 -->
<Modal v-model="changeStatusModalVisible" title="学生异动" width="520">
<div style="padding:10px 0;">
<div style="margin-bottom:16px;display:flex;align-items:center;">
<span style="width:90px;">当前学生</span>
<Input v-model="currentStuName" disabled style="flex:1;"></Input>
</div>
<div>
<span style="width:90px;display:inline-block;margin-bottom:10px;">异动情况</span>
<div class="status-btn-wrap">
<Button
v-for="item in studentabnormallist"
:key="item.id"
:type="selectedStatus===item.id?'primary':'default'"
style="margin:6px"
@click="selectedStatus=item.id"
size="small"
>{{ item.name }}</Button>
</div>
</div>
</div>
<div slot="footer" align="right">
<Button @click="changeStatusModalVisible=false">取消</Button>
<Button type="primary" @click="submitChangeStatus">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'StudentManage',
components: { myTree },
data() {
return {
// 左侧树形数据
treeData: [],
selectnodeid:'',
schsystemId:'',
gradeId:'',
classId:'',
// 搜索
searchKey: '',
// 分页
pageNum: 1,
pageSize: 40,
total: 0,
selectedRows: [],
// 表格列配置
tableColumns: [],
tableData: [],
// 弹窗开关
changeStatusModalVisible: false,
// 异动弹窗
currentStuName: '',
selectedStatus: null,
studentabnormallist: [],
}
},
mixins: [self_viewport],
created() {
this.getClassTree();
this.initTableColumn()
},
methods: {
//获取学生状态列表
getstudentabnormal:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getstudentabnormal, item, resp => {
if(resp.code==0)
{
this.studentabnormallist=resp.data;
}
});
},
//获取班级树
getClassTree:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getClassTree, item, resp => {
if(resp.code==0)
{
this.treeData=resp.data;
if(resp.data.children.length>0){
// 起始节点为 data
const pathArr = _this.getFirstPath(resp.data);
_this.schsystemId=pathArr[pathArr.length-1].schsystemId;
_this.gradeId=pathArr[pathArr.length-1].gradeId+'';
_this.classId=pathArr[pathArr.length-1].classId+'';
_this.selectnodeid=pathArr[pathArr.length-1].id;
_this.loadData();
}
}
});
},
//获取最后一级的map
getFirstPath(rootNode) {
const path = [];
let cur = rootNode;
while (cur) {
// 存入当前层级关键信息
path.push({
id: cur.id,
name: cur.name,
type: cur.type,
schsystemId:cur.schsystemId,
gradeId:cur.gradeId,
classId:cur.classId,
});
// 有子节点就取第一个,没有就终止
if (cur.children && cur.children.length > 0) {
cur = cur.children[0];
} else {
cur = null;
}
}
return path;
},
// 初始化表格列 配置式columns
initTableColumn() {
this.tableColumns = [
{type: 'selection',width: 55,align: 'center'},
{ title: '年级', key: 'gradeName', align: 'center' },
{ title: '班级', key: 'className', align: 'center' },
{ title: '姓名', key: 'name', align: 'center', width: 140 },
{ title: '异动详情', key: 'abnormalStatusName', align: 'center' },
{ title: '异动日期', key: 'className', align: 'center' },
{ title: '身份证', key: 'idCard', align: 'center', width: 90 },
{
title: '操作',
align: 'center',
width: 80,
render: (h, params) => {
return h('Button', {
props: { type: 'text', icon: 'md-create',type:'large' },
on: { click: () => this.openEditModal(params.row) }
})
}
}
]
},
// 树形选中切换
treeLink(treeNode) {
this.schsystemId=treeNode.schsystemId;
this.gradeId=treeNode.gradeId?treeNode.gradeId+'':'';
this.classId=treeNode.classId?treeNode.classId+'':'';
this.selectnodeid=treeNode.id;
this.pageNum = 1
this.loadData()
},
// 多选行回调
selectionChange(rows) {
this.selectedRows = rows
},
// 加载列表数据
loadData() {
const _this = this;
var item = {
schsystemId:_this.schsystemId,
gradeId:_this.gradeId,
classId:_this.classId,
keyword:_this.searchKey,
page:_this.pageNum-1,
size:_this.pageSize
};
_this.$api.get(APIConfig.BschClassSystem.getbaseSchAbnormalSearch, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
// 搜索
handleSearch() {
this.pageNum = 1;
this.loadData();
},
// 分页切换
pageChange(p) {
this.pageNum = p
this.loadData()
},
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadData()
},
//学生编辑状态
openEditModal(row) {
this.changeStatusModalVisible=true;
this.currentStuName=row.name;
this.getstudentabnormal();
this.selectedStatus=row.abnormalStatus;
this.selectedRows=[row];
},
//还原事件
submitChangeStatus() {
if (!this.selectedRows.length) return this.$Message.warning('请勾选学生');
const newItem = {
abnormalStatus: 1,
stuIds:this.selectedRows.map(a=>a.id)
}
const url = APIConfig.BschClassSystem.studentsetAbnormalStatus;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success('异动状态修改完成');
this.changeStatusModalVisible = false;
this.selectedRows = [];
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
}
}
</script>
<style scoped>
.student-manage-wrap {
width: 100%;
height: 100%;
display: flex;
gap: 16px;
box-sizing: border-box;
padding: 12px;
}
/* 左侧树固定高度 */
.left-tree-box {
width: 260px;
border: 1px solid #e6e6e6;
overflow-y: auto;
flex-shrink: 0;
}
/* 右侧整体弹性布局 */
.right-main-box {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
/* 顶部栏 */
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
margin-bottom: 10px;
flex-shrink: 0;
}
.btn-left-group {
display: flex;
align-items: center;
}
/* 表格自适应滚动容器 */
.table-scroll-wrap {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
height: 100%;
}
/* 底部栏:分页固定底部 */
.bottom-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
flex-shrink: 0;
}
.page-right {
display: flex;
align-items: center;
}
/* 调班、异动弹窗按钮排版 */
.class-btn-wrap, .status-btn-wrap {
display: flex;
flex-wrap: wrap;
}
</style>
@@ -0,0 +1,341 @@
<template>
<div class="student-manage-wrap">
<!-- 左侧树形区域 固定高度滚动 -->
<div class="left-tree-box">
<my-tree :treeData="treeData" :selectId="selectnodeid" @on-tree-click="treeLink"></my-tree>
</div>
<!-- 右侧主体区域 -->
<div class="right-main-box">
<!-- 顶部按钮+搜索栏 -->
<div class="top-bar">
<div class="btn-left-group"></div>
<div class="search-wrap" style="width: 100%;text-align: right;">
<Input
v-model="searchKey"
placeholder="请输入学生姓名"
search
style="width:300px"
@on-search="handleSearch"
/>
</div>
</div>
<!-- 表格容器:自适应剩余高度滚动 -->
<div class="table-scroll-wrap">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
:height="useTableHeight()-12"
@on-selection-change="selectionChange"
></Table>
</div>
<!-- 左下角批量删除 + 底部分页固定 -->
<div class="bottom-bar">
<Button type="default" @click="selectedStatus=1;submitChangeStatus()">还原</Button>
<div class="page-right">
<span style="margin-right:12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40,50]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
</div>
<!-- 弹窗6:学生异动 -->
<Modal v-model="changeStatusModalVisible" title="学生异动" width="520">
<div style="padding:10px 0;">
<div style="margin-bottom:16px;display:flex;align-items:center;">
<span style="width:90px;">当前学生</span>
<Input v-model="currentStuName" disabled style="flex:1;"></Input>
</div>
<div>
<span style="width:90px;display:inline-block;margin-bottom:10px;">异动情况</span>
<div class="status-btn-wrap">
<Button
v-for="item in studentabnormallist"
:key="item.id"
:type="selectedStatus===item.id?'primary':'default'"
style="margin:6px"
@click="selectedStatus=item.id"
size="small"
>{{ item.name }}</Button>
</div>
</div>
</div>
<div slot="footer" align="right">
<Button @click="changeStatusModalVisible=false">取消</Button>
<Button type="primary" @click="submitChangeStatus()">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'StudentManage',
components: { myTree },
data() {
return {
// 左侧树形数据
treeData: [],
selectnodeid:'',
enrollYear:'',
classId:'',
// 搜索
searchKey: '',
// 分页
pageNum: 1,
pageSize: 40,
total: 0,
selectedRows: [],
// 表格列配置
tableColumns: [],
tableData: [],
// 弹窗开关
changeStatusModalVisible: false,
// 异动弹窗
currentStuName: '',
selectedStatus: null,
studentabnormallist: [],
}
},
mixins: [self_viewport],
created() {
this.getClassTree();
this.initTableColumn()
},
methods: {
//获取学生状态列表
getstudentabnormal:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getstudentabnormal, item, resp => {
if(resp.code==0)
{
this.studentabnormallist=resp.data;
}
});
},
//获取班级树
getClassTree:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getHigherSchClassTree, item, resp => {
if(resp.code==0)
{
this.treeData=resp.data;
if(resp.data.children.length>0){
// 起始节点为 data
const pathArr = _this.getFirstPath(resp.data);
_this.enrollYear=pathArr[pathArr.length-1].enrollYear+'',
_this.classId=pathArr[pathArr.length-1].classId;
_this.selectnodeid=pathArr[pathArr.length-1].id;
_this.loadData();
}
}
});
},
//获取最后一级的map
getFirstPath(rootNode) {
const path = [];
let cur = rootNode;
while (cur) {
// 存入当前层级关键信息
path.push({
id: cur.id,
name: cur.name,
type: cur.type,
enrollYear:cur.enrollYear,
classId:cur.classId
});
// 有子节点就取第一个,没有就终止
if (cur.children && cur.children.length > 0) {
cur = cur.children[0];
} else {
cur = null;
}
}
return path;
},
// 初始化表格列 配置式columns
initTableColumn() {
this.tableColumns = [
{type: 'selection',width: 55,align: 'center'},
{ title: '年级', key: 'gradeName', align: 'center' },
{ title: '班级', key: 'className', align: 'center' },
{ title: '姓名', key: 'name', align: 'center', width: 140 },
{ title: '异动详情', key: 'abnormalStatusName', align: 'center' },
{ title: '异动日期', key: 'className', align: 'center' },
{ title: '身份证', key: 'idCard', align: 'center', width: 90 },
{
title: '操作',
align: 'center',
width: 80,
render: (h, params) => {
return h('Button', {
props: { type: 'text', icon: 'md-create',type:'large' },
on: { click: () => this.openEditModal(params.row) }
})
}
}
]
},
// 树形选中切换
treeLink(treeNode) {
this.enrollYear=treeNode.enrollYear?treeNode.enrollYear+'':'';
this.classId=treeNode.classId?treeNode.classId+'':'';
this.selectnodeid=treeNode.id;
this.pageNum = 1
this.loadData()
},
// 多选行回调
selectionChange(rows) {
this.selectedRows = rows
},
// 加载列表数据
loadData() {
const _this = this;
var item = {
year:_this.enrollYear,
classId:_this.classId,
keyword:_this.keyword,
page:_this.pageNum-1,
size:_this.pageSize
};
_this.$api.get(APIConfig.BschClassSystem.gethigherSchAbnormalSearch, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
// 搜索
handleSearch() {
this.pageNum = 1
this.loadData()
},
// 分页切换
pageChange(p) {
this.pageNum = p
this.loadData()
},
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadData()
},
//学生编辑状态
openEditModal(row) {
this.changeStatusModalVisible=true;
this.currentStuName=row.name;
this.getstudentabnormal();
this.selectedStatus=row.abnormalStatus;
this.selectedRows=[row];
},
//还原事件
submitChangeStatus() {
const newItem = {
abnormalStatus: this.selectedStatus,
stuIds:this.selectedRows.map(a=>a.id)
}
const url = APIConfig.BschClassSystem.studentsetAbnormalStatus;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success('异动状态修改完成');
this.changeStatusModalVisible = false;
this.selectedRows = [];
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
}
}
</script>
<style scoped>
.student-manage-wrap {
width: 100%;
height: 100%;
display: flex;
gap: 16px;
box-sizing: border-box;
padding: 12px;
}
/* 左侧树固定高度 */
.left-tree-box {
width: 260px;
border: 1px solid #e6e6e6;
overflow-y: auto;
flex-shrink: 0;
}
/* 右侧整体弹性布局 */
.right-main-box {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
/* 顶部栏 */
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
margin-bottom: 10px;
flex-shrink: 0;
}
.btn-left-group {
display: flex;
align-items: center;
}
/* 表格自适应滚动容器 */
.table-scroll-wrap {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
height: 100%;
}
/* 底部栏:分页固定底部 */
.bottom-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
flex-shrink: 0;
}
.page-right {
display: flex;
align-items: center;
}
/* 调班、异动弹窗按钮排版 */
.class-btn-wrap, .status-btn-wrap {
display: flex;
flex-wrap: wrap;
}
</style>
+217
View File
@@ -0,0 +1,217 @@
<template>
<div class="edu-layout-wrap">
<Row gutter="10" class="layout-row">
<!-- 左侧:学校树形 -->
<Col span="4" class="left-tree-wrap" style="overflow: auto;" :style="'height:'+(useTableHeight({ isSearch: true, isOperat: true })+160)+'px'">
<div class="tree-title">学校</div>
<my-tree :treeData="treeData" @on-tree-click="treeLink"></my-tree>
</Col>
<!-- 右侧:学制选择 -->
<Col span="20" class="right-select-wrap" :style="'height:'+(useTableHeight({ isSearch: true, isOperat: true })+160)+'px'">
<div class="select-title">学制选择</div>
<!-- 幼儿园分组 -->
<div class="group-wrap" v-for="(itt,index) in schsystemlist" :key="index">
<div class="group-label">{{ itt.academicStageName }}</div>
<div class="btn-group-row">
<Button
v-for="(item,index2) in itt.academicStageList"
:key="item.id"
:type="item.checked ? 'primary' : 'default'"
shape="circle"
@click="selectEduType(item.id,index,index2)"
class="edu-btn"
>
{{ item.name }}
</Button>
</div>
</div>
<div style="margin-bottom:20px;"></div>
<!-- 底部确定按钮 -->
<div class="confirm-btn-wrap" style="position: fixed;bottom: 0;text-align: center;left: 0;right: 0;background: #fff;padding: 10px;border-top: 1px solid #eee;">
<Button type="primary" style="padding:0px 40px;" @click="handleConfirm">确定</Button>
</div>
</Col>
</Row>
</div>
</template>
<script>
import myTree from "../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: "EduYearSelect",
components: { myTree},
data() {
return {
selectedEdu: "",
selectedSchool: null,
treeData: [],
schsystemlist:[],
selectnodeid:0
};
},
mixins: [self_viewport],
created(){
let _this=this;
_this.getGroupTree();
_this.getschsystemlist();
},
mounted() {
},
methods: {
//获取全区树
getGroupTree:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getGroupTree, item, resp => {
if(resp.code==0)
{
_this.treeData = resp.data;
}else{
_this.treeData=[];
}
});
},
//节点点击事件
treeLink(treeNode){
if(treeNode.type==2){
this.selectnodeid=treeNode.id;
this.getschsystemlist();
}
},
//获取学校绑定学制列表
getschsystemlist:function(){
const _this = this;
var item={
schId:this.selectnodeid
};
_this.$api.get(APIConfig.BschClassSystem.getschsystemlist, item, resp => {
if(resp.code==0)
{
_this.schsystemlist = resp.data;
}else{
_this.schsystemlist=[];
}
});
},
selectEduType(val,index,index2) {
const list = this.schsystemlist[index].academicStageList;
// 先记录当前点击项原本的勾选状态
const originChecked = list[index2].checked;
// 本组全部取消选中
list.forEach(item => {
item.checked = false;
});
// 如果原来没选中 → 选中;原来已选中 → 保持取消
if (!originChecked) {
list[index2].checked = true;
}
},
handleConfirm() {
if (!this.selectnodeid) {
this.$Message.warning("请先选择左侧学校");
return;
}
const checkedIdArr = this.schsystemlist
.flatMap(item => item.academicStageList)
.filter(item => item.checked)
.map(item => item.id);
if (checkedIdArr.length==0) {
this.$Message.warning("请选择对应学制");
return;
}
const submitData = {
schId: this.selectnodeid,
schsystemIds: checkedIdArr.join(',')
};
var url = APIConfig.BschClassSystem.schsystemsave;
this.$api.post(url, submitData, resp => {
if (resp.code == 0) {
this.$Message.success("已设置完成!");
this.getschsystemlist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
}
};
</script>
<style scoped>
.edu-layout-wrap {
width: 100%;
height: 100%;
padding: 10px;
background: #fff;
}
.layout-row {
height: 100%;
}
.left-tree-wrap {
border: 1px solid #e5e7eb;
border-radius: 4px;
border-top-right-radius: 0;
/* height: 650px; */
display: flex;
flex-direction: column;
}
.tree-title {
font-size: 22px;
font-weight: 500;
padding: 10px 10px 10px;
border-bottom: 1px solid #eee;
}
.edu-tree {
flex: 1;
overflow-y: auto;
padding: 10px;
}
.right-select-wrap {
border: 1px solid #e5e7eb;
border-left: 0px;
border-radius: 4px;
border-top-left-radius: 0;
/* height: 650px; */
padding: 10px;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: auto;
}
.select-title {
font-size: 22px;
font-weight: 500;
margin-bottom: 10px;
}
.group-wrap {
margin-bottom: 35px;
}
.group-label {
font-size: 18px;
padding: 12px 10px;
background-color: #f5f5f5;
margin-bottom: 15px;
border-radius: 4px;
}
.btn-group-row {
display: flex;
gap: 20px;
padding-left: 10px;
}
.edu-btn {
padding: 6px 22px;
font-size: 15px;
}
.confirm-btn-wrap {
margin-top: auto;
text-align: center;
padding-top: 20px;
}
</style>
+443
View File
@@ -0,0 +1,443 @@
<template>
<!-- 最外层整体边框容器(和校区/系部/专业页面统一外壳样式) -->
<div class="outer-wrap">
<!-- 顶部筛选+操作栏 -->
<div class="top-bar">
<Button type="primary" icon="ios-add" @click="openAddModal">添加课程</Button>
<div class="filter-group">
<Select v-model="filterDept" placeholder="选择所属系部" style="width:160px;margin-right:10px" @on-change="handleSearch" clearable>
<Option v-for="item in divisionselectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<Select v-model="filterMajor" placeholder="选择所属专业" style="width:180px;margin-right:10px" @on-change="handleSearch" clearable filterable>
<Option v-for="item in majorsselectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<Select v-model="filterType" placeholder="选择类型" style="width:140px;margin-right:10px" @on-change="handleSearch" clearable>
<Option v-for="item in coursetypeselectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<Input
v-model="keyword"
placeholder="输入代码或名称搜索"
style="width:240px"
@keyup.enter="handleSearch"
>
<Icon slot="suffix" type="ios-search" @click="handleSearch" style="cursor:pointer;"></Icon>
</Input>
</div>
</div>
<!-- 自适应表格区域:内容滚动、分页固定底部 -->
<div class="adapt-container">
<!-- 表格滚动容器 -->
<div class="table-scroll-box">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
class="course-table"
></Table>
</div>
<!-- 底部分页固定 -->
<div class="pagination-bottom">
<span class="total-text">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10,20,50]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
<!-- 新增/编辑课程弹窗(匹配编辑课程信息截图) -->
<Modal v-model="modalVisible" :title="modalTitle" width="580">
<Form ref="form" :model="formData" :rules="formRules" label-width="110">
<FormItem label="课程代码" prop="courseCode">
<Input v-model="formData.courseCode" placeholder="请输入课程代码"></Input>
</FormItem>
<FormItem label="课程名称" prop="courseName">
<Input v-model="formData.courseName" placeholder="请输入课程名称"></Input>
</FormItem>
<FormItem label="课程类别" prop="courseType">
<Select v-model="formData.courseType" placeholder="请选择类别" style="width:100%" transfer>
<Option v-for="item in coursetypeselectlist" :key="item.id" :value="item.id+''">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="所属专业" prop="bindMajor">
<Select
v-model="formData.bindMajor"
multiple
placeholder="可多选所属专业"
style="width:100%" transfer
>
<Option v-for="(item,index) in majorsselectlist" :key="index" :value="item.id">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="课程系部" prop="bindDept">
<Select
v-model="formData.bindDept"
multiple
placeholder="可多选所属系部"
style="width:100%" transfer
>
<Option v-for="(item,index) in divisionselectlist" :key="index" :value="item.id">{{ item.name }}</Option>
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="modalVisible = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitForm">提交</Button>
</div>
</Modal>
</div>
</template>
<script>
export default {
name: 'CourseManage',
data() {
return {
// 顶部筛选条件
keyword: '',
filterDept: '',
filterMajor: '',
filterType: '',
// 分页参数 iView2 Page 配置
pageNum: 1,
pageSize: 10,
total: 0,
// 下拉选项数据源
divisionselectlist: [],
majorsselectlist: [],
coursetypeselectlist:[],
// iView2 表格列配置(摒弃TableColumn,columns标准写法)
tableColumns: [
{ title: '序号', type: 'index', align: 'center', width: 70 },
{ title: '课程代码', key: 'code', align: 'center' },
{ title: '课程名称', key: 'name', align: 'center' },
{ title: '类型', key: 'typeName', align: 'center' },
{
title: '所属专业',
key: 'bindMajorStr',
align: 'center',
render: (h, params) => {
var ZGZList = params.row.majorsList;//数据数组
var newArr = [];
ZGZList.forEach((a,index)=>{
newArr.push(h('div',{class:'spcontent'},[
h('span', a.name)
]))
})
return h('div',newArr);
}
},
{
title: '所属系部',
key: 'bindDeptStr',
align: 'center',
render: (h, params) => {
var ZGZList = params.row.divisionList;//数据数组
var newArr = [];
ZGZList.forEach((a,index)=>{
newArr.push(h('div',{class:'spcontent'},[
h('span', a.name)
]))
})
return h('div',newArr);
}
},
{
title: '操作',
key: 'action',
align: 'center',
width: 120,
render: (h, params) => {
return h('div', { style: 'display:flex;gap:12px;justify-content:center' }, [
h('Button', {
props: { type: 'text', icon: 'ios-create', size: 'default' },
on: { click: () => this.editRow(params.row, params.index) }
}),
h('Button', {
props: { type: 'text', icon: 'ios-trash', size: 'default' },
on: { click: () => this.delRow(params.index) }
})
])
}
}
],
// 表格模拟数据(和截图内容一一对应)
tableData: [],
// 弹窗控制
modalVisible: false,
modalTitle: '',
editIndex: null,
// 表单数据(匹配编辑弹窗5个字段)
formData: {
courseCode: '',
courseName: '',
courseType: '',
bindMajor: [],
bindDept: []
},
// 表单校验规则
formRules: {
courseCode: [{ required: true, message: '请输入课程代码', trigger: 'blur' }],
courseName: [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
courseType: [{ required: true, message: '请选择课程类别', trigger: 'change' }],
bindMajor: [{ required: true, type: 'array', min: 1, message: '请选择所属专业', trigger: 'change' }],
bindDept:[{ required: true, type: 'array', min: 1, message: '请选择课程系部', trigger: 'change' }],
}
}
},
created(){
this.getdivisionselect();
this.getmajorsselect();
this.getcoursetypeselect();
this.getcourselist();
},
methods: {
//系部下拉
getdivisionselect() {
const _this = this;
var item = {};
_this.$api.get(APIConfig.BschClassSystem.getdivisionselect, item, resp => {
if (resp.code == 0) {
_this.divisionselectlist = resp.data;
} else {
_this.divisionselectlist = [];
}
});
},
//专业下拉
getmajorsselect() {
const _this = this;
var item = {};
_this.$api.get(APIConfig.BschClassSystem.getmajorsselect, item, resp => {
if (resp.code == 0) {
_this.majorsselectlist = resp.data;
} else {
_this.majorsselectlist = [];
}
});
},
//课程类别下拉
getcoursetypeselect() {
const _this = this;
var item = {};
_this.$api.get(APIConfig.BschClassSystem.getcoursetypeselect, item, resp => {
if (resp.code == 0) {
_this.coursetypeselectlist = resp.data;
} else {
_this.coursetypeselectlist = [];
}
});
},
//获取课程列表
getcourselist() {
const _this = this;
var item = {
keyword: this.keyword,
divisionId: this.filterDept,
majorsId: this.filterMajor,
type: this.filterType,
page:this.pageNum-1,
size:this.pageSize,
sort:'id'
};
_this.$api.get(APIConfig.BschClassSystem.getcourselist, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
// 搜索重置页码
handleSearch() {
this.pageNum = 1
this.getcourselist();
},
// 页码切换
pageChange(page) {
this.pageNum = page;
this.getcourselist();
},
// 每页条数修改
sizeChange(size) {
this.pageSize = size;
this.pageNum = 1;
this.getcourselist();
},
// 打开新增弹窗
openAddModal() {
this.editIndex = null
this.modalTitle = '新增课程信息'
this.formData = {
courseCode: '',
courseName: '',
courseType: '',
bindMajor: [],
bindDept: []
}
this.modalVisible = true
},
// 编辑行回填弹窗
editRow(row, idx) {
this.editIndex = idx
this.modalTitle = '编辑课程信息'
this.formData = {
id:row.id,
courseCode: row.code,
courseName: row.name,
courseType: row.type+'',
bindMajor: row.majorsList.map(a=>a.id),
bindDept: row.divisionList.map(a=>a.id)
}
this.modalVisible = true
},
// 删除弹窗确认
delRow(idx) {
this.$Modal.confirm({
title: '提示',
content: '确定删除当前课程?',
onOk: () => {
var url=APIConfig.BschClassSystem.coursedelete+this.tableData[idx].id;
this.$api.post(url,{},(resp)=>{
this.$Message.success('删除成功!');
this.getcourselist();
});
}
})
},
// 弹窗提交
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) return
const form = this.formData
if (this.editIndex === null) {
// 新增
const newItem = {
code: form.courseCode,
name: form.courseName,
type: form.courseType,
majorsIds: form.bindMajor,
divisionIds:form.bindDept
}
const url = APIConfig.BschClassSystem.coursesave;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("新增课程成功!");
this.getcourselist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
} else {
// 编辑修改
const newItem = {
id:form.id,
code: form.courseCode,
name: form.courseName,
type: form.courseType,
majorsIds: form.bindMajor,
divisionIds:form.bindDept
}
const url = APIConfig.BschClassSystem.coursesave;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("编辑课程成功!");
this.getcourselist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
this.modalVisible = false
})
}
}
}
</script>
<style scoped>
/* 全局外边框,和之前校区/系部/专业页面样式统一 */
.outer-wrap {
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 16px;
width: 100%;
height: 100%;
box-sizing: border-box;
background: #fff;
display: flex;
/* margin:0 10px; */
flex-direction: column;
}
/* 顶部操作筛选栏 */
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: #f7f7f7;
margin-bottom: 16px;
flex-shrink: 0;
}
.filter-group {
display: flex;
align-items: center;
}
/* 自适应主体容器 */
.adapt-container {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
/* 表格区域自动填充剩余高度,内部滚动 */
.table-scroll-box {
flex: 1;
overflow-y: auto;
}
.course-table {
width: 100%;
}
/* 分页固定底部,不跟随表格滚动 */
.pagination-bottom {
margin-top: 16px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.total-text {
font-size: 14px;
color: #666;
}
</style>
<style>
.spcontent{
border: 1px solid #dcdee2;
border-radius: 5px;
padding: 2px 5px;
background: #f8f8f9;
margin: 5px 0;
}
</style>
+372
View File
@@ -0,0 +1,372 @@
<template>
<div class="outer-wrap">
<!-- 顶部年级下拉 -->
<div class="top-select-wrap">
<Select v-model="gradeVal" style="width:140px" @on-change="getsubjectlist">
<Option v-for="(item,index) in schsystemselectlist" :key="index" :value="item.id">{{ item.name }}</Option>
</Select>
</div>
<!-- 所有学科拖拽容器(两类混排) -->
<div class="subject-box">
<div class="btn-container">
<draggable
v-model="subjectlist"
class="drag-wrap"
animation="200"
:options="{ group: 'people' }"
@start="drag = true"
@end="drag = false"
:move="getdata1"
@update="datadragEnd1"
>
<div
v-for="(item, idx) in subjectlist"
:key="idx"
class="subject-btn"
:class="[item.source === 0 ? 'sys-btn' : 'cus-btn', { active: item.enable }]"
@click="toggleSelect(idx)"
@mouseenter="item.source === 1 && (hoverIndex = idx)"
@mouseleave="hoverIndex = null"
>
<span class="text">{{ item.name }}</span>
<!-- 仅自建学科悬浮显示编辑删除 -->
<div v-if="item.source === 1 && hoverIndex === idx" class="operate-group" @click.stop>
<span class="icon-edit" @click="editItem(idx)">✏️</span>
<span class="icon-del" @click="delItem(idx)">🗑️</span>
</div>
</div>
</draggable>
<!-- 添加自建学科按钮 -->
<Button icon="ios-add-outline" class="add-subject-btn" size="large" @click="openAddModal">
添加学科
</Button>
</div>
</div>
<!-- 底部图例说明 -->
<div class="legend-bottom">
<span class="legend-item">
<i class="legend-box system-box"></i>
系统学科(蓝色,可多选、拖拽排序,不可删改)
</span>
<span class="legend-item">
<i class="legend-box custom-box"></i>
学校自建学科(绿色,可多选、拖拽、hover编辑删除)
</span>
</div>
<!-- 新增/编辑弹窗 学科信息 -->
<Modal v-model="modalVisible" title="学科信息" width="420">
<Form ref="form" :model="formData" :rules="formRules" label-width="90">
<FormItem label="学科名称" prop="subjectName">
<Input v-model="formData.subjectName" placeholder="请输入学科名称"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="modalVisible = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitForm">提交</Button>
</div>
</Modal>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'SubjectManage',
components: { draggable },
data() {
return {
schsystemselectlist:[],
gradeVal: '',
hoverIndex: null,
// 选中下标数组,支持多选
selectedIndexArr: [],
// 合并数组:type区分 system系统 / custom自建
subjectlist: [],
modalVisible: false,
editIndex: null,
formData: {id:0,subjectName: '',enable:true,source:1,sort:null },
formRules: {
subjectName: [{ required: true, message: '请输入学科名称', trigger: 'blur' }]
}
}
},
created(){
this.getschsystemselect();
},
methods: {
//学制下拉
getschsystemselect() {
const _this = this;
var item = {};
_this.$api.get(APIConfig.BschClassSystem.getschsystemselect, item, resp => {
if (resp.code == 0) {
_this.schsystemselectlist = resp.data;
if(_this.schsystemselectlist.length>0){
_this.gradeVal=_this.schsystemselectlist[0].id;
_this.getsubjectlist();
}
} else {
_this.schsystemselectlist = [];
}
});
},
//获取学科列表
getsubjectlist() {
const _this = this;
var item = {
schSystemId:_this.gradeVal
};
_this.$api.get(APIConfig.BschClassSystem.getsubjectlist, item, resp => {
if (resp.code == 0) {
_this.subjectlist = resp.data;
} else {
_this.subjectlist = [];
}
});
},
// 多选切换选中/取消
toggleSelect(index) {
let submap=this.subjectlist[index];
this.$set(this.subjectlist[index], 'enable', !submap.enable);
const submitData ={
id:submap.id,
name:submap.name,
schSystemId:this.gradeVal,
enable:submap.enable,
source:submap.source,
sort:submap.sort
};
const url = APIConfig.BschClassSystem.subjectsave;
this.$api.post(url, JSON.stringify(submitData), resp => {
if (resp.code == 0) {
this.$Message.success("启用成功!");
this.getsubjectlist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
},
//学科拖动前
getdata1 (evt) {
//console.log(evt.draggedContext.element)
},
//学科拖动后
datadragEnd1 (evt) {
var _this=this;
//console.log('拖动后的索引 :' + evt.newIndex);
//console.log('拖动前的索引 :' + evt.oldIndex);
var ids=[],orders=[];
_this.subjectlist.forEach(element => {
ids.push(element.code);
});
var url=APIConfig.BschClassSystem.subjectsort+'?schSystemId='+_this.gradeVal;
_this.$api.post(url,JSON.stringify(ids),(resp)=>{
_this.$Message.info('拖动排序成功!');
_this.getsubjectlist();
});
},
// 打开新增(默认新增自建学科)
openAddModal() {
this.editIndex = null
this.formData.subjectName = ''
this.modalVisible = true
},
// 编辑自建学科
editItem(index) {
const row = this.subjectlist[index]
if (row.source !== 1) return
this.editIndex = index
this.formData.subjectName = row.name;
this.formData.id=row.id;
this.formData.enable=row.enable;
this.formData.sort=row.sort;
this.modalVisible = true
},
// 删除自建学科
delItem(index) {
const row = this.subjectlist[index]
if (row.source !== 1) return
this.$Modal.confirm({
title: '提示',
content: '确定删除该学科?',
onOk: () => {
// 删除条目
var url=APIConfig.BschClassSystem.subjectdelete+this.subjectlist[index].id;
this.$api.post(url,{},(resp)=>{
this.$Message.success('删除成功!');
this.getsubjectlist();
});
}
})
},
// 弹窗提交
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) return
const name = this.formData.subjectName.trim()
if (this.editIndex === null) {
// 新增默认是自建学科
const submitData ={
name:name,
schSystemId:this.gradeVal,
enable:true,
source:1,
sort:null
};
const url = APIConfig.BschClassSystem.subjectsave;
this.$api.post(url, JSON.stringify(submitData), resp => {
if (resp.code == 0) {
this.$Message.success("新增自建学科成功!");
this.getsubjectlist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
} else {
// 修改原有自建学科名称
const submitData ={
id:this.formData.id,
name:name,
schSystemId:this.gradeVal,
enable:this.formData.enable,
source:this.formData.source,
sort:this.formData.sort
};
const url = APIConfig.BschClassSystem.subjectsave;
this.$api.post(url, JSON.stringify(submitData), resp => {
if (resp.code == 0) {
this.$Message.success("修改学科成功!");
this.getsubjectlist();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
this.modalVisible = false
})
}
}
}
</script>
<style scoped>
.outer-wrap {
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 16px;
width: 100%;
height: 100%;
box-sizing: border-box;
background: #ffffff;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.top-select-wrap {
margin-bottom: 16px;
}
.subject-box {
flex: 1;
}
.btn-container {
display: flex;
flex-wrap: wrap;
gap: 12px;
row-gap: 16px;
align-items: flex-start;
}
.drag-wrap {
display: flex;
flex-wrap: wrap;
gap: 12px;
row-gap: 16px;
}
/* 按钮基础通用样式 */
.subject-btn {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
white-space: nowrap;
user-select: none;
position: relative;
display: flex;
align-items: center;
gap: 8px;
}
/* ========== 系统学科 蓝色样式 ========== */
.sys-btn {
border: 1px solid #4080ff;
color: #4080ff;
background: #fff;
}
.sys-btn.active {
background: #4080ff;
color: #fff;
}
/* ========== 自建学科 绿色样式 ========== */
.cus-btn {
border: 1px solid #00b42a;
color: #00b42a;
background: #fff;
}
.cus-btn.active {
background: #00b42a;
color: #fff;
}
/* 悬浮编辑删除组 */
.operate-group {
display: flex;
gap: 6px;
}
.icon-edit, .icon-del {
font-size: 14px;
cursor: pointer;
}
.add-subject-btn {
padding: 8px 16px;
color: #666;
}
.legend-bottom {
margin-top: 30px;
display: flex;
gap: 30px;
align-items: center;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
color: #666;
}
.legend-box {
width: 16px;
height: 16px;
border-radius: 3px;
display: inline-block;
}
.system-box {
border: 1px solid #4080ff;
background: #fff;
}
.custom-box {
border: 1px solid #00b42a;
background: #fff;
}
</style>
+35
View File
@@ -0,0 +1,35 @@
<template>
<div class="outer-wrap" style="height:calc(100% - 50px);">
<!-- 顶部标签切换 -->
<Tabs v-model="activeTab" type="line">
<TabPane label="教师信息管理" :name="1"></TabPane>
<TabPane label="任教管理" :name="2"></TabPane>
<TabPane label="教师分组管理" :name="3"></TabPane>
<!-- <TabPane label="教师变动查询" :name="4"></TabPane> -->
</Tabs>
<teacherinfo v-if="activeTab==1"></teacherinfo>
<teachseeting v-if="activeTab==2"></teachseeting>
<Teachergrouping v-if="activeTab==3"></Teachergrouping>
<Teachertransfer v-if="activeTab==4"></Teachertransfer>
</div>
</template>
<script>
import teacherinfo from "./teacherManage/teacherinfo";
import teachseeting from "./teacherManage/teachseeting";
import Teachergrouping from "./teacherManage/Teachergrouping";
import Teachertransfer from "./teacherManage/Teachertransfer";
export default {
name: 'teacherManage',
components: { teacherinfo,teachseeting,Teachergrouping,Teachertransfer },
data() {
return {
activeTab:1,
}
},
methods: {
},
}
</script>
+516
View File
@@ -0,0 +1,516 @@
<template>
<div class="group-manage-page">
<!-- 顶部操作栏 -->
<div class="top-header">
<div class="left-btn-group">
<Button type="primary" @click="openAddModal">添加</Button>
<Button type="success" style="margin-left:10px" @click="openImportModal">批量导入</Button>
</div>
<div class="right-tab-group">
<Button :type="activeType==='1'?'primary':'default'" @click="changeType('1')">年级组</Button>
<Button :type="activeType==='2'?'primary':'default'" style="margin-left:6px" @click="changeType('2')">教研组</Button>
<Button :type="activeType==='3'?'primary':'default'" style="margin-left:6px" @click="changeType('3')">备课组</Button>
</div>
</div>
<!-- 三栏主体布局 -->
<div class="three-col-container">
<!-- 左侧:分组栏 -->
<div class="col-wrap">
<h3 class="col-title">组</h3>
<div class="list-scroll-box">
<div
class="group-item"
:class="{active: selectGroupIndex === index}"
v-for="(item, index) in groupList"
:key="index"
@click="selectGroup(index)"
>
{{ item.cgName }}
</div>
</div>
<div class="col-bottom-btns">
<Button type="error" @click="delGroup">删除分组</Button>
<Button style="margin-left:8px" @click="editGroup">编辑分组</Button>
<Button type="primary">Primary</Button>
</div>
</div>
<!-- 中间:组员栏 -->
<div class="col-wrap">
<h3 class="col-title">组员</h3>
<div class="list-scroll-box">
<template v-if="groupList.length>0">
<div class="member-item" v-for="(item, idx) in groupList[selectGroupIndex].cgMember" :key="idx">
{{ item.name }}
</div>
</template>
</div>
<div class="col-bottom-btns">
<Button type="error" @click="openSetMember">设置组员</Button>
</div>
</div>
<!-- 右侧:组长栏 -->
<div class="col-wrap">
<h3 class="col-title">组长</h3>
<div class="list-scroll-box">
<template v-if="groupList.length>0">
<div class="leader-item" v-for="(item, idx) in groupList[selectGroupIndex].cgLeader" :key="idx">
{{ item.name }}
</div>
</template>
</div>
<div class="col-bottom-btns">
<Button type="error" @click="openSetLeader">设置组长</Button>
</div>
</div>
</div>
<!-- 弹窗1:添加学科 -->
<Modal v-model="addModalVisible" :title="addFormData.id?'编辑组':'添加组'" width="520">
<Form ref="addForm" :model="addFormData" label-width="90">
<FormItem label="组名">
<Input v-model="addFormData.groupName" maxlength="20" placeholder="最多可输入20个字符"/>
</FormItem>
<FormItem label="年级">
<Select v-model="addFormData.gradeIds" placeholder="请选择" style="width:100%" multiple transfer>
<Option v-for="(item,index) in gradeselectlist" :key="index" :value="item.id">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="学科">
<Select v-model="addFormData.subjectIds" placeholder="请选择" style="width:100%" multiple transfer>
<Option v-for="(item,index) in subjectselectlist" :key="index" :value="item.id">{{ item.name }}</Option>
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="addModalVisible = false">取消</Button>
<Button type="primary" @click="submitAdd">提交</Button>
</div>
</Modal>
<!-- 弹窗2:教师批量导入 -->
<Modal v-model="importModalVisible" title="批量导入分组信息" width="520">
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<FormItem label="Excel上传:" style="margin-bottom:10px;width:100%;">
<i-input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></i-input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
:show-upload-list="false"
:headers="uploadHeader"
accept=".xls,.xlsx"
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.listData.data!=null" style="margin-bottom:10px">
<div v-if="UpParmas.listData.code==0" style="color:red">导入失败,请修改Excel文件中相关数据后再重新上传!</div>
<div v-if="UpParmas.listData.code!=0" style="color:green">导入成功!</div>
</FormItem>
</Form>
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[分组导入模板.xls]</a>
</template>
</Alert>
<Table :columns="UpParmas.columns" :data="UpParmas.listData.data" v-if="UpParmas.listData.data"></Table>
</div>
<Spin fix v-if="spinShow">
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</Modal>
<user-check ref="userCheck" :treeData="treeData" v-model="userids" @on-submit="submit" :isDisabled="isAddto" />
<user-check ref="userCheck2" :treeData="treeData" v-model="userids2" @on-submit="submit2" :isDisabled="isAddto" />
</div>
</template>
<script>
import userCheck from "@/components/user_check.vue";
export default {
name: 'GroupTeacherManage',
components: {userCheck},
data() {
return {
// 顶部切换类型 grade年级组 / teach教研组 / prepare备课组
activeType: '1',
// 当前选中分组下标
selectGroupIndex: 0,
gradeselectlist:[],
subjectselectlist:[],
// 分组列表
groupList: [],
treeData: [],
userids: [],
userids2: [],
userNames: [],
isAddto: false,
// 添加分组弹窗
addModalVisible: false,
addFormData: {
id:'',
groupName: '',
gradeIds: [],
subjectIds: []
},
// 批量导入弹窗
importModalVisible: false,
spinShow:false,
UpParmas:{
fileName:'',
listData:{},
datapre:{},
actionUrl:APIConfig.BschClassSystem.importCustomGroup,
columns:[
{
title: '行号',
key: 'rows',
align: 'center',
width:'80'
},
{
title: '内容',
key: 'columnValue',
align: 'center',
},
{
title: '错误详情',
key: 'errorMsg',
align: 'center',
}
],
},
}
},
computed: {
uploadHeader() { //附件上传header
let config = {};
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
config["currentRole"] = currentRole;
}
return config;
},
},
created(){
var _this=this;
_this.treeData = [
{
name: "教师",
data: [
{
name: "基础平台",
data: _this.getTreeData2(),
}
],
},
];
},
mounted() {
this.getgradeselect();
this.getsubjectselect();
this.getSchCustomGroup();
},
methods: {
//获取年级下拉
getgradeselect:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getgradeselect, item, resp => {
if(resp.code==0)
{
this.gradeselectlist=resp.data;
}
});
},
//获取学科下拉
getsubjectselect:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getsubjectselect, item, resp => {
if(resp.code==0)
{
this.subjectselectlist=resp.data;
}
});
},
// 获取教师分组列表
getSchCustomGroup() {
const _this = this;
let item={
type:_this.activeType
};
_this.$api.get(APIConfig.BschClassSystem.getSchCustomGroup, item, resp => {
if (resp.code == 0) {
_this.groupList = resp.data;
} else {
_this.groupList = [];
}
});
},
// 切换顶部分组类型
changeType(type) {
this.activeType = type;
this.groupList=[];
// 此处可请求接口重新加载对应分组数据
this.getSchCustomGroup();
},
// 选中左侧分组
selectGroup(index) {
this.selectGroupIndex = index
// 模拟切换分组加载组员、组长数据
this.getSchCustomGroup();
},
// 打开添加弹窗
openAddModal() {
this.addModalVisible = true
this.addFormData = {id:'', groupName: '', grade: '', subject: '' }
},
// 提交新增分组
submitAdd() {
this.$refs.addForm.validate(valid => {
if (!valid) return
const newItem = {
groupName:this.addFormData.groupName,
groupType:this.activeType,
gradeIds:this.addFormData.gradeIds,
subjectIds:this.addFormData.subjectIds
}
if(this.addFormData.id){
newItem.id=this.addFormData.id;
}
const url = APIConfig.BschClassSystem.saveSchCustomGroup;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success(this.addFormData.id?"新增分组成功!":"保存分组成功!");
this.addModalVisible = false;
this.getSchCustomGroup();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
})
},
// 编辑分组
editGroup() {
this.addFormData.id=this.groupList[this.selectGroupIndex].cgId;
this.addFormData.groupName=this.groupList[this.selectGroupIndex].cgName;
this.addFormData.gradeIds=this.groupList[this.selectGroupIndex].gradeIds;
this.addFormData.subjectIds=this.groupList[this.selectGroupIndex].subjectIds;
this.addModalVisible=true;
},
// 删除分组
delGroup() {
this.$Modal.confirm({
title: '提示',
content: '确定删除该分组?删除后不可恢复',
onOk: () => {
let item={
groupId:this.groupList[this.selectGroupIndex].cgId
};
var url=APIConfig.BschClassSystem.deleteSchCustomGroup;
this.$api.post(url,item,(resp)=>{
this.selectGroupIndex = 0
this.$Message.success('删除成功!');
this.getSchCustomGroup();
});
}
})
},
// 设置组员
openSetMember() {
this.userids=this.groupList[this.selectGroupIndex].cgMember.map(a=>a.id);
this.$refs.userCheck.show();
},
// 设置组长
openSetLeader() {
this.userids2=this.groupList[this.selectGroupIndex].cgLeader.map(a=>a.id);
this.$refs.userCheck2.show();
},
// 设置组员确认
submit(arr) {
// if(arr.length==0){
// this.$Message.info('请选择组员!');
// return;
// }
let teaIds = arr.map(x => x.id);
let item={
groupId:this.groupList[this.selectGroupIndex].cgId,
teaIds:teaIds
};
var url=APIConfig.BschClassSystem.saveMembers;
this.$api.post(url,JSON.stringify(item),(resp)=>{
this.$Message.success('设置组员成功!');
this.getSchCustomGroup();
});
},
// 设置组长确认
submit2(arr) {
let teaIds = arr.map(x => x.id);
let item={
groupId:this.groupList[this.selectGroupIndex].cgId,
leaderIds:teaIds
};
var url=APIConfig.BschClassSystem.setLeaders;
this.$api.post(url,JSON.stringify(item),(resp)=>{
this.$Message.success('设置组长成功!');
this.getSchCustomGroup();
});
},
getTreeData2() {
return () =>
new Promise((resolve, reject) => {
this.$api.get(APIConfig.BschClassSystem.getTeacherTree, null, resp => {
console.log("请求完成!");
resolve([resp.data]);
});
});
},
// 批量导入相关
openImportModal() {
this.importModalVisible = true
},
//导入成功返回
importFile(response, file, fileList){
const _this=this;
console.log(response);
console.log(file);
_this.spinShow=false;
_this.UpParmas.fileName=file.name;
_this.UpParmas.listData=response;
if(response.State)
{
_this.$Message.info('导入成功!');
_this.getteachssearch();
}else{
_this.$Message.info(response.Message);
}
},
//附件上传之前的方法
handleUpload:function(file){
const _this=this;
_this.spinShow=true;
},
//模版导出
MoBanExceldaochu() {
var _this = this;
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
let query='?schId='+schId;
var url=APIConfig.BschClassSystem.GroupdownloadTemplate+query;
window.location.href=url;
}else{
this.$Message.info('获取不了学校!');
}
},
//附件上传模态框关闭事件
UpfileModalcloseclick:function(){
this.importModalVisible=false;
this.UpParmas={
fileName:'',
listData:{},
datapre:{},
}
},
}
}
</script>
<style scoped>
.group-manage-page {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 12px;
}
/* 顶部栏 */
.top-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 14px;
background: #f7f7f7;
border-radius: 4px;
margin-bottom: 16px;
flex-shrink: 0;
}
.left-btn-group, .right-tab-group {
display: flex;
align-items: center;
}
/* 三栏容器 */
.three-col-container {
display: flex;
flex: 1;
gap: 16px;
min-height: 0;
}
.col-wrap {
flex: 1;
border: 1px solid #e5e7eb;
display: flex;
flex-direction: column;
min-height: 0;
}
.col-title {
margin: 12px 14px;
font-size: 18px;
font-weight: bold;
flex-shrink: 0;
}
/* 滚动列表区域 */
.list-scroll-box {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 0 14px;
}
.group-item {
padding: 8px 10px;
margin: 4px 0;
cursor: pointer;
border-radius: 3px;
}
.group-item.active {
background: #9b77e9;
color: #fff;
}
.member-item, .leader-item {
padding: 6px 10px;
margin: 3px 0;
}
/* 底部按钮 */
.col-bottom-btns {
padding: 12px 14px;
border-top: 1px solid #eee;
flex-shrink: 0;
}
</style>
<style>
.ivu-alert-desc{
font-size: 12px;
}
</style>
+187
View File
@@ -0,0 +1,187 @@
<template>
<div class="page-wrap">
<!-- 顶部搜索区域 -->
<div class="top-search-bar">
<div class="placeholder-left"></div>
<Input
v-model="searchKey"
placeholder="账号、姓名、联系电话、变动详情"
style="width: 360px"
suffix="ios-search"
@on-search="handleSearch"
clearable
></Input>
</div>
<!-- 核心:表格自适应滚动容器 -->
<div class="table-scroll-box">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
class="teacher-table"
@on-selection-change="selectionChange"
:height="useTableHeight()-12"
></Table>
</div>
<!-- 分页栏:固定在底部,不会滚动 -->
<div class="page-fixed-bottom">
<span class="total-text">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10,20,50]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
</template>
<script>
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'TeacherChangeRecord',
data() {
return {
searchKey: '',
pageNum: 1,
pageSize: 10,
total: 52,
selectedRows: [],
tableColumns: [
{
title: '序号',
align: 'center',
width: 70,
render: (h, params) => {
const num = (this.pageNum - 1) * this.pageSize + params.index + 1
return h('span', num)
}
},
{
title: '教师账号',
key: 'account',
align: 'center'
},
{
title: '教师姓名',
key: 'name',
align: 'center'
},
{
title: '联系电话',
key: 'phone',
align: 'center'
},
{
title: '变动详情',
key: 'changeInfo',
align: 'center'
},
{
title: '变动日期',
key: 'changeDate',
align: 'center'
}
],
tableData: [
{ account: '13761014664', name: '郭秀丽', phone: '13761014664', changeInfo: '从上海市进才中学北校转出', changeDate: '2020-04-01' },
{ account: '13761040842', name: '刘莉', phone: '13761040842', changeInfo: '从上海市进才中学北校转出', changeDate: '2020-07-08' },
{ account: '13482212686', name: '万凤翠', phone: '13482212686', changeInfo: '从上海市进才中学北校转出', changeDate: '2020-07-08' },
{ account: '13681943351', name: '许娟', phone: '13681943351', changeInfo: '从上海市进才中学北校转出', changeDate: '2020-07-08' },
{ account: '18816921752', name: '许曼', phone: '18816921752', changeInfo: '从上海市进才中学北校转出', changeDate: '2020-07-08' },
{ account: '18017500305', name: '龚庆', phone: '18017500305', changeInfo: '从上海市进才中学北校转出', changeDate: '2020-07-08' },
{ account: '13901923389', name: '俞胜', phone: '13901923389', changeInfo: '从上海市进才中学北校转出', changeDate: '2020-08-30' },
{ account: '13371935731', name: '刘涵', phone: '13371935731', changeInfo: '从上海市进才中学北校教辅转出', changeDate: '2021-06-01' }
]
}
},
mixins: [self_viewport],
mounted() {
this.loadData()
},
methods: {
selectionChange(rows) {
this.selectedRows = rows
},
loadData() {
console.log('分页参数', this.pageNum, this.pageSize, this.searchKey)
// 接口请求示例
// this.$axios.get('/api/teacher/changeList', { params: { pageNum, pageSize, keyword: searchKey } }).then(res => {
// this.tableData = res.data.records
// this.total = res.data.total
// })
},
handleSearch() {
this.pageNum = 1
this.loadData()
},
pageChange(p) {
this.pageNum = p
this.loadData()
},
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadData()
}
}
}
</script>
<style scoped>
/* 页面根容器:占满父高度,纵向弹性布局 */
.page-wrap {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 12px;
}
/* 顶部搜索栏 固定高度不压缩 */
.top-search-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 14px;
background: #f7f7f7;
border-radius: 4px;
margin-bottom: 12px;
flex-shrink: 0;
}
.placeholder-left {
flex: 1;
}
/* 表格区域:自动填充剩余高度,内容超出滚动 */
.table-scroll-box {
flex: 1;
min-height: 0;
}
.table-scroll-box >>> .teacher-table {
height: 100%;
}
/* 分页容器:固定在底部,禁止被压缩、不会随表格滚动 */
.page-fixed-bottom {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 12px;
gap: 12px;
flex-shrink: 0;
}
.total-text {
font-size: 14px;
color: #333;
}
</style>
+945
View File
@@ -0,0 +1,945 @@
<template>
<div class="outer-wrap">
<div class="page-flex-layout">
<!-- 左侧分组树形栏 -->
<div class="tree-sidebar">
<div class="tree-op-buttons">
<Tooltip content="添加子组" placement="top-start">
<Button shape="circle" size="default" @click="openAddGroupModal" title="添加子组">
<Icon type="ios-add"></Icon>
</Button>
</Tooltip>
<Tooltip content="修改子组" placement="top-start">
<Button :disabled="selectnodetype==7" shape="circle" size="default" @click="openEditGroupModal" title="修改子组">
<Icon type="ios-create-outline"></Icon>
</Button>
</Tooltip>
<Tooltip content="移动子组" placement="top-start">
<Button :disabled="selectnodetype==7" shape="circle" size="default" @click="openMoveGroupModal" title="移动子组">
<Icon type="ios-folder-open"></Icon>
</Button>
</Tooltip>
<Tooltip content="删除子组" placement="top-start">
<Button :disabled="selectnodetype==7" shape="circle" size="default" @click="delCurrentGroup" title="删除子组">
<Icon type="ios-trash"></Icon>
</Button>
</Tooltip>
</div>
<my-tree :treeData="treeData" @on-tree-click="treeLink"></my-tree>
</div>
<!-- 右侧教师主体区域 -->
<div class="right-main">
<!-- 顶部操作栏 -->
<div class="top-tool-bar">
<div class="left-btn-group">
<Button :disabled="selectnodetype==7" type="primary" @click="openAddTeacherModal">添加教师</Button>
<Button type="success" style="margin-left:10px" @click="openImportModal">教师导入</Button>
</div>
<div class="search-wrap">
<Input
class="fr"
v-model="searchKeyword"
placeholder="请输入教师名字或教师账号"
search
style="width:260px"
@on-search="searchTeacher"
/>
</div>
</div>
<!-- 【修复重点容器】表格自适应区域 -->
<div class="table-adapt-wrap">
<div class="table-scroll">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
class="teacher-table"
@on-selection-change="selectionChange"
:height="useTableHeight({ isSearch: true, isOperat: true })-22"
></Table>
</div>
<!-- 分页栏:永远固定在表格底部,不跟随滚动 -->
<div class="bottom-bar">
<div class="batch-btn-group">
<Button type="error" @click="batchDeleteTeacher">删除教师</Button>
<Button style="margin-left:8px" @click="batchTransferTeacher">转出教师</Button>
<Button style="margin-left:8px" @click="openBatchMoveTeacherModal">移动教师</Button>
<Button style="margin-left:8px" @click="openSetStatusModal">设置状态</Button>
</div>
<div class="pagination-wrap">
<Page
:current.sync="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10,20,50]"
show-sizer
show-total
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
</div>
</div>
</div>
<!-- 弹窗1:添加教师 -->
<Modal v-model="addTeacherModal" title="添加教师" width="540">
<Form ref="addTeacherForm" :model="addTeacherFormData" :rules="addTeacherRules" label-width="110">
<FormItem label="教师姓名" prop="teacherName">
<Input v-model="addTeacherFormData.teacherName" placeholder="请输入老师姓名"></Input>
</FormItem>
<FormItem label="用户名" prop="account">
<Input v-model="addTeacherFormData.account" placeholder="请输入用户名"></Input>
</FormItem>
<FormItem label="密码" prop="password">
<Input v-model="addTeacherFormData.password" placeholder="请输入密码" type="password"></Input>
</FormItem>
<FormItem label="身份证号" prop="idCard">
<Input v-model="addTeacherFormData.idCard" placeholder="请输入身份证号"></Input>
</FormItem>
<FormItem label="手机号" prop="phone">
<Input v-model="addTeacherFormData.phone" placeholder="请输入手机号"></Input>
</FormItem>
<FormItem label="性别" prop="gender">
<Select v-model="addTeacherFormData.gender" style="width:100%" transfer>
<Option value="1">男</Option>
<Option value="0">女</Option>
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="addTeacherModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitAddTeacher">提交</Button>
</div>
</Modal>
<!-- 弹窗2:编辑教师 -->
<Modal v-model="editTeacherModal" title="编辑教师" width="540">
<Form ref="editTeacherForm" :model="editTeacherFormData" :rules="editTeacherRules" label-width="110">
<FormItem label="教师姓名" prop="teacherName">
<Input v-model="editTeacherFormData.teacherName"></Input>
</FormItem>
<FormItem label="用户名" prop="account">
<Input v-model="editTeacherFormData.account"></Input>
</FormItem>
<FormItem label="身份证号" prop="idCard">
<Input v-model="editTeacherFormData.idCard"></Input>
</FormItem>
<FormItem label="手机号" prop="phone">
<Input v-model="editTeacherFormData.phone"></Input>
</FormItem>
<FormItem label="性别" prop="gender">
<Select v-model="editTeacherFormData.gender" style="width:100%" transfer>
<Option value="1">男</Option>
<Option value="0">女</Option>
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="editTeacherModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitEditTeacher">提交</Button>
</div>
</Modal>
<!-- 弹窗3:教师导入 -->
<Modal v-model="importModal" title="教师导入" width="460">
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<FormItem label="Excel上传:" style="margin-bottom:10px;width:100%;">
<i-input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></i-input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
:show-upload-list="false"
:headers="uploadHeader"
accept=".xls,.xlsx"
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.listData.data!=null" style="margin-bottom:10px">
<div v-if="UpParmas.listData.code==0" style="color:red">导入失败,请修改Excel文件中相关数据后再重新上传!</div>
<div v-if="UpParmas.listData.code!=0" style="color:green">导入成功!</div>
</FormItem>
</Form>
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[教师导入模板.xls]</a>
</template>
</Alert>
<Table :columns="UpParmas.columns" :data="UpParmas.listData.data" v-if="UpParmas.listData.data"></Table>
</div>
<Spin fix v-if="spinShow">
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</Modal>
<!-- 弹窗4:添加子组 -->
<Modal v-model="addGroupModal" title="添加子组" width="420">
<Form ref="addGroupForm" :model="addGroupFormData" :rules="addGroupRules" label-width="90">
<FormItem label="组名称">
<Input v-model="addGroupFormData.parentGroupName" disabled></Input>
</FormItem>
<FormItem label="子组名称" prop="groupName">
<Input v-model="addGroupFormData.groupName" placeholder="请输入组名称"></Input>
</FormItem>
<FormItem label="子组排序">
<Input v-model="addGroupFormData.sort"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="addGroupModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitAddGroup">提交</Button>
</div>
</Modal>
<!-- 弹窗5:修改子组 -->
<Modal v-model="editGroupModal" title="修改子组" width="420">
<Form ref="editGroupForm" :model="editGroupFormData" :rules="editGroupRules" label-width="90">
<FormItem label="子组名称" prop="groupName">
<Input v-model="editGroupFormData.groupName"></Input>
</FormItem>
<FormItem label="子组排序">
<Input v-model="editGroupFormData.sort"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="editGroupModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitEditGroup">提交</Button>
</div>
</Modal>
<!-- 弹窗6:移动子组 -->
<Modal v-model="moveGroupModal" title="移动子组" width="440">
<div style="margin-bottom:12px">
<span>待移动组:</span>
<Input v-model="moveGroupTargetName" disabled style="width:280px;display:inline-block;margin-left:6px"></Input>
</div>
<div style="margin-top:10px">
<span>移动至:</span>
<Tree :data="treeData" ref="moveGroupTreeRef"></Tree>
</div>
<div slot="footer" align="right">
<Button @click="moveGroupModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmMoveGroup">确定</Button>
</div>
</Modal>
<!-- 弹窗7:批量删除确认 -->
<Modal v-model="batchDeleteModal" title="删除" width="400">
<Alert type="warning" show-icon>
确定是否删除教师?<br/>删除后将不可恢复。
</Alert>
<div slot="footer" align="right">
<Button @click="batchDeleteModal = false" style="margin-right:10px">取消</Button>
<Button type="error" @click="confirmBatchDelete">确定</Button>
</div>
</Modal>
<!-- 弹窗8:移动教师弹窗 -->
<!-- 弹窗8:移动教师弹窗(无箭头,勾选自动进右侧,右侧删除自动取消左侧勾选) -->
<Modal v-model="moveTeacherModal" title="移动教师" width="680">
<div style="margin-bottom:16px;display:flex;align-items:center;">
<span style="white-space:nowrap;margin-right:10px;">待移动教师:</span>
<Input v-model="moveTeacherName" disabled style="flex:1" />
</div>
<div style="display:flex;align-items:stretch;gap:12px;">
<!-- 左侧:选择组 zTree勾选树 -->
<div style="flex:1;border:1px solid #dcdcdc;display:flex;flex-direction:column;">
<div style="background:#3377dd;color:#fff;padding:8px 12px;text-align:center;font-weight:bold;">选择组</div>
<div style="flex:1;overflow:auto;padding:8px;">
<my-tree
ref="moveTeacherLeftTree"
:treeData="treeData"
:setting="treeSetting"
@on-tree-Check="checkedtreeLink"
></my-tree>
</div>
</div>
<!-- 中间箭头区域直接删除,不再需要 -->
<!-- 右侧:移动至组 已选列表 带单条删除 -->
<div style="flex:1;border:1px solid #dcdcdc;display:flex;flex-direction:column;">
<div style="background:#3377dd;color:#fff;padding:8px 12px;text-align:center;font-weight:bold;">移动至组</div>
<div style="flex:1;overflow:auto;padding:8px;">
<div
v-for="(item,idx) in targetGroupList"
:key="item.id"
style="padding:4px 0;display:flex;align-items:center;gap:6px;justify-content:space-between;"
>
<div style="display:flex;align-items:center;gap:6px;">
<Icon type="ios-checkbox-outline"></Icon>
<span>{{ item.name }}</span>
</div>
<Icon
type="ios-close"
style="color:#f56c6c;cursor:pointer;font-size:16px;"
@click="handleDeleteRightItem(item, idx)"
/>
</div>
<div v-if="targetGroupList.length === 0" style="color:#999;text-align:center;padding:20px 0;">暂无选中分组</div>
</div>
</div>
</div>
<div slot="footer" align="right">
<Button @click="moveTeacherModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitMoveTeacher">确定</Button>
</div>
</Modal>
<!-- 弹窗9:【设置教师的状态】 匹配设计图 -->
<Modal v-model="setStatusModal" title="设置教师的状态" width="380">
<Form ref="statusForm" :model="statusFormData" label-width="120">
<FormItem label="选择设置状态">
<RadioGroup v-model="statusFormData.status">
<Radio label="1">正常</Radio>
<Radio label="0">禁用</Radio>
</RadioGroup>
</FormItem>
</Form>
<div slot="footer" align="right">
<Button @click="setStatusModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitSetStatus">确定</Button>
</div>
</Modal>
<!-- 弹窗10:转出教师弹窗 -->
<Modal v-model="transferTeacherModal" title="转出教师" width="420">
<p>确认将选中教师做转出操作?转出后不在本校编制内</p>
<div slot="footer" align="right">
<Button @click="transferTeacherModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmTransferTeacher">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'TeacherInfoManage',
components: { myTree },
data() {
return {
selectnodeid:'',
selectnodetitle: '',
selectnodetype:7,
searchKeyword: '',
selectedRows: [],
pageNum: 1,
pageSize: 20,
total: 0,
treeData: [],
// 移动教师弹窗穿梭框
checkedTreeNodes: [], // 左侧树形勾选的节点
targetGroupList: [], // 右侧移入的目标分组数组
treeSetting:{
view: {
dblClickExpand: false,
showIcon: false,
},
check: {
enable: true,
// 核心两项配置
chkboxType: {
"Y": "", // 勾选父节点 【不会】向下联动勾选子节点
"N": "" // 取消父节点 【不会】向下联动取消子节点
},
autoCheckTrigger: false
},
callback: {}
},
tableColumns: [
{ type: 'selection', width: 50 },
{ title: '序号', type: 'index', align: 'center', width: 70 },
{ title: '教师账号', key: 'account', align: 'center' },
{ title: '教师姓名', key: 'name', align: 'center' },
{
title: '教师组',
key: 'groupName',
align: 'center',
render: (h, params) => {
return h('div', [
params.row.groupList.map(a=>a.name).join(',')
]);
},
},
{ title: '联系电话', key: 'tel', align: 'center' },
{
title: '性别',
key: 'gender',
align: 'center',
width: 70,
render: (h, params) => {
return h('div', [
params.row.gender==0?'女':params.row.gender==1?'男':''
]);
},
},
{ title: '状态', key: 'statusName', align: 'center', width: 80 },
{
title: '操作',
key: 'action',
align: 'center',
width: 90,
render: (h, params) => {
return h('Button', {
props: { type: 'text', icon: 'md-create', size: 'large' },
on: { click: () => this.editSingleTeacher(params.row, params.index) }
})
}
}
],
tableData: [],
spinShow:false,
UpParmas:{
fileName:'',
listData:{},
datapre:{},
actionUrl:APIConfig.BschClassSystem.teacherImport,
columns:[
{
title: '行号',
key: 'rows',
align: 'center',
width:'80'
},
{
title: '内容',
key: 'columnValue',
align: 'center',
},
{
title: '错误详情',
key: 'errorMsg',
align: 'center',
}
],
},
addTeacherModal: false,
editTeacherModal: false,
importModal: false,
addGroupModal: false,
editGroupModal: false,
moveGroupModal: false,
batchDeleteModal: false,
moveTeacherModal: false,
setStatusModal: false,
transferTeacherModal: false,
addTeacherFormData: { teacherName: '', account: '', password: '', idCard: '', phone: '', gender: '0' },
addTeacherRules: {
teacherName: [{ required: true, message: '请输入教师姓名', trigger: 'blur' }],
account: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
gender: [{ required: true, message: '请选择性别', trigger: 'change' }]
},
editTeacherIndex: null,
editTeacherFormData: {id:null, teacherName: '', account: '', idCard: '', phone: '', gender: '0',teaId:0,status:1 },
editTeacherRules: {
teacherName: [{ required: true, message: '请输入教师姓名', trigger: 'blur' }],
account: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
gender: [{ required: true, message: '请选择性别', trigger: 'change' }]
},
addGroupFormData: { parentGroupName: '', groupName: '', sort: '' },
addGroupRules: { groupName: [{ required: true, message: '请输入子组名称', trigger: 'blur' }] },
editGroupIndex: null,
editGroupFormData: { groupName: '', sort: '' },
editGroupRules: { groupName: [{ required: true, message: '请输入子组名称', trigger: 'blur' }] },
moveGroupTargetName: '',
moveTeacherName: '',
statusFormData: { status: '1' }
}
},
mixins: [self_viewport],
computed: {
uploadHeader() { //附件上传header
let config = {};
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
config["currentRole"] = currentRole;
}
return config;
},
},
created() {
this.getTeaTree();
},
methods: {
//获取教师树
getTeaTree:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getTeaTree, item, resp => {
if(resp.code==0)
{
_this.treeData=resp.data;
_this.selectnodeid=resp.data.id;
_this.selectnodetitle=resp.data.name;
_this.selectnodetype=resp.data.type;
_this.loadData();
}
});
},
// 树形选中切换
treeLink(treeNode) {
this.selectnodeid=treeNode.id;
this.selectnodetitle=treeNode.name;
this.selectnodetype=treeNode.type;
this.pageNum = 1
this.loadData()
},
// 移动教师树形复选框勾选回调(接收my-tree抛出的zTree选中数组)移动教师树形选中切换
checkedtreeLink(allCheckedList){
this.checkedTreeNodes = [...allCheckedList]
// 右侧列表 = 只保留当前左侧还在勾选的条目,自动过滤取消勾选项
this.targetGroupList = this.targetGroupList.filter(rightItem => {
return allCheckedList.some(leftItem => leftItem.id === rightItem.id)
})
// 新增左侧新勾选的节点,去重加入右侧
allCheckedList.forEach(node => {
const exist = this.targetGroupList.some(item => item.id === node.id)
if (!exist) {
this.targetGroupList.push({
id: node.id,
name: node.name
})
}
})
},
// 加载列表数据
loadData() {
const _this = this;
var item = {
groupId:_this.selectnodeid,
keyword:_this.searchKeyword,
page:_this.pageNum-1,
size:_this.pageSize
};
_this.$api.get(APIConfig.BschClassSystem.getteachersearch, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
selectionChange(rows) {
this.selectedRows = rows
},
searchTeacher() {
this.pageNum = 1;
this.loadData();
},
pageChange(p) { this.pageNum = p;this.loadData(); },
sizeChange(s) { this.pageSize = s; this.pageNum = 1;this.loadData(); },
openAddTeacherModal() {
this.addTeacherFormData = { teacherName: '', account: '', password: '', idCard: '', phone: '', gender: '0' }
this.addTeacherModal = true
},
submitAddTeacher() {
this.$refs.addTeacherForm.validate(valid => {
if (!valid) return
// 新增
const newItem = {
groupId:this.selectnodeid,
name:this.addTeacherFormData.teacherName,
account:this.addTeacherFormData.account,
pwd:this.addTeacherFormData.password,
idCard:this.addTeacherFormData.idCard,
tel:this.addTeacherFormData.phone,
gender:this.addTeacherFormData.gender,
teaId:0,
status:1
}
const url = APIConfig.BschClassSystem.teachersave;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("新增教师成功!");
this.addTeacherModal = false
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
})
},
editSingleTeacher(row, idx) {
this.editTeacherIndex = idx;
this.editTeacherFormData = {
id:row.id,
teacherName: row.name,
account: row.account,
idCard: row.idCard || '',
phone: row.tel,
gender: row.gender+'',
teaId:row.teaId,
status:row.status
};
this.editTeacherModal = true;
},
submitEditTeacher() {
this.$refs.editTeacherForm.validate(valid => {
if (!valid) return
// 编辑
const newItem = {
id:this.editTeacherFormData.id,
groupId:this.selectnodeid,
name:this.editTeacherFormData.teacherName,
account:this.editTeacherFormData.account,
idCard:this.editTeacherFormData.idCard,
tel:this.editTeacherFormData.phone,
gender:this.editTeacherFormData.gender,
teaId:this.editTeacherFormData.teaId,
status:this.editTeacherFormData.status
}
const url = APIConfig.BschClassSystem.teachersave;
this.$api.post(url, JSON.stringify(newItem), resp => {
if (resp.code == 0) {
this.$Message.success("编辑教师成功!");
this.editTeacherModal = false
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
})
},
openImportModal() { this.importModal = true },
//导入成功返回
importFile(response, file, fileList){
const _this=this;
console.log(response);
console.log(file);
_this.spinShow=false;
_this.UpParmas.fileName=file.name;
_this.UpParmas.listData=response;
if(response.State)
{
_this.$Message.info('导入成功!');
_this.getsearcBaseSchClass();
}else{
_this.$Message.info(response.Message);
}
},
//附件上传之前的方法
handleUpload:function(file){
const _this=this;
_this.spinShow=true;
},
MoBanExceldaochu() {
var _this = this;
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
var url=APIConfig.BschClassSystem.teacherdownloadTemplate+'?schId='+schId;
window.location.href=url;
}else{
this.$Message.info('获取不了学校!');
}
},
//附件上传模态框关闭事件
UpfileModalcloseclick:function(){
this.importModal=false;
this.UpParmas={
fileName:'',
listData:{},
datapre:{},
}
},
batchDeleteTeacher() {
if (!this.selectedRows.length) return this.$Message.warning('请先勾选教师')
this.batchDeleteModal = true
},
confirmBatchDelete() {
const idlist = this.selectedRows.map(i => i.id);
const url = APIConfig.BschClassSystem.teacherdelete+idlist.join(',');
this.$api.post(url,{}, resp => {
if (resp.code == 0) {
this.$Message.success("删除成功!");
this.selectedRows = [];
this.batchDeleteModal = false;
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
},
// 弹窗打开初始化
openBatchMoveTeacherModal() {
if (!this.selectedRows.length) return this.$Message.warning('请先勾选教师')
this.moveTeacherName = this.selectedRows.map(i => i.name).join('、')
// 清空数据
this.checkedTreeNodes = []
this.targetGroupList = []
this.$nextTick(() => {
// 清空整棵树所有勾选
const ztree = this.$refs.moveTeacherLeftTree.getZtreeInstance()
ztree.checkAllNodes(false)
})
this.moveTeacherModal = true
},
// 右侧点击X删除单条 → 反向取消左侧树勾选
handleDeleteRightItem(delItem, index) {
// 删除右侧数组
this.targetGroupList.splice(index, 1)
// 获取树实例,取消对应节点复选框
const ztree = this.$refs.moveTeacherLeftTree.getZtreeInstance()
const targetNode = ztree.getNodeByParam('id', delItem.id)
if (targetNode) {
ztree.checkNode(targetNode, false, false)
}
},
// 确定按钮 直接提交
submitMoveTeacher() {
if (this.targetGroupList.length === 0) {
return this.$Message.warning('请至少选择一个目标分组')
}
const teacherIdArr = this.selectedRows.map(row => row.id)
const groupIdArr = this.targetGroupList.map(item => item.id)
// console.log('待移动教师ID', teacherIdArr)
// console.log('目标分组ID', groupIdArr)
let item={
ids:teacherIdArr,
transferGroupIds:groupIdArr
};
const url = APIConfig.BschClassSystem.moveGroup;
this.$api.post(url,JSON.stringify(item), resp => {
if (resp.code == 0) {
this.$Message.success('教师移动分组成功');
this.moveTeacherModal = false;
// 清空选择缓存
this.selectedRows = [];
this.targetGroupList = [];
this.checkedTreeNodes = [];
// 刷新表格数据
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
},
batchTransferTeacher() {
if (!this.selectedRows.length) return this.$Message.warning('请先勾选教师')
this.transferTeacherModal = true
},
//转出事件
confirmTransferTeacher() {
const idlist = this.selectedRows.map(i => i.id);
const url = APIConfig.BschClassSystem.transferOut+idlist.join(',');
this.$api.post(url,{}, resp => {
if (resp.code == 0) {
this.$Message.success("转出操作完成!");
this.selectedRows = [];
this.transferTeacherModal = false;
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
},
openBatchMoveTeacherModal() {
if (!this.selectedRows.length) return this.$Message.warning('请先勾选教师')
this.moveTeacherName = this.selectedRows.map(i => i.name).join('、')
this.moveTeacherModal = true
},
confirmMoveTeacher() {
this.moveTeacherModal = false
this.$Message.success('移动教师成功')
},
openSetStatusModal() {
if (!this.selectedRows.length) return this.$Message.warning('请先勾选教师')
this.statusFormData.status = '1'
this.setStatusModal = true
},
submitSetStatus() {
const idlist = this.selectedRows.map(i => i.id);
let item={
ids:idlist.join(','),
status:this.statusFormData.status
};
const url = APIConfig.BschClassSystem.teachersetStatus;
this.$api.post(url,item, resp => {
if (resp.code == 0) {
this.$Message.success("状态修改成功!");
this.selectedRows = [];
this.setStatusModal = false;
this.loadData();
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
},
openAddGroupModal() {
if (!this.selectnodeid) return this.$Message.warning('请先选择父分组')
this.addGroupFormData.parentGroupName = this.selectnodetitle
this.addGroupFormData.groupName = ''
this.addGroupFormData.sort = ''
this.addGroupModal = true
},
submitAddGroup() {
this.$refs.addGroupForm.validate(valid => {
if (!valid) return;
this.$Message.success('新增分组成功');
this.addGroupModal = false;
})
},
openEditGroupModal() {
if (!this.selectnodeid) return this.$Message.warning('请先选择分组')
this.editGroupIndex = this.selectnodeid;
this.editGroupFormData.groupName = this.selectnodetitle;
this.editGroupFormData.sort = '';
this.editGroupModal = true;
},
submitEditGroup() {
this.$refs.editGroupForm.validate(valid => {
if (!valid) return
this.selectedTreeNode.title = this.editGroupFormData.groupName
this.editGroupModal = false
this.$Message.success('修改分组成功')
})
},
openMoveGroupModal() {
if (!this.selectnodeid) return this.$Message.warning('请先选择分组')
this.moveGroupTargetName = this.selectnodetitle
this.moveGroupModal = true
},
confirmMoveGroup() {
this.moveGroupModal = false
this.$Message.success('移动分组成功')
},
delCurrentGroup() {
if (!this.selectnodeid) return this.$Message.warning('请先选择分组')
this.$Modal.confirm({
title: '提示',
content: `确定删除分组【${this.selectedTreeNode.title}】?`,
onOk: () => {
this.$Message.success('删除分组成功')
}
})
}
}
}
</script>
<style scoped>
/* 全局布局逐级修复高度 + min-height:0 解决flex塌陷 */
.outer-wrap {
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 16px;
width: 100%;
height: 100%;
box-sizing: border-box;
background: #fff;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.page-flex-layout {
display: flex;
flex: 1;
gap: 16px;
min-height: 0; /* 第一层修复 */
}
.tree-sidebar {
width: 250px;
border: 1px solid #e8e8e8;
display: flex;
flex-direction: column;
min-height: 0;
}
.tree-op-buttons {
padding: 8px;
display: flex;
gap: 8px;
border-bottom: 1px solid #eee;
}
.tree-sidebar >>> .ivu-tree {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.right-main {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0; /* 第二层修复 */
}
.top-tool-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0px 16px;
background: #f7f7f7;
margin-bottom: 16px;
flex-shrink: 0;
}
.table-adapt-wrap {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0; /* 第三层核心修复 */
}
.table-scroll {
flex: 1;
min-height: 0;
}
.teacher-table {
height: 100%;
}
/* 分页固定底部,永远不参与滚动 */
.bottom-bar {
margin-top: 16px;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.total-text {
font-size: 14px;
color: #666;
margin-right: 12px;
}
</style>
<style>
.ivu-alert-desc{
font-size: 12px;
}
</style>
+537
View File
@@ -0,0 +1,537 @@
<template>
<div class="teach-manage-page">
<!-- 顶部筛选栏 -->
<div class="top-filter-bar">
<div class="filter-left">
<Select v-model="majorVal" placeholder="选择学制" style="width: 220px" @on-change="getselectBySchsystemId">
<Option v-for="(item,index) in schsystemselectlist" :key="index" :value="item.id">{{ item.name }}</Option>
</Select>
<Select v-model="gradeVal" placeholder="选择年级" style="width: 180px; margin-left: 12px" @on-change="handleGradeChange">
<Option v-for="(item,index) in selectBySchsystemIdlist" :key="index" :value="item.id">{{ item.name }}</Option>
</Select>
</div>
<div class="btn-group-right">
<Button type="success" @click="handleBatchImport">批量导入</Button>
<Button type="success" style="margin-left: 10px" @click="handleExport">任教导出</Button>
</div>
</div>
<!-- 关键外层嵌套:强制横向溢出滚动 -->
<div class="table-outer">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
:height="useTableHeight()+48"
></Table>
</div>
<!-- 选择教师弹窗 -->
<Modal v-model="teacherModalVisible" title="选择教师" width="420" @on-cancel="resetModal">
<div style="padding:10px 20px;">
<div style="margin-bottom:18px;">
<span style="display:inline-block;width:60px;">科室</span>
<Select v-model="selectDept" placeholder="按科室过滤" style="width:280px" @on-change="getteacherSelect" clearable>
<Option v-for="item in groupteacherSelectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
</div>
<div>
<span style="display:inline-block;width:60px;">教师</span>
<Select v-model="selectTeacher" placeholder="请选择" style="width:280px" filterable>
<Option v-for="item in teacherSelectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
</div>
</div>
<div slot="footer" align="right">
<Button @click="teacherModalVisible = false">取消</Button>
<Button type="primary" @click="submitSelectTeacher">提交</Button>
</div>
</Modal>
<!-- 批量导入弹窗 -->
<Modal v-model="importModalVisible" title="批量导入任教信息" width="520">
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<FormItem label="Excel上传:" style="margin-bottom:10px;width:100%;">
<i-input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></i-input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
:show-upload-list="false"
:headers="uploadHeader"
accept=".xls,.xlsx"
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.listData.data!=null" style="margin-bottom:10px">
<div v-if="UpParmas.listData.code==0" style="color:red">导入失败,请修改Excel文件中相关数据后再重新上传!</div>
<div v-if="UpParmas.listData.code!=0" style="color:green">导入成功!</div>
</FormItem>
</Form>
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[任教导入模板.xls]</a>
</template>
</Alert>
<Table :columns="UpParmas.columns" :data="UpParmas.listData.data" v-if="UpParmas.listData.data"></Table>
</div>
<Spin fix v-if="spinShow">
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</Modal>
</div>
</template>
<script>
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'TeachManage',
mixins: [self_viewport],
data() {
return {
majorVal: '',
gradeVal: '',
importModalVisible: false,
schsystemselectlist: [],
selectBySchsystemIdlist: [],
// 后端原始数据源
subjectList: [],
classList: [],
teachList: [],
tableColumns: [],
tableData: [],
teacherModalVisible: false,
currentRowIndex: null,
currentSubId: '',
currentClassId: null,
oldTeacherId: null,
detailId: null, // 任教明细主键id
selectDept: '',
selectTeacher: '',
groupteacherSelectlist: [],
teacherSelectlist: [],
spinShow:false,
UpParmas:{
fileName:'',
listData:{},
datapre:{},
actionUrl:APIConfig.BschClassSystem.teacherImport,
columns:[
{
title: '行号',
key: 'rows',
align: 'center',
width:'80'
},
{
title: '内容',
key: 'columnValue',
align: 'center',
},
{
title: '错误详情',
key: 'errorMsg',
align: 'center',
}
],
},
}
},
computed: {
uploadHeader() { //附件上传header
let config = {};
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
config["currentRole"] = currentRole;
}
return config;
},
},
created() {
this.getschsystemselect();
this.getgroupteacherSelect();
this.getteacherSelect();
},
methods: {
// 学制下拉
getschsystemselect() {
const _this = this;
_this.$api.get(APIConfig.BschClassSystem.getschsystemselect, {}, resp => {
if (resp.code == 0) {
_this.schsystemselectlist = resp.data;
if (_this.schsystemselectlist.length > 0) {
_this.majorVal = _this.schsystemselectlist[0].id;
_this.getselectBySchsystemId();
}
} else {
_this.schsystemselectlist = [];
}
});
},
// 通过学制ID获取年级下拉
getselectBySchsystemId() {
const _this = this;
const item = { schsystemId: _this.majorVal };
_this.$api.get(APIConfig.BschClassSystem.getselectBySchsystemId, item, resp => {
if (resp.code == 0) {
_this.selectBySchsystemIdlist = resp.data;
_this.getteachssearch();
} else {
_this.selectBySchsystemIdlist = [];
}
});
},
// 教师组下拉
getgroupteacherSelect() {
const _this = this;
_this.$api.get(APIConfig.BschClassSystem.getgroupteacherSelect, {}, resp => {
if (resp.code == 0) {
_this.groupteacherSelectlist = resp.data;
} else {
_this.groupteacherSelectlist = [];
}
});
},
// 教师下拉
getteacherSelect() {
const _this = this;
const item = { groupId: _this.selectDept };
_this.$api.get(APIConfig.BschClassSystem.getteacherSelect, item, resp => {
if (resp.code == 0) {
_this.teacherSelectlist = resp.data;
if (_this.oldTeacherId) {
const findT = _this.teacherSelectlist.find(t => t.id === _this.oldTeacherId);
if (findT) {
_this.selectTeacher = findT.id;
}
}
} else {
_this.teacherSelectlist = [];
}
});
},
// 打开弹窗并回显原有教师+明细ID
openTeacherModal(rowIdx, rowData, subId) {
this.resetModal();
this.currentRowIndex = rowIdx;
this.currentSubId = subId;
this.currentClassId = rowData.classId;
this.detailId = null;
const classTeach = this.teachList.find(item => item.classId === this.currentClassId);
if (classTeach) {
const subItem = classTeach.teachSubjectList.find(s => s.subId === subId);
if (subItem) {
this.oldTeacherId = subItem.teacherId || null;
this.detailId = subItem.id;
}
}
this.teacherModalVisible = true;
this.getteacherSelect();
},
// 核心:获取任教列表并格式化表格数据 + 自动生成表头
getteachssearch() {
const _this = this;
const item = {
schsystemId: this.majorVal,
gradeId: this.gradeVal
};
_this.$api.get(APIConfig.BschClassSystem.getteachssearch, item, resp => {
if (resp.code !== 0) {
_this.subjectList = [];
_this.classList = [];
_this.teachList = [];
_this.tableData = [];
_this.tableColumns = [];
return;
}
const { subjectList, classList, teachList } = resp.data;
_this.subjectList = subjectList;
_this.classList = classList;
_this.teachList = teachList;
// 组装表格行数据:一行一个班级,key=subId
_this.tableData = _this.classList.map(classItem => {
const matchTeach = _this.teachList.find(t => t.classId === classItem.classId);
const row = {
gradeId:classItem.gradeId,
grade: classItem.gradeName,
className: classItem.name,
classId: classItem.classId
};
if (matchTeach && Array.isArray(matchTeach.teachSubjectList)) {
matchTeach.teachSubjectList.forEach(subItem => {
row[subItem.subId] = subItem.teacherName || '';
});
}
return row;
});
// 动态构建列
_this.buildTableColumns();
});
},
// 动态生成表格列(完全基于后端subjectList,无硬编码科目)
buildTableColumns() {
// 左侧固定列
const baseColumns = [
{ title: '年级', key: 'grade', fixed: 'left', width: 110, align: 'center' },
{ title: '班级', key: 'className', fixed: 'left', width: 110, align: 'center' }
];
// 科目动态列
const subjectColumns = this.subjectList.map(subItem => {
const subId = subItem.id;
const subName = subItem.name;
return {
title: subName,
key: subId,
width: 180,
align: 'center',
render: (h, params) => {
const val = params.row[subId];
return h('div', {
style: {
display: 'flex',
alignItems: 'center',
width: '140px',
height: '34px',
border: '1px solid #dcdcdc',
borderRadius: '4px',
cursor: 'pointer',
margin: '0 auto'
},
on: {
click: () => {
this.openTeacherModal(params.index, params.row, subId)
}
}
}, [
h('span', {
style: {
flex: 1,
textAlign: 'center',
fontSize: '14px',
paddingLeft: '6px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}, val || '点击选择'),
val ? h('span', {
style: {
width: '22px',
height: '22px',
lineHeight: '22px',
textAlign: 'center',
color: 'red',
borderLeft: '1px solid #eee',
flexShrink: 0,
fontSize: '14px',
cursor: 'pointer'
},
on: {
click: (e) => {
e.stopPropagation();
const row = params.row;
let delId = null;
const classTeach = this.teachList.find(t => t.classId === row.classId);
if (classTeach) {
const target = classTeach.teachSubjectList.find(s => s.subId === subId);
delId = target?.id;
}
this.$api.post(APIConfig.BschClassSystem.teachsdelete+delId, {}, res => {
if (res.code === 0) {
this.$Message.success('移除教师成功');
this.getteachssearch();
}
});
}
}
}, '×') : null
]);
}
};
});
// 右侧操作列
const actionColumn = [{
title: '操作',
key: 'action',
fixed: 'right',
width: 80,
align: 'center',
render: (h, params) => {
return h('Button', {
props: { type: 'text', icon: 'ios-refresh', size: 'large' },
attrs: { title: '重置本行所有任教' },
on: { click: () => this.handleResetRow(params.index) }
})
}
}];
this.tableColumns = [...baseColumns, ...subjectColumns, ...actionColumn];
},
// 弹窗提交分配教师
submitSelectTeacher() {
if (!this.selectTeacher) {
this.$Message.warning('请选择教师');
return;
}
const row = this.tableData[this.currentRowIndex];
const params = {
id: this.detailId,
gradeId:row.gradeId,
classId: row.classId,
subId: this.currentSubId,
teaId: this.selectTeacher
};
this.$api.post(APIConfig.BschClassSystem.teachssave, JSON.stringify(params), res => {
if (res.code === 0) {
this.$Message.success('分配教师成功');
this.teacherModalVisible = false;
this.getteachssearch();
}
});
},
// 关闭弹窗重置变量
resetModal() {
this.selectDept = '';
this.selectTeacher = '';
this.currentRowIndex = null;
this.currentSubId = '';
this.currentClassId = null;
this.oldTeacherId = null;
this.detailId = null;
},
// 整行重置清空所有科目教师
handleResetRow(index) {
this.$Modal.confirm({
title: '提示',
content: '确定清空该班级所有学科任教教师?',
onOk: () => {
const row = this.tableData[index];
const params = {
classId: row.classId
};
this.$api.post(APIConfig.BschClassSystem.delClassTeach, params, res => {
if (res.code === 0) {
this.getteachssearch();
this.$Message.success('重置完成');
}
});
}
});
},
handleGradeChange() {
this.getteachssearch();
},
handleBatchImport() {
this.importModalVisible = true;
},
//导入成功返回
importFile(response, file, fileList){
const _this=this;
console.log(response);
console.log(file);
_this.spinShow=false;
_this.UpParmas.fileName=file.name;
_this.UpParmas.listData=response;
if(response.State)
{
_this.$Message.info('导入成功!');
_this.getteachssearch();
}else{
_this.$Message.info(response.Message);
}
},
//附件上传之前的方法
handleUpload:function(file){
const _this=this;
_this.spinShow=true;
},
MoBanExceldaochu() {
var _this = this;
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
let query=this.gradeVal?'?schId='+schId+'&schsystemId='+this.majorVal+'&gradeId='+this.gradeVal:'?schId='+schId+'&schsystemId='+this.majorVal;
var url=APIConfig.BschClassSystem.TeachdownloadTemplate+query;
window.location.href=url;
}else{
this.$Message.info('获取不了学校!');
}
},
//附件上传模态框关闭事件
UpfileModalcloseclick:function(){
this.importModalVisible=false;
this.UpParmas={
fileName:'',
listData:{},
datapre:{},
}
},
handleExport() {
var _this = this;
if (localStorage.getItem("currentRole")) {
let currentRole = localStorage.getItem("currentRole");
let { schId, roleId, userId, childId } = JSON.parse(decodeURIComponent(currentRole));
let query=this.gradeVal?'?schId='+schId+'&schsystemId='+this.majorVal+'&gradeId='+this.gradeVal:'?schId='+schId+'&schsystemId='+this.majorVal;
var url=APIConfig.BschClassSystem.teachsexport+query;
window.location.href=url;
}else{
this.$Message.info('获取不了学校!');
}
}
}
}
</script>
<style scoped>
.teach-manage-page {
width: 100%;
padding: 10px;
box-sizing: border-box;
}
.top-filter-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background-color: #f7f7f7;
margin-bottom: 10px;
}
.filter-left, .btn-group-right {
display: flex;
align-items: center;
}
.table-outer {
width: 100%;
overflow-x: auto;
}
</style>
<style>
.ivu-alert-desc{
font-size: 12px;
}
</style>
+302
View File
@@ -0,0 +1,302 @@
<template>
<div class="teacher-transfer-wrap">
<!-- 顶部筛选栏 -->
<div class="top-filter-bar">
<div class="search-wrap" style="width:100%;">
<span>学校:</span>
<Select v-model="filterSchool" style="width: 180px" @on-change="getgroupSelectBySchId">
<Option value="-1">全部</Option>
<Option v-for="item in SchoolList" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<span style="margin-left: 16px">学科组:</span>
<Select v-model="filterGroup" style="width: 180px" @on-change="handleFilterChange">
<Option value="-1">全部</Option>
<Option v-for="item in groupSelectBySchIdlList" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<span class="fr">
<Input
placeholder="请输入教师姓名"
search
@on-search="handleSearch"
suffix="ios-search"
style="width: 280px"
v-model="searchName"
/>
</span>
</div>
</div>
<!-- 自适应滚动表格容器 -->
<div class="table-scroll-wrap">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
:height="useTableHeight()+32"
@on-selection-change="onSelectChange"
></Table>
</div>
<!-- 底部栏:转入按钮 + 固定分页 -->
<div class="bottom-bar">
<Button type="default" status="danger" @click="openTransferModal">转入</Button>
<div class="page-area">
<span style="margin-right: 12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
<!-- 弹窗:转至其他组 -->
<Modal v-model="transferModalVisible" title="转至其他组" width="540">
<div style="padding: 15px 10px;">
<div style="margin-bottom: 18px;display: flex;align-items: center;">
<span style="width: 100px;">待移动教师:</span>
<Input v-model="moveTeacherName" disabled style="flex:1;"></Input>
</div>
<div>
<span style="width: 100px;display: inline-block;margin-bottom: 10px;color:#f56c6c;font-weight:bold;">移动至:</span>
<my-tree :treeData="groupTreeData" @on-tree-click="treeLink"></my-tree>
</div>
</div>
<div slot="footer" align="right">
<Button @click="transferModalVisible = false">取消</Button>
<Button type="primary" @click="submitTransfer">提交</Button>
</div>
</Modal>
</div>
</template>
<script>
import myTree from "../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'TeacherTransferPage',
components: { myTree },
data() {
return {
// 筛选条件
filterSchool: '-1',
filterGroup: '-1',
searchName: '',
// 分页
pageNum: 1,
pageSize: 10,
total: 0,
selectedRows: [],
// 表格配置列
tableColumns: [],
SchoolList:[],
groupSelectBySchIdlList:[],
tableData: [],
// 转入弹窗
transferModalVisible: false,
selectnodeid:0,
moveTeacherName: '',
targetGroupNode: null,
// 目标分组树形数据
groupTreeData: []
}
},
mixins: [self_viewport],
created() {
this.getSchoolList();
this.initTableColumn()
this.loadTableData()
},
methods: {
//获取学校
getSchoolList:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.transferOutSchSelect, item, resp => {
if(resp.code==0)
{
_this.SchoolList = resp.data;
}else{
_this.SchoolList=[];
}
});
},
//获取科室
getgroupSelectBySchId:function(){
const _this = this;
var item={
schId:_this.filterSchool
};
_this.$api.get(APIConfig.BschClassSystem.getgroupSelectBySchId, item, resp => {
if(resp.code==0)
{
_this.groupSelectBySchIdlList = resp.data;
}else{
_this.groupSelectBySchIdlList=[];
}
});
},
// 初始化配置式表格列
initTableColumn() {
this.tableColumns = [
{ type: 'selection', width: 55, align: 'center' },
{ title: '序号', type: 'index', align: 'center', width: 70 },
{ title: '学校', key: 'schName', align: 'center' },
{ title: '学科组', key: 'groupNames', align: 'center', width: 110 },
{ title: '教师姓名', key: 'name', align: 'center' },
{ title: '身份证', key: 'idCard', align: 'center' },
]
},
// 勾选行回调
onSelectChange(rows) {
this.selectedRows = rows
},
// 加载列表数据
loadTableData() {
const _this = this;
var item = {
schId:_this.filterSchool=='-1'?'':_this.filterSchool,
groupId:_this.filterGroup=='-1'?'':_this.filterGroup,
keyword:_this.searchName,
page:_this.pageNum-1,
size:_this.pageSize
};
_this.$api.get(APIConfig.BschClassSystem.getsearchTransferOut, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
// 下拉筛选切换
handleFilterChange() {
this.pageNum = 1
this.loadTableData()
},
// 搜索
handleSearch() {
this.pageNum = 1
this.loadTableData()
},
// 分页切换
pageChange(p) {
this.pageNum = p
this.loadTableData()
},
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadTableData()
},
// 打开转入弹窗
openTransferModal() {
if (!this.selectedRows.length) {
return this.$Message.warning('请先勾选需要转入的教师')
}
this.moveTeacherName=this.selectedRows.map(a=>a.name).join(',');
this.transferModalVisible = true
this.getTeaTree();
},
//获取教师树
getTeaTree:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getTeaTree, item, resp => {
if(resp.code==0)
{
_this.groupTreeData=resp.data;
}
});
},
// 树形选中切换
treeLink(treeNode) {
this.selectnodeid=treeNode.id;
},
// 提交转入分组
submitTransfer() {
if (!this.selectnodeid) {
return this.$Message.warning('请选择目标分组')
}
const teacherIdArr = this.selectedRows.map(row => row.schId+'_'+row.teaId);
let item={
schTeaIds:teacherIdArr,
transferGroupId:this.selectnodeid
};
const url = APIConfig.BschClassSystem.setTransferIn;
this.$api.post(url,JSON.stringify(item), resp => {
if (resp.code == 0) {
this.$Message.success('教师转入分组成功!');
this.transferModalVisible = false
this.selectedRows = []
this.loadTableData()
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
}
}
</script>
<style scoped>
.teacher-transfer-wrap {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 12px;
}
/* 顶部筛选行 */
.top-filter-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 14px;
flex-shrink: 0;
}
.filter-left {
display: flex;
align-items: center;
}
/* 表格自适应滚动容器 */
.table-scroll-wrap {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
height: 100%;
}
/* 底部栏:分页固定底部 */
.bottom-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 14px;
flex-shrink: 0;
}
.page-area {
display: flex;
align-items: center;
}
</style>
+39
View File
@@ -0,0 +1,39 @@
<template>
<div style="height:100%;">
<Christianity v-if="type==1"></Christianity>
<HigherEducation v-if="type==2"></HigherEducation>
</div>
</template>
<script>
import Christianity from "./zhuanruinfostu/Christianity";
import HigherEducation from "./zhuanruinfostu/HigherEducation";
export default {
name: 'zhuanruinfostu',
components: { Christianity,HigherEducation },
data() {
return {
type:0,
}
},
created(){
this.getType();
},
methods: {
//获取学制
getType:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getType, item, resp => {
if(resp.code==0)
{
_this.type = resp.data;
}else{
_this.type=1;
}
});
},
},
}
</script>
+325
View File
@@ -0,0 +1,325 @@
<template>
<div class="student-move-wrap">
<!-- 顶部筛选栏 -->
<div class="top-filter-bar">
<div class="search-wrap" style="width:100%;">
<span>学校:</span>
<Select v-model="filterSchool" style="width: 200px" @on-change="getselectBySchId">
<Option value="-1">全部</Option>
<Option v-for="item in SchoolList" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<span style="margin-left: 16px">年级:</span>
<Select v-model="filterGrade" style="width: 140px" @on-change="getselectBySchIdAndGradeId">
<Option value="-1">全部</Option>
<Option v-for="item in gradeselectList" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<span style="margin-left: 16px">班级:</span>
<Select v-model="filterClass" style="width: 140px" @on-change="handleFilterChange">
<Option value="-1">全部</Option>
<Option v-for="item in schclassselectlist" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<span>
</span>
<Input
placeholder="请输入学生姓名"
search
@on-search="handleSearch"
suffix="ios-search"
class="fr"
style="width: 280px"
v-model="keyword"
/>
</div>
</div>
<!-- 自适应滚动表格容器 -->
<div class="table-scroll-wrap">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
:height="useTableHeight()+32"
@on-selection-change="onSelectionChange"
></Table>
</div>
<!-- 底部栏:转入按钮 + 固定分页 -->
<div class="bottom-bar">
<Button type="default" status="danger" @click="openMoveModal">转入</Button>
<div class="page-area">
<span style="margin-right: 12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
<!-- 弹窗:转至其他组 -->
<Modal v-model="moveModalVisible" title="转至其他组" width="540">
<div style="padding: 15px 10px;">
<div style="margin-bottom: 18px;display: flex;align-items: center;">
<span style="width: 105px;">待移动用户:</span>
<Input v-model="moveStuName" disabled style="flex:1;"></Input>
</div>
<div>
<span style="width: 105px;display: inline-block;margin-bottom: 10px;color:#f56c6c;font-weight:bold;">移动至:</span>
<my-tree :treeData="targetTreeData" @on-tree-click="treeLink"></my-tree>
</div>
</div>
<div slot="footer" align="right">
<Button @click="moveModalVisible = false">取消</Button>
<Button type="primary" @click="submitMove">提交</Button>
</div>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'StudentMovePage',
components: { myTree },
data() {
return {
// 筛选条件
filterSchool: '-1',
filterGrade: '-1',
filterClass: '-1',
keyword: '',
// 分页
pageNum: 1,
pageSize: 10,
total: 0,
selectedRows: [],
SchoolList:[],
gradeselectList:[],
schclassselectlist:[],
// 表格配置式列
tableColumns: [],
tableData: [],
// 转入弹窗
moveModalVisible: false,
moveStuName: '',
selectnodeid: 0,
// 目标学校年级树形
targetTreeData: []
}
},
mixins: [self_viewport],
created() {
this.getstudenttransferOutSchSelect();
this.initTableColumn();
this.loadTableData();
},
methods: {
//获取学校下拉
getstudenttransferOutSchSelect:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.studenttransferOutSchSelect, item, resp => {
if(resp.code==0)
{
_this.SchoolList = resp.data;
}else{
_this.SchoolList=[];
}
});
},
//获取年级
getselectBySchId:function(){
const _this = this;
var item={
schId:_this.filterSchool
};
_this.$api.get(APIConfig.BschClassSystem.getselectBySchId, item, resp => {
if(resp.code==0)
{
_this.gradeselectList = resp.data;
}else{
_this.gradeselectList=[];
}
});
},
//获取班级下拉
getselectBySchIdAndGradeId:function(){
const _this = this;
var item={
schId:_this.filterSchool,
gradeId:_this.filterGrade
};
_this.$api.get(APIConfig.BschClassSystem.getselectBySchIdAndGradeId, item, resp => {
if(resp.code==0)
{
this.schclassselectlist=resp.data;
}
});
},
// 初始化表格列
initTableColumn() {
this.tableColumns = [
{ type: 'selection', width: 55, align: 'center' },
{ title: '序号', type: 'index', align: 'center', width: 70 },
{ title: '学校', key: 'schName', align: 'center' },
{ title: '年级', key: 'gradeName', align: 'center', width: 110 },
{ title: '班级', key: 'className', align: 'center', width: 90 },
{ title: '姓名', key: 'name', align: 'center' },
{ title: '身份号', key: 'idCard', align: 'center' },
]
},
// 多选勾选回调
onSelectionChange(rows) {
this.selectedRows = rows
},
// 加载列表数据
loadTableData() {
const _this = this;
var item = {
schId:_this.filterSchool=='-1'?'':_this.filterSchool,
gradeId:_this.filterGrade=='-1'?'':_this.filterGrade,
classId:_this.filterClass=='-1'?'':_this.filterClass,
keyword:_this.keyword,
page:_this.pageNum-1,
size:_this.pageSize
};
_this.$api.get(APIConfig.BschClassSystem.getstudentsearchTransferOut, item, resp => {
if (resp.code == 0) {
_this.tableData = resp.data.content;
_this.total=resp.data.total;
} else {
_this.tableData = [];
}
});
},
// 下拉筛选切换
handleFilterChange() {
this.pageNum = 1
this.loadTableData()
},
// 关键词搜索
handleSearch() {
this.pageNum = 1
this.loadTableData()
},
// 分页切换
pageChange(p) {
this.pageNum = p
this.loadTableData()
},
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadTableData()
},
// 打开转入弹窗
openMoveModal() {
if (!this.selectedRows.length) {
return this.$Message.warning('请先勾选需要转入的学生')
}
this.moveStuName = this.selectedRows.map(a=>a.name).join(',');
this.moveModalVisible = true;
this.getClassTree();
},
//获取班级树
getClassTree:function(){
const _this = this;
var item={};
_this.$api.get(APIConfig.BschClassSystem.getClassTree, item, resp => {
if(resp.code==0)
{
this.targetTreeData=resp.data;
}
});
},
// 树形选中切换
treeLink(treeNode) {
this.selectnodeid=treeNode.id;
},
// 提交迁移班级
submitMove() {
if (!this.selectnodeid) {
return this.$Message.warning('请选择目标年级节点')
}
const teacherIdArr = this.selectedRows.map(row => row.id);
let item={
stuIds:teacherIdArr,
transferClassId:this.selectnodeid
};
const url = APIConfig.BschClassSystem.studentsetTransferIn;
this.$api.post(url,JSON.stringify(item), resp => {
if (resp.code == 0) {
this.$Message.success('学生转入分组成功!');
this.moveModalVisible = false
this.selectedRows = []
this.loadTableData()
} else if (resp.code == 4) {
this.$Message.info(resp.msg);
}
});
}
}
}
</script>
<style scoped>
.student-move-wrap {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 12px;
}
/* 顶部筛选栏 */
.top-filter-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 14px;
flex-shrink: 0;
}
.filter-left {
display: flex;
align-items: center;
}
/* 表格自适应滚动容器 */
.table-scroll-wrap {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
height: 100%;
}
/* 底部栏:分页固定底部 */
.bottom-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 14px;
flex-shrink: 0;
}
.page-area {
display: flex;
align-items: center;
}
</style>
@@ -0,0 +1,285 @@
<template>
<div class="student-move-wrap">
<!-- 顶部筛选栏 -->
<div class="top-filter-bar">
<div class="search-wrap" style="width:100%;">
<span>学校:</span>
<Select v-model="filterSchool" style="width: 200px" @on-change="handleFilterChange">
<Option value="">全部</Option>
<Option value="上海市大宁国际小学">上海市大宁国际小学</Option>
<Option value="上海市浦东新区崂山小学 (陆家嘴校区)">上海市浦东新区崂山小学 (陆家嘴校区)</Option>
<Option value="上海市西延安中学">上海市西延安中学</Option>
</Select>
<span style="margin-left: 16px">年级:</span>
<Select v-model="filterGrade" style="width: 140px" @on-change="handleFilterChange">
<Option value="">全部</Option>
<Option value="五年级">五年级</Option>
<Option value="初三">初三</Option>
</Select>
<span style="margin-left: 16px">班级:</span>
<Select v-model="filterClass" style="width: 140px" @on-change="handleFilterChange">
<Option value="">全部</Option>
<Option value="1班">1班</Option>
<Option value="2班">2班</Option>
<Option value="4班">4班</Option>
<Option value="5班">5班</option>
<Option value="8班">8班</Option>
</Select>
<span>
</span>
<Input
placeholder="请输入教师姓名"
search
@on-search="handleSearch"
suffix="ios-search"
class="fr"
style="width: 280px"
v-model="searchName"
/>
</div>
</div>
<!-- 自适应滚动表格容器 -->
<div class="table-scroll-wrap">
<Table
border
stripe
:columns="tableColumns"
:data="tableData"
:height="useTableHeight()+32"
@on-selection-change="onSelectionChange"
></Table>
</div>
<!-- 底部栏:转入按钮 + 固定分页 -->
<div class="bottom-bar">
<Button type="default" status="danger" @click="openMoveModal">转入</Button>
<div class="page-area">
<span style="margin-right: 12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40]"
show-sizer
@on-change="pageChange"
@on-page-size-change="sizeChange"
></Page>
</div>
</div>
<!-- 弹窗:转至其他组 -->
<Modal v-model="moveModalVisible" title="转至其他组" width="540">
<div style="padding: 15px 10px;">
<div style="margin-bottom: 18px;display: flex;align-items: center;">
<span style="width: 105px;">待移动用户:</span>
<Input v-model="moveStuName" disabled style="flex:1;"></Input>
</div>
<div>
<span style="width: 105px;display: inline-block;margin-bottom: 10px;color:#f56c6c;font-weight:bold;">移动至:</span>
<Tree
:data="targetTreeData"
ref="targetTree"
single
:selected.sync="selectedTargetNode"
style="border:1px solid #e5e7eb;max-height:320px;overflow-y:auto;padding:8px;"
></Tree>
</div>
</div>
<div slot="footer" align="right">
<Button @click="moveModalVisible = false">取消</Button>
<Button type="primary" @click="submitMove">提交</Button>
</div>
</Modal>
</div>
</template>
<script>
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'StudentMovePage',
data() {
return {
// 筛选条件
filterSchool: '',
filterGrade: '',
filterClass: '',
keyword: '',
// 分页
pageNum: 1,
pageSize: 10,
total: 225,
selectedRows: [],
// 表格配置式列
tableColumns: [],
tableData: [
{ id: 1, serial: 1, school: '上海市大宁国际小学', grade: '五年级', className: '1班', name: '万思娴', idCard: '' },
{ id: 2, serial: 2, school: '上海市大宁国际小学', grade: '五年级', className: '4班', name: '姜涵越', idCard: '' },
{ id: 3, serial: 3, school: '上海市大宁国际小学', grade: '五年级', className: '5班', name: '胡又溪', idCard: '' },
{ id: 4, serial: 4, school: '上海市大宁国际小学', grade: '五年级', className: '8班', name: '刘语桐', idCard: '' },
{ id: 5, serial: 5, school: '上海市浦东新区崂山小学 (陆家嘴校区)', grade: '五年级', className: '1班', name: '周辛玥', idCard: '' },
{ id: 6, serial: 6, school: '上海市浦东新区崂山小学 (陆家嘴校区)', grade: '五年级', className: '2班', name: '胡羽洁', idCard: '' },
{ id: 7, serial: 7, school: '上海市西延安中学', grade: '初三', className: '2班', name: '吴弈辰', idCard: '' },
{ id: 8, serial: 8, school: '上海市西延安中学', grade: '初三', className: '4班', name: '丁昉', idCard: '' },
],
// 转入弹窗
moveModalVisible: false,
moveStuName: '',
selectedTargetNode: null,
// 目标学校年级树形
targetTreeData: [
{
title: '上海市进才中学北校',
expand: true,
children: [
{ title: '预备' },
{ title: '初一' },
{ title: '初二' },
{ title: '初三' },
]
}
]
}
},
mixins: [self_viewport],
created() {
this.initTableColumn()
this.loadTableData()
},
methods: {
// 初始化表格列
initTableColumn() {
this.tableColumns = [
{ type: 'selection', width: 55, align: 'center' },
{ title: '序号', key: 'serial', align: 'center', width: 70 },
{ title: '学校', key: 'school', align: 'center' },
{ title: '年级', key: 'grade', align: 'center', width: 110 },
{ title: '班级', key: 'className', align: 'center', width: 90 },
{ title: '姓名', key: 'name', align: 'center' },
{ title: '身份号', key: 'idCard', align: 'center' },
]
},
// 多选勾选回调
onSelectionChange(rows) {
this.selectedRows = rows
},
// 加载列表数据
loadTableData() {
console.log('筛选参数:', this.filterSchool, this.filterGrade, this.filterClass, this.keyword, this.pageNum, this.pageSize)
/* 后端接口示例
this.$axios.get('/api/student/move/list', {
params: {
school: this.filterSchool,
grade: this.filterGrade,
className: this.filterClass,
keyword: this.keyword,
pageNum: this.pageNum,
pageSize: this.pageSize
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
*/
},
// 下拉筛选切换
handleFilterChange() {
this.pageNum = 1
this.loadTableData()
},
// 关键词搜索
handleSearch() {
this.pageNum = 1
this.loadTableData()
},
// 分页切换
pageChange(p) {
this.pageNum = p
this.loadTableData()
},
sizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.loadTableData()
},
// 打开转入弹窗
openMoveModal() {
if (!this.selectedRows.length) {
return this.$Message.warning('请先勾选需要转入的学生')
}
// 单选模式:取第一个选中学生
const row = this.selectedRows[0]
this.moveStuName = row.name
this.selectedTargetNode = null
this.moveModalVisible = true
},
// 提交迁移班级
submitMove() {
if (!this.selectedTargetNode) {
return this.$Message.warning('请选择目标年级节点')
}
this.$Message.success(`已将【${this.moveStuName}】迁移至 ${this.selectedTargetNode.title}`)
this.moveModalVisible = false
this.selectedRows = []
this.loadTableData()
}
}
}
</script>
<style scoped>
.student-move-wrap {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 12px;
}
/* 顶部筛选栏 */
.top-filter-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 14px;
flex-shrink: 0;
}
.filter-left {
display: flex;
align-items: center;
}
/* 表格自适应滚动容器 */
.table-scroll-wrap {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
height: 100%;
}
/* 底部栏:分页固定底部 */
.bottom-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 14px;
flex-shrink: 0;
}
.page-area {
display: flex;
align-items: center;
}
</style>