feat: 迁移 Vue3 + Vite + view-ui-plus(对齐 NoticeFront 框架)

- 构建:Vue CLI 4 → Vite 5,入口迁至根 index.html(含 env-config + 主题通道 loader)
- 技术栈:Vue 2.6 → Vue 3.4,view-design → view-ui-plus 1.3.24,vue-router 3 → 4(hash)
- 壳层重写:App.vue / components/layout.vue 对齐 Notice 骨架(logo-badge、icon-* 通道图标、resolveMenuPage、crumb-home 基线)
- 机械转换:具名 slot → template #x、.sync → v-model:、@click.native、$set、beforeDestroy、/deep/ → :deep()
- render:h('Button')/h('Input') → 真组件 + 扁平 props/onClick
- 组件适配:vuedraggable 4 的 #item 插槽;vue-treeselect → @zanmato/vue3-treeselect;Page 分页改 v-model + pageSize 显式回写
- 清理:删除 Vue2 死代码(layout_old/bak/child、App.Net、codeModal、未引用组件)
- 修复:深链接首次加载被重定向到首个模块(router.isReady)

验证:vite build 通过;headless CDP 全 15 路由无异常;教师列表 2 行渲染、操作列按钮点击弹窗正常。
This commit is contained in:
lijing
2026-10-20 15:16:52 +08:00
parent 0564f5f89d
commit 7e49587c10
73 changed files with 6083 additions and 33190 deletions
+10
View File
@@ -0,0 +1,10 @@
# 开发环境(vite dev --mode dev 加载;build 产物不读任何 .env.*)
# 主题通道:index.html 的 loader 与 vite.config.ts 的 /static 代理已经就位;
# 联调本地通道源时用环境变量覆盖代理目标:
# YH_THEME_PROXY_TARGET=http://localhost:9527 npm run dev
#
# serviceHost 留空 → API 走同源相对 /api/*,由 vite proxy 转发到本地后端(规避 CAS 不回 CORS 头)
VITE_BASE_HOST=http://base.yunhaixingtong.com
VITE_SSO_HOST=http://sso.yunhaixingtong.com
VITE_SERVICE_HOST=
VITE_IS_MOCK=false
-11
View File
@@ -1,11 +0,0 @@
# 开发环境(Vue CLI 默认 mode=development 时自动加载本文件)
#
# 主题通道接入说明(《云海子应用前端接入规范》§2.2 第一步):
# 1. public/index.html 已插入 A 类 loader,主题/皮肤由通道 /static/js/yh-theme-init.js 下发;
# 2. 本地 dev 的 /static 请求由 vue.config.js 的 devServer.proxy 兜到主题通道;
# 3. 默认通道地址 http://base.yunhaixingtong.com,联调本地通道源时用环境变量覆盖:
# YH_THEME_PROXY_TARGET=http://localhost:9527 npm run dev
# 4. 跨源直连(VITE_THEME_HOST)是 Vite 工程的机制,本项目为 Vue CLI(webpack),
# 内联脚本里的 %VITE_THEME_HOST% 会被判为占位符并置空,统一走上面的 dev 代理。
#
# 说明:本文件不含敏感配置,仅作接入连法备忘。
+5
View File
@@ -0,0 +1,5 @@
# 本地纯前端调试模式:仅 vite dev --mode mock 加载
VITE_BASE_HOST=
VITE_SSO_HOST=
VITE_SERVICE_HOST=http://localhost:8101
VITE_IS_MOCK=true
-11
View File
@@ -1,11 +0,0 @@
module.exports = {
presets: [
[
"@vue/app",
{
polyfills: ["es6.promise", "es6.symbol", "es6.array.iterator", "es6.object.assign"],
useBuiltIns: "entry"
}
]
]
};
+54
View File
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta content="telephone=no,email=no" name="format-detection" />
<!-- 运行时配置引导:在任何应用模块执行前同步拉取 /<contextPath>/api/env-config
(后端免登录端点,projman 配置管理在线维护),替代生产构建环境变量。
本应用产物部署于 /{contextPath}/front/,target 取 URL 第 2 段。 -->
<script>
(function () {
try {
var seg = location.pathname.split("/").filter(Boolean);
if (seg.length < 2) return; // 本地开发(根路径):走 .env.dev / .env.mock
var xhr = new XMLHttpRequest();
xhr.open("GET", "/" + seg[0] + "/api/env-config?target=" + encodeURIComponent(seg[1]), false);
xhr.send(null);
if (xhr.status === 200) {
var conf = JSON.parse(xhr.responseText);
if (conf && typeof conf === "object") window.__APP_CONFIG__ = conf;
}
} catch (e) {
/* 拉取失败保持未设置,走同域推导兜底 */
}
})();
</script>
<!-- 主题通道引导(A 类免插件接入,YschTheme《管理端主题远程化与公共静态通道方案》§11):
唯一接入点。FOUC 门、manifest 预取、主题 <link> 追加与排序(晚于 viewuiplus.css)全由
通道 yh-theme-init.js 承担;主题升级走 /static 通道,本应用无需重新构建。
origin 零写死,优先级四档:__APP_CONFIG__.staticHost > __APP_CONFIG__.baseHost
> %VITE_THEME_HOST%(仅 dev) > 同源相对 "/static/…"(生产常态,本地 dev 走 vite 代理)。 -->
<script>
(function () {
var c = window.__APP_CONFIG__ || {};
var themeHost = "%VITE_THEME_HOST%";
if (themeHost.charAt(0) === "%") themeHost = "";
var host = String(c.staticHost || c.baseHost || themeHost).replace(/\/+$/, "");
document.write('<script src="' + host + '/static/js/yh-theme-init.js"><\/script>');
})();
</script>
<title><%- title %></title>
<link rel="stylesheet" href="./libs/ztree/zTreeStyle.css" />
<script src="./libs/jquery.min.js"></script>
<script src="./libs/ztree/jquery.ztree.all.min.js"></script>
<%- injectScript %>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+4575 -28920
View File
File diff suppressed because it is too large Load Diff
+32 -27
View File
@@ -1,29 +1,34 @@
{
"name": "bschstandard",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"@riophae/vue-treeselect": "^0.4.0",
"core-js": "^3.6.5",
"node-sass": "latest",
"sortablejs": "^1.15.7",
"view-design": "^4.3.2",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuedraggable": "^2.24.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"sass": "^1.56.1",
"node-sass": "^6.0.1",
"sass-loader": "^10.2.0",
"vue-template-compiler": "^2.6.11"
}
"name": "bschclass",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --mode dev",
"dev:mock": "vite --mode mock",
"build": "vite build --mode base",
"preview": "vite preview"
},
"dependencies": {
"@zanmato/vue3-treeselect": "^0.4.4",
"alova": "3.4.0",
"axios": "0.24.0",
"highlight.js": "11.9.0",
"mitt": "3.0.1",
"pinia": "2.1.4",
"sortablejs": "1.15.7",
"view-ui-plus": "1.3.24",
"vue": "3.4.19",
"vue-router": "4.3.0",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@types/node": "20.4.4",
"@vitejs/plugin-vue": "5.0.4",
"sass": "1.64.1",
"typescript": "5.2.2",
"vite": "5.1.4",
"vite-plugin-html": "3.2.2",
"vue-tsc": "1.8.27"
}
}
-44
View File
@@ -1,44 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta content="telephone=no,email=no" name="format-detection" />
<meta http-equiv="Access-Control-Allow-Origin" content="*" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= htmlWebpackPlugin.options.title %></title>
<script src="./config/index.js"></script>
<link rel="stylesheet" href="./libs/base.layout.css" />
<link rel="stylesheet" href="./libs/default-theme.css" />
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_4962449_bwzjcx59wz4.css" />
<!-- 主题通道引导(A 类免插件接入,见 YschTheme《管理端主题远程化与公共静态通道方案》§11):
唯一接入点。不装 npm 依赖、不改构建配置;FOUC 门、manifest 预取、主题 <link> 追加与
排序(晚于 viewuiplus.css)全由通道 yh-theme-init.js 承担,主题升级走 /static 通道,
本应用无需重新构建。
origin 零写死,优先级四档:__APP_CONFIG__.staticHost(专用通道覆盖)
> __APP_CONFIG__.baseHost(env-config 在线维护,换域名改配置即生效)
> %VITE_THEME_HOST%(仅 dev 注入的跨源直连,需通道已开 CORS)
> 同源相对 "/static/…"(生产常态:本应用部署在 base 域 /<app>/front/,与通道同源;
本地 dev 由 vue.config.js 的 /static 代理兜到通道)。
document.write 是有意为之:保证 parser-blocking,让档位类与 FOUC 门先于首帧;
build 不读 .env,占位符留字面量 → 判首字符 % 置空 → 产物零环境值烧入。 -->
<script>
(function () {
var c = window.__APP_CONFIG__ || {};
var themeHost = "%VITE_THEME_HOST%";
if (themeHost.charAt(0) === "%") themeHost = "";
var host = String(c.staticHost || c.baseHost || themeHost).replace(/\/+$/, "");
document.write('<script src="' + host + '/static/js/yh-theme-init.js"><\/script>');
})();
</script>
</head>
<body>
<div id="app"></div>
<script src="./libs/vue.js"></script>
<script src="./libs/jquery.min.js"></script>
<script src="./libs/axios.js"></script>
<script src="./libs/clipboard.min.js"></script>
</body>
</html>
-211
View File
@@ -1,211 +0,0 @@
<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>
+145 -232
View File
@@ -1,246 +1,159 @@
<script setup lang="ts">
import { reactive, ref, onMounted, provide, nextTick } from "vue";
import { useRouter } from "vue-router";
import { Message } from "view-ui-plus";
import layout from "@/components/layout.vue";
import conf from "@/assets/config/host.config";
import api from "@/api";
import { resolveMenuPage } from "@/utils/menuRoute";
const router = useRouter();
const isLogin = ref(false);
const isNoAuthority = ref(false);
const isRouterAlive = ref(true);
const layoutData = reactive<any>({
appName: "学校管理",
baseHost: conf.baseHost,
ssoHost: conf.ssoHost,
appList: [],
userInfo: null,
modList: [],
isAlone: false,
});
const reload = () => {
isRouterAlive.value = false;
nextTick(() => (isRouterAlive.value = true));
};
provide("reload", reload);
const getUserInfo = () =>
new Promise<any>((resolve, reject) => {
api.get(APIConfig.Base.GetUserInfo, null, (resp: any) => resolve(resp.data), reject);
});
const getUserMod = () =>
new Promise<any>((resolve, reject) => {
api.get(APIConfig.Base.GetUserMod, null, (resp: any) => resolve(resp.data), reject);
});
/** 平台模块 → 侧边栏菜单项:icon 取路由 meta(icon-* 通道图标名) */
const layoutInit = (info: any, mod: any[]) => {
const routes: any[] = (router.options.routes as any[]) || [];
const arr = (mod || [])
.map((element: any) => {
const hit = routes.find(
x => x.meta && x.meta.modId !== undefined && x.meta.modId == element.customId
);
return {
id: element.id,
name: element.name,
modId: element.customId,
page: element.path,
children: element.children || [],
icon: (hit && hit.meta && hit.meta.icon) || "",
};
})
.sort((a: any, b: any) => a.modId - b.modId);
layoutData.modList = arr;
const hasChildren = arr.some((m: any) => m.children && m.children.length > 0);
layoutData.isAlone = arr.length <= 1 && !hasChildren;
const { id, name, profilePicture } = info || {};
layoutData.userInfo = { id, name: name || "管理员", avatar: profilePicture || "" };
layoutData.appList = [];
if (arr.length === 0) {
layoutData.isAlone = true;
isNoAuthority.value = true;
}
};
onMounted(async () => {
try {
const info = await getUserInfo();
localStorage.setItem(
"currentRole",
encodeURI(
JSON.stringify({
childId: 0,
roleId: info.roleIds[0],
schId: info.school ? info.school.id : 0,
userId: info.id,
})
)
);
let mod = await getUserMod();
if (info.type == 1) mod = mod.filter((a: any) => a.customId < 4);
layoutInit(info, mod);
isLogin.value = true;
// 等首次导航解析完成再判断,否则深链接(如 /#/teacherManage)会被误判成 "/"
// 而重定向到第一个模块(Vue Router 初始导航是异步的)
await router.isReady();
// 登录后跳转第一个可用模块
if (router.currentRoute.value.path === "/") {
const firstModule = (mod || [])[0];
const firstRaw = firstModule?.children?.length ? firstModule.children[0] : firstModule;
const first = resolveMenuPage(
firstRaw?.page,
firstRaw?.customId ?? firstRaw?.id,
firstRaw?.name
);
if (!first.external && first.path && first.path !== "/") {
router.replace(first.path);
}
}
} catch (error) {
console.error(error);
Message.error("载入用户信息失败!");
layoutData.userInfo = { name: "管理员", avatar: "" };
layoutData.modList = [];
layoutData.isAlone = true;
isNoAuthority.value = true;
isLogin.value = true;
}
});
</script>
<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">
<layout v-if="isLogin" :layoutData="layoutData" />
<div v-if="isLogin" class="base-wrap-main" :class="{ 'alone-page': layoutData.isAlone }">
<div class="main-wrapper">
<div v-if="isNoAuthority" class="no-authority-wrap">
<h3>暂无权限</h3>
<h6>如需查看,请联络管理员为您分配权限</h6>
</div>
<router-view v-else-if="isRouterAlive" :key="$route.fullPath" />
</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);
localStorage.setItem("currentRole", encodeURI(JSON.stringify({ childId:0, roleId:info.roleIds[0], schId:info.school?info.school.id:0, userId:info.id })));
var mod = await this.getUserMod();
if(info.type==1){
mod=mod.filter(a=>a.customId<4)
}
this.layoutInit(info, mod, [], 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 => {
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);
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.customId
);
let icon = iconIndex > -1 ? "iconfont " + routes[iconIndex].meta.icon : ""; //icon不传则为默认
arr.push({
id: element.id,
name: element.name,
modId: element.customId,
page: element.path,
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;
html,
body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
/* 开始过渡阶段,动画出去阶段 */
.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%;
}
/* 页面级滚动容器:main-wrapper 只有 min-height 时内容超一屏会被 body 的 overflow:hidden 裁掉 */
.base-wrap-main .main-wrapper {
height: 100%;
overflow-y: auto;
}
/* 表格操作列(render 出的 Tooltip 包裹层)横排且随单元格居中 */
.ivu-table-cell > .ivu-tooltip {
display: inline-block;
vertical-align: middle;
margin: 0 2px;
}
</style>
+4 -3
View File
@@ -1,13 +1,14 @@
// 配置API接口地址
// var root = '/api/v1'
// var root = '/api/debugapi'
var root = "/api";
// 置空:APIConfig 已带 /api 前缀(生产为绝对地址,dev 为同源相对),避免 baseURL 二次拼接
var root = "";
// var root = ''
// 引用axios
var axios = require("axios");
import axios from "axios";
// import Notice from 'iview/src/components/notice';
import { Message } from "view-design";
import { Message } from "view-ui-plus";
// 自定义判断元素类型JS
function toType(obj) {
-180
View File
@@ -1,180 +0,0 @@
// 配置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);
},
};
+72
View File
@@ -0,0 +1,72 @@
/**
* 宿主配置(对齐 NoticeFront @/assets/config/host.config)。
* 优先级:__APP_CONFIG__(线上运行时下发)> VITE_*(仅本地 dev)> 同域推导兜底。
*/
const env = import.meta.env;
const seg = window.location.pathname.split("/").filter(Boolean);
const origin = window.location.origin;
// 部署在 /{contextPath}/front/ → 上下文段即首段;本地 dev 根路径段数不足 → 留空走同源相对
const ctx = seg.length >= 2 ? `/${seg[0]}` : "";
export interface BaseConf {
baseHost: string;
ssoHost: string;
serviceHost: string;
serviceUrl: string;
fileHost: string;
flowHost: string;
isLayoutOld: boolean;
isMock: boolean;
isDebug: boolean;
}
const conf: BaseConf = {
baseHost: origin,
ssoHost: "",
serviceHost: ctx ? origin + ctx : "",
serviceUrl: ctx ? origin + ctx : "",
fileHost: ctx ? origin + ctx : "",
flowHost: "",
isLayoutOld: false,
isMock: false,
isDebug: env.DEV,
};
const applyConf = (target: any, source: any) => {
if (!source || typeof source !== "object") return;
Object.keys(source).forEach(key => {
if (source[key] !== undefined && source[key] !== null && source[key] !== "") {
target[key] = source[key];
}
});
};
// 本地开发/演示(build 产物中 VITE_* 恒 undefined → 自动跳过)
applyConf(conf, {
baseHost: env.VITE_BASE_HOST,
ssoHost: env.VITE_SSO_HOST,
serviceHost: env.VITE_IS_MOCK === "true" ? "" : env.VITE_SERVICE_HOST,
serviceUrl: env.VITE_IS_MOCK === "true" ? "" : env.VITE_SERVICE_HOST,
fileHost: env.VITE_IS_MOCK === "true" ? "" : env.VITE_SERVICE_HOST,
isMock: env.VITE_IS_MOCK === "true",
});
// 线上运行时配置(最高优先)
try {
const runtime: any = (window as any).__APP_CONFIG__;
if (runtime && typeof runtime === "object") {
applyConf(conf, {
baseHost: runtime.baseHost,
ssoHost: runtime.ssoHost,
serviceHost: runtime.serviceHost,
serviceUrl: runtime.serviceHost,
fileHost: runtime.serviceHost,
flowHost: runtime.flowHost,
});
}
} catch {
/* 无运行时配置时沿用兜底 */
}
export default conf;
+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<path fill="#4aaff0" d="M512,585 C415,455 330,398 215,395 C185,395 160,415 160,450 L160,770 C160,815 190,838 240,845 C340,856 430,872 512,888 C594,872 684,856 784,845 C834,838 864,815 864,770 L864,450 C864,415 839,395 809,395 C694,398 609,455 512,585 Z"/>
<path fill="#4aaff0" d="M218,312 C335,292 462,352 514,462 C452,404 334,354 218,378 Z"/>
<circle fill="#4aaff0" cx="218" cy="345" r="33"/>
<path fill="#80d976" d="M508,462 C455,310 525,145 700,105 C758,250 668,425 508,462 Z"/>
<path fill="none" stroke="#4aaff0" stroke-width="44" stroke-linecap="round" d="M210,935 Q285.5,881 361,935 T512,935 T663,935 T814,935"/>
</svg>

After

Width:  |  Height:  |  Size: 701 B

-473
View File
@@ -1,473 +0,0 @@
$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
@@ -1,505 +0,0 @@
<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
@@ -1,80 +0,0 @@
<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
@@ -1,54 +0,0 @@
<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
@@ -1,24 +0,0 @@
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
@@ -1,240 +0,0 @@
<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>
+291
View File
@@ -0,0 +1,291 @@
<script setup lang="ts">
import { ref, computed, onBeforeMount, onBeforeUnmount, onMounted, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "view-ui-plus";
import { resolveMenuPage } from "@/utils/menuRoute";
import defaultLogo from "@/assets/image/yunhai-logo.svg";
const props = defineProps<{ layoutData: any }>();
const route = useRoute();
const router = useRouter();
const currPath = ref("");
const navPath = ref<any[]>([]);
const baseHost = computed(() => props.layoutData?.baseHost || "");
const ssoHost = computed(() => props.layoutData?.ssoHost || "");
const appName = computed(() => props.layoutData?.appName || "学校管理");
const userInfo = computed(() => props.layoutData?.userInfo || { name: "管理员", avatar: "" });
const isAlone = computed(() => props.layoutData?.isAlone ?? false);
const modList = computed<any[]>(() => props.layoutData?.modList || []);
const pathNavStyle = computed(() => (isAlone.value ? { left: "0", width: "100%" } : null));
/* 顶栏 Logo:平台键 yh-logo-url(yh-theme-init 外观补齐段写入)优先,本仓资产兜底 */
const logoUrl = ref<string>(defaultLogo);
const readLogoUrl = () => {
let v: string | null = null;
try {
v = localStorage.getItem("yh-logo-url");
} catch {
v = null;
}
logoUrl.value = v || defaultLogo;
};
const onLogoError = () => {
if (logoUrl.value !== defaultLogo) logoUrl.value = defaultLogo;
};
onMounted(() => {
readLogoUrl();
document.addEventListener("yh-appearance-updated", readLogoUrl);
});
onBeforeUnmount(() => document.removeEventListener("yh-appearance-updated", readLogoUrl));
/* 头像:URL 为空或加载失败时回落首字 */
const avatarFailed = ref(false);
watch(
() => userInfo.value?.avatar,
() => (avatarFailed.value = false)
);
const avatarText = computed(() => String(userInfo.value?.name || "?").trim().slice(0, 1));
const flaten = (tree: any[], pid = 0): any[] => {
const arr: any[] = [];
(tree || []).forEach((element: any) => {
const { id, name, modId } = element;
const resolved = resolveMenuPage(element.page, element.modId ?? element.id, element.name);
arr.push({ id, name, modId, page: resolved.path, external: resolved.external, pid });
if (element.children && element.children.length) {
arr.push(...flaten(element.children, id));
}
});
return arr;
};
const menuArr = computed(() => flaten(modList.value));
const routeChange = (to: any) => {
const meta: any = to.meta || {};
const { modId, title, parentId } = meta;
const navArr: any[] = [];
const find: any =
menuArr.value.find((x: any) => x.page && x.page === to.path) ||
menuArr.value.find((x: any) => x.modId === modId);
if (find) {
currPath.value = find.page || "";
if (find.pid) {
const pfind = menuArr.value.find((x: any) => x.id === find.pid);
if (pfind) navArr.push({ name: pfind.name });
}
navArr.push({ name: find.name || title, page: find.page });
navPath.value = navArr;
return;
}
if (parentId) {
const parent = menuArr.value.find((x: any) => x.modId === parentId);
if (parent) {
currPath.value = parent.page || "";
if (parent.pid) {
const gp = menuArr.value.find((x: any) => x.id === parent.pid);
if (gp) navArr.push({ name: gp.name });
}
navArr.push({ name: parent.name, page: parent.page });
navArr.push({ name: title });
navPath.value = navArr;
return;
}
}
const r = router.getRoutes().find((rt: any) => (rt.meta as any)?.modId === modId);
if (r) {
currPath.value = r.path;
navPath.value = [{ name: (r.meta as any).title, page: r.path }];
return;
}
currPath.value = "";
navPath.value = [];
};
onBeforeMount(() => routeChange(route));
watch(
() => route.fullPath,
() => routeChange(route)
);
const resolveItemPage = (item: any) =>
resolveMenuPage(item?.page, item?.modId ?? item?.id, item?.name);
const pageOf = (item: any): string => resolveItemPage(item).path;
const linkMenu = (item: any) => {
const { path: page, external } = resolveItemPage(item);
if (!page) {
Message.warning(`菜单「${item?.name || "未命名"}」未配置有效页面路径,请检查基础平台模块设置`);
return;
}
if (external) {
window.location.href = page;
return;
}
if (page === route.path) {
router.go(0);
} else {
router.push(page);
}
};
const linkHomePage = () => {
if (baseHost.value) {
window.location.href = baseHost.value + "/userhome/front/index.html";
} else {
window.location.href = "/";
}
};
const linkUserInfo = () => {
window.location.href = baseHost.value + "/userhome/front/index.html#/settings?pane=profile";
};
const logout = () => {
let url = window.location.href;
url = "/logout?service=" + url;
window.location.href = ssoHost.value + url;
};
const childChange = (index: number) => {
const arr = modList.value || [];
if (arr[index]) arr[index].childState = !arr[index].childState;
};
/* 菜单图标:路由 meta 的 icon-* 通道图标名;未配置统一兜底 icon-list */
const getMenuIcon = (item?: any): string => item?.icon || "icon-list";
</script>
<template>
<div class="layout-wrap">
<header class="header-container fix">
<span class="logo-badge fl"><img :src="logoUrl" alt="云海" @error="onLogoError" /></span>
<div class="delimiter fl align-middle"></div>
<h4 class="fl align-middle">{{ appName }}</h4>
<div class="school-bg"></div>
<div class="header-aside fr">
<span class="avatar-wrap fl align-middle" @click="linkUserInfo">
<img
v-if="userInfo.avatar && !avatarFailed"
:src="userInfo.avatar"
alt=""
@error="avatarFailed = true"
/>
<span v-else class="avatar-text">{{ avatarText }}</span>
</span>
<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="退出登陆">
<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 modList" :key="'menu_li_' + index">
<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>
<i :class="['iconfont', getMenuIcon(item)]"></i>
{{ item.name }}
</a>
</li>
<transition name="fade_child_ul">
<div class="child-item-wrap" v-show="item.childState">
<li
:class="pageOf(citem) == currPath ? 'active child-item' : 'child-item'"
v-for="citem in item.children"
:key="citem.id"
>
<a href="javascript:;" @click="linkMenu(citem)">
{{ citem.name }}
</a>
</li>
</div>
</transition>
</template>
<li v-else :class="pageOf(item) == currPath ? 'active' : ''">
<a href="javascript:;" @click="linkMenu(item)">
<i :class="['iconfont', getMenuIcon(item)]"></i>
{{ item.name }}
</a>
</li>
</template>
</ul>
<div class="clear-box"></div>
</div>
<div class="path-nav" :style="pathNavStyle">
<Breadcrumb separator=">">
<a href="javascript:;" @click="linkHomePage">
<BreadcrumbItem>
<span class="crumb-home">
<i class="iconfont icon-shouye"></i>
首页
</span>
</BreadcrumbItem>
</a>
<BreadcrumbItem v-if="!isAlone" to="/">{{ appName }}</BreadcrumbItem>
<template v-for="(item, index) in navPath" :key="index">
<BreadcrumbItem v-if="index != navPath.length - 1" :to="item.page">
{{ item.name }}
</BreadcrumbItem>
<BreadcrumbItem v-else>{{ item.name }}</BreadcrumbItem>
</template>
</Breadcrumb>
</div>
</div>
</template>
<style scoped>
/* 仅覆盖必要动态样式;骨架与皮肤全部来自中央静态通道 base.layout.css */
.left-menu a .iconfont {
margin-right: 8px;
}
.header-container .logo-badge {
margin: 0 10px 0 0;
}
.header-aside .avatar-wrap {
width: 28px;
height: 28px;
margin-right: 14px;
border-radius: 50%;
overflow: hidden;
background: rgba(255, 255, 255, 0.22);
color: #fff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.header-aside .avatar-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.header-aside .avatar-text {
font-size: 13px;
font-weight: 600;
line-height: 1;
}
/* 面包屑「首页」:行内基线流 -0.125em 光学居中(接入规范 §4) */
.crumb-home .iconfont {
position: static;
top: 0;
margin-right: 4px;
vertical-align: -0.125em;
}
</style>
-64
View File
@@ -1,64 +0,0 @@
<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
@@ -1,23 +0,0 @@
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
@@ -1,95 +0,0 @@
<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>
+1 -2
View File
@@ -3,8 +3,7 @@
</template>
<script>
require("@public/libs/ztree/zTreeStyle.css");
import "@public/libs/ztree/jquery.ztree.all.min.js";
export default {
props: {
treeData: {
-112
View File
@@ -1,112 +0,0 @@
<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
@@ -1,169 +0,0 @@
<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
@@ -1,100 +0,0 @@
<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>
+3 -8
View File
@@ -48,16 +48,15 @@
</ul>
</div>
</div>
<div slot="footer">
<template #footer><div>
<i-Button type="primary" @click="submit">提交</i-Button>
<i-Button @click="modal = false" style="margin-left: 8px">取消</i-Button>
</div>
</div></template>
</Modal>
</template>
<script>
require("@public/libs/ztree/zTreeStyle.css");
import "@public/libs/ztree/jquery.ztree.all.min.js";
export default {
props: {
treeData: {
@@ -79,10 +78,6 @@ export default {
},
},
},
model: {
prop: "userids",
event: "change",
},
data() {
return {
ztreeDomId: null,
+5 -9
View File
@@ -1,12 +1,8 @@
let conf = {
baseHost: "http://base.yunhaixingtong.com",
ssoHost: "http://sso.yunhaixingtong.com",
serviceUrl: "http://base.yunhaixingtong.com/userclass",
fileHost: "http://base.yunhaixingtong.com/userclass",
isLayoutOld: false
// serviceUrl: "http://basedebug.bestsch.com/BschHome/",
};
import conf from "@/assets/config/host.config";
if (typeof window.conf !== "undefined") conf = window.conf;
// 兼容旧代码:全局 window.conf 与默认导出
if (typeof window !== "undefined") {
window.conf = conf;
}
export default conf;
-251
View File
@@ -1,251 +0,0 @@
<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
@@ -1,305 +0,0 @@
<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>
-306
View File
@@ -1,306 +0,0 @@
<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
@@ -1,62 +0,0 @@
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");
+39
View File
@@ -0,0 +1,39 @@
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import router from "@/router";
import ViewUIPlus from "view-ui-plus";
import "view-ui-plus/dist/styles/viewuiplus.css";
// 布局/主题/图标(base.layout.css + icons.css)由中央静态通道下发:
// index.html 的 yh-theme-init.js 在 DOMContentLoaded 后按 manifest 版本追加 <link>,
// 天然排在本文件组件库样式之后。
import "@/assets/styles/main.scss";
import api from "@/api";
import utils from "@/utils";
import conf from "@/assets/config/host.config";
import bus from "@/utils/bus";
import registerDirectives from "@/utils/directive";
import APIConfig from "@/config/apiConf";
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.use(ViewUIPlus);
// 兼容存量代码:全局 APIConfig / conf,以及 Vue2 时代的四个原型方法
(window as any).APIConfig = APIConfig;
(window as any).conf = conf;
app.config.globalProperties.$api = api;
app.config.globalProperties.$utils = utils;
app.config.globalProperties.$conf = conf;
app.config.globalProperties.$bus = bus;
registerDirectives(app);
app.mount("#app");
-8
View File
@@ -1,8 +0,0 @@
import codeModal from "../components/codeModal/index.js";
export default {
methods: {
showCode({ name, url, type = "vue" }) {
codeModal({ name, url, type });
}
}
};
-141
View File
@@ -1,141 +0,0 @@
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: "/studySystemChoice",
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;
+9
View File
@@ -0,0 +1,9 @@
import { createRouter, createWebHashHistory } from "vue-router";
import routes from "./routes";
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;
+138
View File
@@ -0,0 +1,138 @@
import type { RouteRecordRaw } 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");
const routes: RouteRecordRaw[] = [
{
path: "/",
name: "root",
component: { render: () => null },
},
{
path: "/studySystemChoice",
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" },
},
];
export default routes;
+16
View File
@@ -0,0 +1,16 @@
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare const APIConfig: any;
declare const $: any;
declare const jQuery: any;
interface Window {
conf: any;
APIConfig: any;
__APP_CONFIG__: any;
__YH_THEME_STATIC_BASE__?: string;
}
+18
View File
@@ -0,0 +1,18 @@
import mitt from "mitt";
/**
* 事件总线:保留 Vue2 时代的 $on / $off / $emit 调用形态,
* 业务代码(layout 的 setNavPath / setMenuPath)无需改动。
*/
const emitter = mitt();
const bus = {
$on: emitter.on,
$off: emitter.off,
$emit: emitter.emit,
on: emitter.on,
off: emitter.off,
emit: emitter.emit,
};
export default bus;
+12
View File
@@ -0,0 +1,12 @@
import hljs from "highlight.js";
/** 代码高亮指令(Vue3:mounted 替代 Vue2 的 bind/inserted) */
export default function registerDirectives(app: any) {
app.directive("highlight", {
mounted(el: HTMLElement) {
el.querySelectorAll("pre code").forEach(block => {
hljs.highlightElement(block as HTMLElement);
});
},
});
}
+3 -3
View File
@@ -43,15 +43,15 @@ export default {
const loop = function (nodes, parentNode) {
for (let node of nodes) {
// 清空所有节点选中状态
vueThis.$set(node, 'selected', false)
node.selected = false
if (node.id === targetId) {
// 目标节点设为选中
vueThis.$set(node, 'selected', true)
node.selected = true
let p = parentNode
// 逐层向上展开父节点
while (p) {
vueThis.$set(p, 'expand', true)
p.expand = true
p = p._parent
}
return true
+146
View File
@@ -0,0 +1,146 @@
import routes from "@/router/routes";
/**
* 平台模块配置 → 前端内部路由的统一解析器(对齐 NoticeFront @/utils/menuRoute)。
* 平台配置的 path 格式不可控(旧前缀 / index.html#/hash / 完整 URL / 配错),
* 侧边栏跳转与高亮、登录后默认跳转都必须先经过这里,禁止手拼路由。
*/
const ROUTE_PATHS: string[] = routes
.map((r: any) => (typeof r.redirect === "string" ? r.redirect : r.path))
.filter((p: any): p is string => !!p && !String(p).includes(":"));
const ROUTE_BY_MODID = new Map<number, string>();
for (const r of routes as any[]) {
const modId = r.meta?.modId as number | undefined;
if (!modId || !r.path || String(r.path).includes(":") || r.redirect) continue;
if (!ROUTE_BY_MODID.has(modId)) ROUTE_BY_MODID.set(modId, r.path);
}
const ROUTE_BY_TITLE = new Map<string, string>();
for (const r of routes as any[]) {
if (r.redirect || !r.path || String(r.path).includes(":")) continue;
const title = r.meta?.title as string | undefined;
if (title && !ROUTE_BY_TITLE.has(title)) ROUTE_BY_TITLE.set(title, r.path);
}
/** 平台历史模块名 → 路由(旧菜单叫法与现页面标题不一致时的对照) */
const NAME_ALIASES: Record<string, string> = {
学制设置: "/studySystemChoice",
时段管理: "/itemSet",
学校管理: "/schoolSetting",
课程管理: "/subjectManForZy",
学科设置: "/subjectSetzhi",
班级列表: "/classManage",
选修班: "/electiveClassManage",
教师列表: "/teacherManage",
学生列表: "/studentManage",
日志查询: "/dailySearch",
};
/** 本应用历史上下文前缀(/bschclass、/userclass、/BschClassSystem)→ 去掉后比对 */
const LEGACY_PREFIXES = ["/bschclass", "/userclass", "/bschclasssystem"];
const EXTERNAL_RE = /^(https?:)?\/\//i;
function extractCandidate(raw: string): string {
let p = raw.trim();
try {
p = decodeURIComponent(p);
} catch {
/* 非法编码保持原样 */
}
const hashIdx = p.indexOf("#");
if (hashIdx >= 0) {
p = p.slice(hashIdx + 1);
} else if (EXTERNAL_RE.test(p)) {
try {
p = new URL(p, "http://placeholder.local").pathname;
} catch {
/* 保持原样 */
}
}
const qIdx = p.indexOf("?");
if (qIdx >= 0) p = p.slice(0, qIdx);
p = p.replace(/index\.html?$/i, "");
p = p.replace(/\/+$/, "");
if (p && !p.startsWith("/")) p = "/" + p;
return p;
}
function matchRoute(candidate: string): string | null {
if (!candidate) return null;
const lower = candidate.toLowerCase();
const exact = ROUTE_PATHS.find((p: string) => p.toLowerCase() === lower);
if (exact) return exact;
for (const prefix of LEGACY_PREFIXES) {
if (lower.startsWith(prefix + "/")) {
const stripped = lower.slice(prefix.length);
const hit = ROUTE_PATHS.find((p: string) => p.toLowerCase() === stripped);
if (hit) return hit;
}
}
return null;
}
function matchByName(name?: string | null): string | null {
const n = String(name ?? "").trim();
if (!n) return null;
const byTitle = ROUTE_BY_TITLE.get(n);
if (byTitle) return byTitle;
const alias = NAME_ALIASES[n];
if (alias) return alias;
const hits = [...ROUTE_BY_TITLE.entries()].filter(
([title]) => title.length >= 2 && (n.includes(title) || title.includes(n))
);
return hits.length === 1 ? hits[0][1] : null;
}
function isAppEntryCandidate(candidate: string): boolean {
if (!candidate || candidate === "/") return true;
return LEGACY_PREFIXES.some(p => candidate === p);
}
export interface ResolvedMenuPage {
path: string;
external: boolean;
}
export const EMPTY_MENU_PAGE: ResolvedMenuPage = { path: "", external: false };
export function resolveMenuPage(
page?: string | null,
modId?: number | null,
name?: string | null
): ResolvedMenuPage {
const raw = String(page ?? "").trim();
const byName = (): string | null => matchByName(name);
const byModId = (): string | null => (modId != null ? ROUTE_BY_MODID.get(modId) ?? null : null);
if (raw) {
const candidate = extractCandidate(raw);
const byPath = matchRoute(candidate);
if (byPath) return { path: byPath, external: false };
if (EXTERNAL_RE.test(raw) && !raw.includes("#")) {
if (!isAppEntryCandidate(candidate)) {
return { path: raw, external: true };
}
}
const fallback = byName() ?? byModId();
if (fallback) return { path: fallback, external: false };
console.warn(
"[menuRoute] 模块路径/名称/modId 均无法匹配前端路由,请检查基础平台模块配置:",
raw,
"modId =", modId,
"name =", name
);
return EMPTY_MENU_PAGE;
}
const fallback = byName() ?? byModId();
if (fallback) return { path: fallback, external: false };
console.warn("[menuRoute] 模块未配置路径且名称/modId 无法识别:", name, "modId =", modId);
return EMPTY_MENU_PAGE;
}
+1 -1
View File
@@ -28,7 +28,7 @@
<div class="pagination-wrap">
<span class="total-text">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10, 20, 50, 100]"
+25 -32
View File
@@ -15,10 +15,10 @@
添加班级
<Icon type="ios-arrow-down"></Icon>
</Button>
<DropdownMenu slot="list">
<template #list><DropdownMenu>
<DropdownItem name="openAddSingleClass">添加单个班级</DropdownItem>
<DropdownItem name="openMultiNumModal">添加多个班级</DropdownItem>
</DropdownMenu>
</DropdownMenu></template>
</Dropdown>
<Button type="success" style="margin-left:10px" @click="openImportModal">班级信息导入</Button>
</div>
@@ -42,10 +42,10 @@
<Input v-model.number="addClassCount" type="number" min="1" />
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="multiClassModal = false">取消</Button>
<Button type="primary" @click="submitBatchAdd">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗2:班级信息导入 -->
@@ -61,9 +61,9 @@
<Button type="primary" @click="confirmImport">确定</Button>
</div> -->
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<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>
<Input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></Input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
@@ -73,7 +73,7 @@
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
<Button icon="md-add"></Button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.fileName" style="margin-bottom:10px">
@@ -84,8 +84,8 @@
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
<template #icon><Icon type="ios-bulb-outline" size="20"></Icon></template>
<template #desc style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[班级导入模板.xls]</a>
@@ -98,9 +98,9 @@
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<template #footer><div>
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</div></template>
</Modal>
<!-- 弹窗3:选择教师 -->
@@ -119,10 +119,10 @@
</Select>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="selectTeacherModal = false">取消</Button>
<Button type="primary" @click="confirmSelectTeacher">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗5:选择校区 -->
@@ -135,15 +135,16 @@
</Select>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="selectCampusModal = false">取消</Button>
<Button type="primary" @click="confirmSelectCampus">提交</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import { Button, Input } from "view-ui-plus";
import myTree from "../../components/my_tree";
export default {
components: { myTree},
@@ -336,16 +337,12 @@ export default {
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)
return h(Input, {modelValue: params.row.cname,
'onUpdate:modelValue': (val) => {
params.row.classAlias = val
this.cellChange(params.index, 'classAlias', val)
}
}
})
},
style: { width: '150px' }})
}
},
{
@@ -543,14 +540,10 @@ export default {
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) }
})
h(Button, {icon: 'ios-eye-outline', type: 'text', size: 'large',
onClick: () => this.viewDetail(params.row)}),
h(Button, {icon: 'ios-trash-outline', type: 'text', color: 'red', size: 'large',disabled:params.row.disableDel,
onClick: () => this.deleteRow(params.index, params.row)})
])
}
}
+4 -4
View File
@@ -50,7 +50,7 @@
<!-- <div class="pagination-bottom">
<span class="total-text">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10,20,50]"
@@ -63,7 +63,7 @@
<!-- 设置教师弹窗 -->
<Modal v-model="modalVisible" title="选择教师" width="400">
<Form ref="form" :model="formData" :rules="formRules" label-width="90">
<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>
@@ -75,10 +75,10 @@
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="modalVisible = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitSelectTeacher">提交</Button>
</div>
</div></template>
</Modal>
</div>
</template>
+20 -25
View File
@@ -30,7 +30,7 @@
style="width:200px"
@keyup.enter="searchList"
>
<Icon slot="suffix" type="ios-search" @click="searchList" style="cursor:pointer;"></Icon>
<template #suffix><Icon type="ios-search" @click="searchList" style="cursor:pointer;"></Icon></template>
</Input>
</div>
</div>
@@ -49,7 +49,7 @@
<div class="pagination-bottom">
<span class="total-text">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10,20,50]"
@@ -64,7 +64,7 @@
<!-- 新增/编辑班级弹窗 -->
<Modal v-model="modalVisible" :title="modalTitle" width="520">
<Form ref="form" :model="formData" :rules="formRules" label-width="110">
<Form ref="form" :model="formData" :rules="formRules" :label-width="110">
<FormItem label="班级代码">
<Input v-model="formData.classCode" placeholder="请输入班级代码"></Input>
</FormItem>
@@ -95,17 +95,17 @@
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="modalVisible = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitForm">提交</Button>
</div>
</div></template>
</Modal>
<!-- 班级信息导入 -->
<Modal v-model="importModal" title="班级信息导入" width="560">
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<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>
<Input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></Input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
@@ -115,7 +115,7 @@
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
<Button icon="md-add"></Button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.fileName" style="margin-bottom:10px">
@@ -126,8 +126,8 @@
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
<template #icon><Icon type="ios-bulb-outline" size="20"></Icon></template>
<template #desc style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[班级导入模板.xls]</a>
@@ -140,15 +140,16 @@
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<template #footer><div>
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
import { Button } from "view-ui-plus";
import myTree from "../../components/my_tree";
export default {
name: 'HigherEducation',
components: { myTree},
@@ -193,18 +194,12 @@
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) }
})
h(Button, {type: 'text', icon: 'ios-eye', size: 'large',
onClick: () => this.viewDetail(params.row)}),
h(Button, {type: 'text', icon: 'ios-create', size: 'large',
onClick: () => this.editRow(params.row, params.index)}),
h(Button, {type: 'text', icon: 'ios-trash', size: 'large',
onClick: () => this.delRow(params.index)})
])
}
}
+2 -2
View File
@@ -30,7 +30,7 @@
<div class="page-wrap">
<span style="margin-right:12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,50,100]"
@@ -159,7 +159,7 @@
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
.table-scroll-wrap :deep(.ivu-table) {
height: 100%;
}
+36 -41
View File
@@ -77,14 +77,14 @@
</div>
<div class="page-pagination">
<Page
:current.sync="pageNum"
:page-size.sync="pageSize"
v-model="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,50]"
show-sizer
show-total
@on-change="loadTableData"
@on-page-size-change="loadTableData"
@on-page-size-change="v => { pageSize = v; loadTableData(); }"
></Page>
</div>
</div>
@@ -92,39 +92,39 @@
<!-- 弹窗1:新增校本分组 -->
<Modal v-model="addGroupModal" title="添加组" width="420">
<Form ref="groupForm" :model="groupForm" :rules="groupRules" label-width="80">
<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">
<template #footer><div 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>
</div></template>
</Modal>
<!-- 弹窗2:编辑校本分组(对标截图红框弹窗) -->
<Modal v-model="editGroupModal" title="编辑组" width="420">
<Form ref="editGroupForm" :model="editGroupForm" :rules="editGroupRules" label-width="80">
<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">
<template #footer><div 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>
</div></template>
</Modal>
<!-- 弹窗3:新增选修班 -->
<Modal v-model="addClassModal" title="添加选修班" width="560">
<Form ref="addClassForm" :model="addClassForm" :rules="addClassRules" label-width="100">
<Form ref="addClassForm" :model="addClassForm" :rules="addClassRules" :label-width="100">
<FormItem label="*班级名称" prop="className">
<Input v-model="addClassForm.className" placeholder="请输入班级名称"></Input>
</FormItem>
@@ -183,15 +183,15 @@
<Input v-model.number="addClassForm.studentNum" placeholder="请输入计划招生人数"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="addClassModal=false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitAddClass">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗4:编辑选修班(完全匹配截图布局) -->
<Modal v-model="editClassModal" title="编辑选修班" width="560">
<Form ref="editClassForm" :model="editClassForm" :rules="addClassRules" label-width="100">
<Form ref="editClassForm" :model="editClassForm" :rules="addClassRules" :label-width="100">
<FormItem label="*班级名称" prop="className">
<Input v-model="editClassForm.className"></Input>
</FormItem>
@@ -229,10 +229,10 @@
</div>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="editClassModal=false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitEditClass">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗5:班级信息导入 -->
@@ -243,10 +243,10 @@
<Upload action="" :before-upload="()=>false">
<Button icon="ios-cloud-upload-outline">选择要上传的文件</Button>
</Upload>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="importClassModal=false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmImportClass">确定</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗6:学生导入 -->
@@ -257,34 +257,35 @@
<Upload action="" :before-upload="()=>false">
<Button icon="ios-cloud-upload-outline">选择要上传的文件</Button>
</Upload>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="importStudentModal=false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmImportStudent">确定</Button>
</div>
</div></template>
</Modal>
<!-- 批量删除确认弹窗 -->
<Modal v-model="delConfirmModal" title="提示" width="360">
<Alert type="warning" show-icon>确认批量删除选中班级?删除后不可恢复</Alert>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="delConfirmModal=false" style="margin-right:10px">取消</Button>
<Button type="error" @click="confirmBatchDel">确定删除</Button>
</div>
</div></template>
</Modal>
<!-- 删除分组二次确认弹窗 -->
<Modal v-model="delGroupConfirmModal" title="删除分组提示" width="380">
<Alert type="error" show-icon>确定删除该分组?分组下所有班级将会一并删除,不可恢复!</Alert>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="delGroupConfirmModal=false" style="margin-right:10px">取消</Button>
<Button type="error" @click="doDelGroup">确认删除</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
export default {
import { Button } from "view-ui-plus";
export default {
name: "CurriculumElectiveClass",
data() {
return {
@@ -323,20 +324,14 @@
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, {type: "text", icon: "ios-create", size: "large",
onClick: () => this.openEditClass(params.row)}),
// 学生管理
h("Button", {
props: { type: "text", icon: "ios-people", size: "large" },
on: { click: () => this.openStudentManage(params.row) }
}),
h(Button, {type: "text", icon: "ios-people", size: "large",
onClick: () => this.openStudentManage(params.row)}),
// 同步刷新
h("Button", {
props: { type: "text", icon: "ios-refresh", size: "large" },
on: { click: () => this.singleSync(params.row) }
})
h(Button, {type: "text", icon: "ios-refresh", size: "large",
onClick: () => this.singleSync(params.row)})
])
}
}
@@ -755,24 +750,24 @@
<style>
/* 全局穿透修改iView表格,完全匹配截图:无外框、无竖线、仅行底横线 */
/deep/ .ivu-table {
:deep(.ivu-table) {
border: none !important;
}
/deep/ .ivu-table-header th {
:deep(.ivu-table-header th) {
border: none;
border-bottom: 1px solid #eee;
text-align: center;
background: #fff;
}
/deep/ .ivu-table td {
:deep(.ivu-table td) {
border-left: none !important;
border-right: none !important;
border-bottom: 1px solid #f0f0f0;
}
/deep/ .ivu-table-row {
:deep(.ivu-table-row) {
border: none;
}
/deep/ .ivu-table-stripe .ivu-table-row:nth-child(even) {
:deep(.ivu-table-stripe .ivu-table-row:nth-child(even)) {
background-color: #fafafa;
}
</style>
+15 -15
View File
@@ -32,14 +32,14 @@
<Button type="error" @click="batchDelConfirm">删除</Button>
<div class="pagination-box">
<Page
:current.sync="pageNum"
:page-size.sync="pageSize"
v-model="pageNum"
:page-size="pageSize"
:total="totalCount"
:page-size-opts="[10,20,50]"
show-sizer
show-total
@on-change="loadStudentData"
@on-page-size-change="loadStudentData"
@on-page-size-change="v => { pageSize = v; loadStudentData(); }"
></Page>
</div>
</div>
@@ -47,7 +47,7 @@
<!-- 弹窗1:调班弹窗 -->
<Modal v-model="transferModal" title="调班" width="680">
<Form label-width="100">
<Form :label-width="100">
<FormItem label="当前班级">
<Input v-model="currentClassName" disabled></Input>
</FormItem>
@@ -75,10 +75,10 @@
</div>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="transferModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmTransferStudent">确定</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗2:学生信息导入 -->
@@ -89,19 +89,19 @@
<Upload action="" :before-upload="()=>false">
<Button icon="ios-cloud-upload-outline">选择要上传的文件</Button>
</Upload>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="importModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmImport">确定</Button>
</div>
</div></template>
</Modal>
<!-- 批量删除二次确认弹窗 -->
<Modal v-model="delConfirmModal" title="提示" width="360">
<Alert type="warning" show-icon>确认删除选中学生?删除后不可恢复</Alert>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="delConfirmModal=false" style="margin-right:10px">取消</Button>
<Button type="error" @click="doBatchDelete">确定删除</Button>
</div>
</div></template>
</Modal>
</div>
</template>
@@ -322,24 +322,24 @@
<style>
/* 全局穿透 表格样式 与截图一致:无外框无竖线,仅行底横线 */
/deep/ .ivu-table {
:deep(.ivu-table) {
border: none !important;
}
/deep/ .ivu-table-header th {
:deep(.ivu-table-header th) {
border: none;
border-bottom: 1px solid #eee;
text-align: center;
background: #fff;
}
/deep/ .ivu-table td {
:deep(.ivu-table td) {
border-left: none !important;
border-right: none !important;
border-bottom: 1px solid #f0f0f0;
}
/deep/ .ivu-table-row {
:deep(.ivu-table-row) {
border: none;
}
/deep/ .ivu-table-stripe .ivu-table-row:nth-child(even) {
:deep(.ivu-table-stripe .ivu-table-row:nth-child(even)) {
background-color: #fafafa;
}
</style>
+19 -25
View File
@@ -24,7 +24,7 @@
<FormItem label="新学年">
<Input disabled :value="addForm.year + '学年'" />
</FormItem>
<Row gutter="16">
<Row :gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="yearStartDate">
<DatePicker
@@ -56,7 +56,7 @@
></DatePicker>
</FormItem>
<div class="form-block-title">第一学期设置</div>
<Row gutter="16">
<Row :gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="firstTermStartDate">
<DatePicker
@@ -81,7 +81,7 @@
</Col>
</Row>
<div class="form-block-title">第二学期设置</div>
<Row gutter="16">
<Row :gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="secondTermStartDate">
<DatePicker
@@ -104,10 +104,10 @@
</Col>
</Row>
</Form>
<div slot="footer" class="modal-footer">
<template #footer><div class="modal-footer">
<Button @click="addModalVisible = false">取消</Button>
<Button type="primary" @click="submitAdd">提交</Button>
</div>
</div></template>
</Modal>
<!-- 编辑弹窗 -->
<Modal v-model="editModalVisible" title="修改学年学期" width="720" class="modalform">
@@ -122,7 +122,7 @@
<FormItem label="新学年">
<Input disabled :value="editForm.year + '学年'" />
</FormItem>
<Row gutter="16">
<Row :gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="yearStartDate">
<DatePicker
@@ -151,7 +151,7 @@
></DatePicker>
</FormItem>
<div class="form-block-title">第一学期设置</div>
<Row gutter="16">
<Row :gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="firstTermStartDate">
<DatePicker
@@ -172,7 +172,7 @@
</Col>
</Row>
<div class="form-block-title">第二学期设置</div>
<Row gutter="16">
<Row :gutter="16">
<Col span="12">
<FormItem label="开始日期" prop="secondTermStartDate">
<DatePicker
@@ -193,15 +193,16 @@
</Col>
</Row>
</Form>
<div slot="footer" class="modal-footer">
<template #footer><div class="modal-footer">
<Button @click="editModalVisible = false">取消</Button>
<Button type="primary" @click="submitEdit">保存</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import { self_viewport } from "@/mixins/self_viewport";
import { Button } from "view-ui-plus";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: "AcademicYearTerm",
data() {
@@ -268,19 +269,12 @@ export default {
width: 90,
align: "center",
render: (h, params) => {
return h(
"Button",
{
props: {
icon: "ios-create",
shape: "circle"
},
on: {
click: () => {
return h(Button,
{icon: "ios-create",
shape: "circle",
onClick: () => {
this.openEditModal(params.row);
}
}
}
}}
);
}
}
@@ -550,7 +544,7 @@ export default {
padding: 10px;
/* margin: 10px 0 15px; */
}
.term-table /deep/ .ivu-table-row td {
.term-table :deep(.ivu-table-row td) {
padding: 6px 0;
}
.form-block-title {
@@ -563,7 +557,7 @@ export default {
.modal-footer {
text-align: right;
}
.modalform /deep/ .ivu-modal-body{
.modalform :deep(.ivu-modal-body) {
padding:10px;
}
</style>
+36 -45
View File
@@ -11,24 +11,22 @@
<div class="btn-container">
<draggable
v-model="campuslist"
animation="150"
item-key="name"
:animation="150"
ghost-class="drag-ghost"
class="drag-box"
:options="{ group: 'people' }"
group="people"
@start="drag = true"
@end="drag = false"
:move="getdata1"
@update="datadragEnd1"
@change="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>
<template #item="{ element, index }">
<div class="tag-wrap" @dblclick="openEditModal('campus', index)">
<div class="tag-btn">{{ element.name }}</div>
<span class="close-btn" @click.stop="delCampus(index)">×</span>
</div>
</template>
</draggable>
<Button icon="ios-add" size="large" class="add-btn" @click="openAddModal('campus')">添加校区</Button>
</div>
@@ -43,24 +41,22 @@
<div class="btn-container">
<draggable
v-model="divisionlist"
animation="150"
item-key="name"
:animation="150"
ghost-class="drag-ghost"
class="drag-box"
:options="{ group: 'people' }"
group="people"
@start="drag = true"
@end="drag = false"
:move="getdata2"
@update="datadragEnd2"
@change="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>
<template #item="{ element, index }">
<div class="tag-wrap" @dblclick="openEditModal('dept', index)">
<div class="tag-btn">{{ element.name }}</div>
<span class="close-btn" @click.stop="delDept(index)">×</span>
</div>
</template>
</draggable>
<Button icon="ios-add" size="large" class="add-btn" @click="openAddModal('dept')">添加系部</Button>
</div>
@@ -80,7 +76,7 @@
placeholder="输入代码或名称搜索"
@keyup.enter="searchMajor"
>
<Icon slot="suffix" type="ios-search" @click="searchMajor" style="cursor:pointer;"></Icon>
<template #suffix><Icon type="ios-search" @click="searchMajor" style="cursor:pointer;"></Icon></template>
</i-Input>
</div>
</div>
@@ -98,7 +94,7 @@
<!-- <div class="pagination-fixed-bottom">
<span class="total-desc">共 {{ majorTotal }} 条</span>
<Page
:current.sync="majorPageNum"
v-model="majorPageNum"
:total="majorTotal"
:page-size="majorPageSize"
:page-size-opts="[10,20,50]"
@@ -111,7 +107,7 @@
<!-- 校区/系部 新增编辑弹窗 -->
<Modal v-model="modalVisible" :title="modalTitle" width="420">
<Form ref="formRef" :model="formData" :rules="formRule" label-width="90">
<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>
@@ -119,15 +115,15 @@
<Input v-model="formData.name" placeholder="请输入系部名称" />
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="modalVisible = false" style="margin-right: 10px">取消</Button>
<Button type="primary" @click="submitModal">提交</Button>
</div>
</div></template>
</Modal>
<!-- 专业新增编辑弹窗 -->
<Modal v-model="majorModalVisible" :title="majorModalTitle" width="460">
<Form ref="majorFormRef" :model="majorForm" :rules="majorFormRule" label-width="90">
<Form ref="majorFormRef" :model="majorForm" :rules="majorFormRule" :label-width="90">
<FormItem label="专业代码">
<Input v-model="majorForm.code" placeholder="请输入专业代码" />
</FormItem>
@@ -138,15 +134,16 @@
<Input v-model="majorForm.schsystemLeng" type="number" placeholder="请输入学制" />
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="majorModalVisible = false" style="margin-right: 10px">取消</Button>
<Button type="primary" @click="submitMajorModal">提交</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import { Button, Icon } from "view-ui-plus";
import draggable from 'vuedraggable'
import Sortable from 'sortablejs'
export default {
@@ -190,19 +187,13 @@ export default {
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) }
}),
h(Button, {type: 'text', icon: 'ios-create', size: 'small',
onClick: () => this.openMajorEdit(params.index)}),
h(Button, {type: 'text', icon: 'ios-trash', size: 'small',
onClick: () => this.delMajor(params.index)}),
// 拖拽把手class固定为 drag-handle
h('Icon', {
props: { type: 'ios-move', size: 18, title: '拖动排序' },
class: 'drag-handle drag-icon'
})
h(Icon, {type: 'ios-move', size: 18, title: '拖动排序',
class: 'drag-handle drag-icon'})
])
}
}
@@ -246,7 +237,7 @@ export default {
}
}, 120)
},
beforeDestroy() {
beforeUnmount() {
if (this.tableDrag) this.tableDrag.destroy()
},
methods: {
+32 -33
View File
@@ -47,7 +47,7 @@
<div class="page-right">
<span style="margin-right:12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40,50]"
@@ -61,7 +61,7 @@
<!-- 弹窗1:添加学生 -->
<Modal v-model="addModalVisible" title="添加学生" width="560">
<Form ref="addForm" :model="addForm" :rules="formRules" label-width="90">
<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>
@@ -88,15 +88,15 @@
<Input v-model="addForm.pwd" type="password" placeholder="请输入密码"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="addModalVisible=false">取消</Button>
<Button type="primary" @click="submitAddStudent">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗2:编辑学生 -->
<Modal v-model="editModalVisible" title="编辑学生" width="560">
<Form ref="editForm" :model="editForm" :rules="formRules" label-width="90">
<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>
@@ -120,10 +120,10 @@
<Input v-model="editForm.account"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="editModalVisible=false">取消</Button>
<Button type="primary" @click="submitEditStudent">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗3:学生信息导入 -->
@@ -141,9 +141,9 @@
<Button type="primary" @click="confirmImport">确定</Button>
</div> -->
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<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>
<Input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></Input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
@@ -153,7 +153,7 @@
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
<Button icon="md-add"></Button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.fileName" style="margin-bottom:10px">
@@ -164,8 +164,8 @@
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
<template #icon><Icon type="ios-bulb-outline" size="20"></Icon></template>
<template #desc style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[学生信息导入模板.xls]</a>
@@ -178,17 +178,17 @@
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<template #footer><div>
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</div></template>
</Modal>
<!-- 弹窗4:学号导入 -->
<Modal v-model="stuIdImportModalVisible" title="学生学号导入" width="520">
<div class="main-contents">
<Form ref="UpParmas2.datapre" :model="UpParmas2.datapre" :label-width="100" inline>
<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>
<Input type="text" style="width:90%;" v-model="UpParmas2.fileName" class="import-input" placeholder="未选中任何文件.." readonly></Input>
<Upload class="fr btn"
:action="UpParmas2.actionUrl"
:data="UpParmas2.datapre"
@@ -198,7 +198,7 @@
:before-upload="handleUpload2"
:on-success="importFile2"
>
<i-button icon="md-add"></i-button>
<Button icon="md-add"></Button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas2.fileName" style="margin-bottom:10px">
@@ -209,8 +209,8 @@
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
<template #icon><Icon type="ios-bulb-outline" size="20"></Icon></template>
<template #desc style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu2" style="font-size:12px;">点击链接下载[学生学号信息导入模板.xls]</a>
@@ -223,9 +223,9 @@
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<template #footer><div>
<i-Button @click="UpfileModalcloseclick2" style="margin-left: 8px">关闭</i-Button>
</div>
</div></template>
</Modal>
<!-- 弹窗5:学生调班 -->
@@ -249,10 +249,10 @@
</div>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="moveClassModalVisible=false">取消</Button>
<Button type="primary" @click="submitMoveClass">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗6:学生异动 -->
@@ -276,25 +276,26 @@
</div>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="changeStatusModalVisible=false">取消</Button>
<Button type="primary" @click="submitChangeStatus">确定</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗7:转出学生弹窗 -->
<Modal v-model="transferStudentModal" title="转出学生" width="420">
<p>确认将选中学生做转出操作?</p>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="transferStudentModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmTransferStudent">确定</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
import { Button } from "view-ui-plus";
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'StudentManage',
@@ -513,10 +514,8 @@
align: 'center',
width: 80,
render: (h, params) => {
return h('Button', {
props: { type: 'text', icon: 'md-create', size: 'large' },
on: { click: () => this.openEditStudent(params.row) }
})
return h(Button, {type: 'text', icon: 'md-create', size: 'large',
onClick: () => this.openEditStudent(params.row)})
}
}
]
@@ -883,7 +882,7 @@
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
.table-scroll-wrap :deep(.ivu-table) {
height: 100%;
}
/* 底部栏:分页固定底部 */
+31 -32
View File
@@ -46,7 +46,7 @@
<div class="page-right">
<span style="margin-right:12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40,50]"
@@ -60,7 +60,7 @@
<!-- 弹窗1:添加学生 -->
<Modal v-model="addModalVisible" title="添加学生" width="560">
<Form ref="addForm" :model="addForm" :rules="formRules" label-width="90">
<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>
@@ -87,15 +87,15 @@
<Input v-model="addForm.pwd" type="password" placeholder="请输入密码"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="addModalVisible=false">取消</Button>
<Button type="primary" @click="submitAddStudent">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗2:编辑学生 -->
<Modal v-model="editModalVisible" title="编辑学生" width="560">
<Form ref="editForm" :model="editForm" :rules="formRules" label-width="90">
<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>
@@ -119,18 +119,18 @@
<Input v-model="editForm.account"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="editModalVisible=false">取消</Button>
<Button type="primary" @click="submitEditStudent">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗3:学生信息导入 -->
<Modal v-model="importModalVisible" title="学生信息导入" width="520">
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<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>
<Input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></Input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
@@ -140,7 +140,7 @@
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
<Button icon="md-add"></Button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.fileName" style="margin-bottom:10px">
@@ -151,8 +151,8 @@
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
<template #icon><Icon type="ios-bulb-outline" size="20"></Icon></template>
<template #desc style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[学生信息导入模板.xls]</a>
@@ -165,17 +165,17 @@
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<template #footer><div>
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</div></template>
</Modal>
<!-- 弹窗4:学号导入 -->
<Modal v-model="stuIdImportModalVisible" title="学生学号导入" width="520">
<div class="main-contents">
<Form ref="UpParmas2.datapre" :model="UpParmas2.datapre" :label-width="100" inline>
<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>
<Input type="text" style="width:90%;" v-model="UpParmas2.fileName" class="import-input" placeholder="未选中任何文件.." readonly></Input>
<Upload class="fr btn"
:action="UpParmas2.actionUrl"
:data="UpParmas2.datapre"
@@ -185,7 +185,7 @@
:before-upload="handleUpload2"
:on-success="importFile2"
>
<i-button icon="md-add"></i-button>
<Button icon="md-add"></Button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas2.fileName" style="margin-bottom:10px">
@@ -196,8 +196,8 @@
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
<template #icon><Icon type="ios-bulb-outline" size="20"></Icon></template>
<template #desc style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu2" style="font-size:12px;">点击链接下载[学生学号信息导入模板.xls]</a>
@@ -210,9 +210,9 @@
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<template #footer><div>
<i-Button @click="UpfileModalcloseclick2" style="margin-left: 8px">关闭</i-Button>
</div>
</div></template>
</Modal>
<!-- 弹窗5:学生调班 -->
@@ -236,10 +236,10 @@
</div>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="moveClassModalVisible=false">取消</Button>
<Button type="primary" @click="submitMoveClass">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗6:学生异动 -->
@@ -263,23 +263,24 @@
</div>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="changeStatusModalVisible=false">取消</Button>
<Button type="primary" @click="submitChangeStatus">确定</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗7:转出学生弹窗 -->
<Modal v-model="transferStudentModal" title="转出学生" width="420">
<p>确认将选中学生做转出操作?</p>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="transferStudentModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmTransferStudent">确定</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import { Button } from "view-ui-plus";
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
@@ -500,10 +501,8 @@ export default {
align: 'center',
width: 80,
render: (h, params) => {
return h('Button', {
props: { type: 'text', icon: 'md-create',type:'large' },
on: { click: () => this.openEditStudent(params.row) }
})
return h(Button, {type: 'text', icon: 'md-create',type:'large',
onClick: () => this.openEditStudent(params.row)})
}
}
]
@@ -866,7 +865,7 @@ export default {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
.table-scroll-wrap :deep(.ivu-table) {
height: 100%;
}
/* 底部栏:分页固定底部 */
+8 -9
View File
@@ -38,7 +38,7 @@
<div class="page-right">
<span style="margin-right:12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40,50]"
@@ -71,16 +71,17 @@
</div>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="changeStatusModalVisible=false">取消</Button>
<Button type="primary" @click="submitChangeStatus">确定</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
import { Button } from "view-ui-plus";
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'StudentManage',
@@ -191,10 +192,8 @@
align: 'center',
width: 80,
render: (h, params) => {
return h('Button', {
props: { type: 'text', icon: 'md-create',type:'large' },
on: { click: () => this.openEditModal(params.row) }
})
return h(Button, {type: 'text', icon: 'md-create',type:'large',
onClick: () => this.openEditModal(params.row)})
}
}
]
@@ -324,7 +323,7 @@
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
.table-scroll-wrap :deep(.ivu-table) {
height: 100%;
}
/* 底部栏:分页固定底部 */
@@ -38,7 +38,7 @@
<div class="page-right">
<span style="margin-right:12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40,50]"
@@ -71,15 +71,16 @@
</div>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="changeStatusModalVisible=false">取消</Button>
<Button type="primary" @click="submitChangeStatus()">确定</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import { Button } from "view-ui-plus";
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
@@ -188,10 +189,8 @@ export default {
align: 'center',
width: 80,
render: (h, params) => {
return h('Button', {
props: { type: 'text', icon: 'md-create',type:'large' },
on: { click: () => this.openEditModal(params.row) }
})
return h(Button, {type: 'text', icon: 'md-create',type:'large',
onClick: () => this.openEditModal(params.row)})
}
}
]
@@ -317,7 +316,7 @@ export default {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
.table-scroll-wrap :deep(.ivu-table) {
height: 100%;
}
/* 底部栏:分页固定底部 */
+6 -6
View File
@@ -1,6 +1,6 @@
<template>
<div class="edu-layout-wrap">
<Row gutter="10" class="layout-row">
<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>
@@ -38,7 +38,7 @@
</Row>
<!-- 新增弹窗 -->
<Modal v-model="addModalVisible" title="添加学校" width="520">
<Form ref="addFormRef" :model="addForm" :rules="formRules" label-width="90">
<Form ref="addFormRef" :model="addForm" :rules="formRules" :label-width="90">
<FormItem label="学校名称" prop="schname">
<Input size="large" v-model="addForm.schname" placeholder="请输入学校名称" />
</FormItem>
@@ -58,17 +58,17 @@
/>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="addModalVisible = false" style="margin-right: 10px">取消</Button>
<Button type="primary" @click="submitAdd">提交</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import Treeselect from '@zanmato/vue3-treeselect'
import '@zanmato/vue3-treeselect/dist/vue3-treeselect.min.css'
import myTree from "../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
+11 -14
View File
@@ -20,7 +20,7 @@
style="width:240px"
@keyup.enter="handleSearch"
>
<Icon slot="suffix" type="ios-search" @click="handleSearch" style="cursor:pointer;"></Icon>
<template #suffix><Icon type="ios-search" @click="handleSearch" style="cursor:pointer;"></Icon></template>
</Input>
</div>
</div>
@@ -41,7 +41,7 @@
<div class="pagination-bottom">
<span class="total-text">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10,20,50]"
@@ -54,7 +54,7 @@
<!-- 新增/编辑课程弹窗(匹配编辑课程信息截图) -->
<Modal v-model="modalVisible" :title="modalTitle" width="580">
<Form ref="form" :model="formData" :rules="formRules" label-width="110">
<Form ref="form" :model="formData" :rules="formRules" :label-width="110">
<FormItem label="课程代码" prop="courseCode">
<Input v-model="formData.courseCode" placeholder="请输入课程代码"></Input>
</FormItem>
@@ -87,16 +87,17 @@
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="modalVisible = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitForm">提交</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
export default {
import { Button } from "view-ui-plus";
export default {
name: 'CourseManage',
data() {
return {
@@ -158,14 +159,10 @@
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) }
})
h(Button, {type: 'text', icon: 'ios-create', size: 'default',
onClick: () => this.editRow(params.row, params.index)}),
h(Button, {type: 'text', icon: 'ios-trash', size: 'default',
onClick: () => this.delRow(params.index)})
])
}
}
+22 -22
View File
@@ -12,30 +12,30 @@
<div class="btn-container">
<draggable
v-model="subjectlist"
item-key="name"
class="drag-wrap"
animation="200"
:options="{ group: 'people' }"
:animation="200"
group="people"
@start="drag = true"
@end="drag = false"
:move="getdata1"
@update="datadragEnd1"
@change="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>
<template #item="{ element, index: idx }">
<div
class="subject-btn"
:class="[element.source === 0 ? 'sys-btn' : 'cus-btn', { active: element.enable }]"
@click="toggleSelect(idx)"
@mouseenter="element.source === 1 && (hoverIndex = idx)"
@mouseleave="hoverIndex = null"
>
<span class="text">{{ element.name }}</span>
<div v-if="element.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>
</div>
</template>
</draggable>
<!-- 添加自建学科按钮 -->
<Button icon="ios-add-outline" class="add-subject-btn" size="large" @click="openAddModal">
@@ -58,15 +58,15 @@
<!-- 新增/编辑弹窗 学科信息 -->
<Modal v-model="modalVisible" title="学科信息" width="420">
<Form ref="form" :model="formData" :rules="formRules" label-width="90">
<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">
<template #footer><div align="right">
<Button @click="modalVisible = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitForm">提交</Button>
</div>
</div></template>
</Modal>
</div>
</template>
@@ -132,7 +132,7 @@
// 多选切换选中/取消
toggleSelect(index) {
let submap=this.subjectlist[index];
this.$set(this.subjectlist[index], 'enable', !submap.enable);
this.subjectlist[index].enable = !submap.enable;
const submitData ={
id:submap.id,
name:submap.name,
+4 -4
View File
@@ -2,10 +2,10 @@
<div class="outer-wrap" style="height:calc(100% - 52px);">
<!-- 顶部标签切换 -->
<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> -->
<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>
+12 -12
View File
@@ -68,7 +68,7 @@
<!-- 弹窗1:添加学科 -->
<Modal v-model="addModalVisible" :title="addFormData.id?'编辑组':'添加组'" width="520">
<Form ref="addForm" :model="addFormData" label-width="90">
<Form ref="addForm" :model="addFormData" :label-width="90">
<FormItem label="组名">
<Input v-model="addFormData.groupName" maxlength="20" placeholder="最多可输入20个字符"/>
</FormItem>
@@ -83,18 +83,18 @@
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="addModalVisible = false">取消</Button>
<Button type="primary" @click="submitAdd">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗2:教师批量导入 -->
<Modal v-model="importModalVisible" title="批量导入分组信息" width="520">
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<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>
<Input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></Input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
@@ -104,7 +104,7 @@
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
<Button icon="md-add"></Button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.fileName" style="margin-bottom:10px">
@@ -115,8 +115,8 @@
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
<template #icon><Icon type="ios-bulb-outline" size="20"></Icon></template>
<template #desc style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[分组导入模板.xls]</a>
@@ -129,12 +129,12 @@
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<template #footer><div>
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</div></template>
</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" />
<user-check ref="userCheck" :treeData="treeData" :userids="userids" @change="v => (userids = v)" @on-submit="submit" :isDisabled="isAddto" />
<user-check ref="userCheck2" :treeData="treeData" :userids="userids2" @change="v => (userids2 = v)" @on-submit="submit2" :isDisabled="isAddto" />
</div>
</template>
+2 -2
View File
@@ -29,7 +29,7 @@
<div class="page-fixed-bottom">
<span class="total-text">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10,20,50]"
@@ -166,7 +166,7 @@ export default {
flex: 1;
min-height: 0;
}
.table-scroll-box >>> .teacher-table {
.table-scroll-box :deep(.teacher-table) {
height: 100%;
}
+36 -37
View File
@@ -69,7 +69,7 @@
</div>
<div class="pagination-wrap">
<Page
:current.sync="pageNum"
v-model="pageNum"
:total="total"
:page-size="pageSize"
:page-size-opts="[10,20,50]"
@@ -86,7 +86,7 @@
<!-- 弹窗1:添加教师 -->
<Modal v-model="addTeacherModal" title="添加教师" width="540">
<Form ref="addTeacherForm" :model="addTeacherFormData" :rules="addTeacherRules" label-width="110">
<Form ref="addTeacherForm" :model="addTeacherFormData" :rules="addTeacherRules" :label-width="110">
<FormItem label="教师姓名" prop="teacherName">
<Input v-model="addTeacherFormData.teacherName" placeholder="请输入老师姓名"></Input>
</FormItem>
@@ -109,15 +109,15 @@
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="addTeacherModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitAddTeacher">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗2:编辑教师 -->
<Modal v-model="editTeacherModal" title="编辑教师" width="540">
<Form ref="editTeacherForm" :model="editTeacherFormData" :rules="editTeacherRules" label-width="110">
<Form ref="editTeacherForm" :model="editTeacherFormData" :rules="editTeacherRules" :label-width="110">
<FormItem label="教师姓名" prop="teacherName">
<Input v-model="editTeacherFormData.teacherName"></Input>
</FormItem>
@@ -137,18 +137,18 @@
</Select>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="editTeacherModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitEditTeacher">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗3:教师导入 -->
<Modal v-model="importModal" title="教师导入" width="460">
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<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>
<Input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></Input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
@@ -158,7 +158,7 @@
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
<Button icon="md-add"></Button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.fileName" style="margin-bottom:10px">
@@ -169,8 +169,8 @@
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
<template #icon><Icon type="ios-bulb-outline" size="20"></Icon></template>
<template #desc style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[教师导入模板.xls]</a>
@@ -183,14 +183,14 @@
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<template #footer><div>
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</div></template>
</Modal>
<!-- 弹窗4:添加子组 -->
<Modal v-model="addGroupModal" title="添加子组" width="420">
<Form ref="addGroupForm" :model="addGroupFormData" :rules="addGroupRules" label-width="90">
<Form ref="addGroupForm" :model="addGroupFormData" :rules="addGroupRules" :label-width="90">
<FormItem label="组名称">
<Input v-model="addGroupFormData.parentGroupName" disabled></Input>
</FormItem>
@@ -201,15 +201,15 @@
<Input v-model="addGroupFormData.sort"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="addGroupModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitAddGroup">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗5:修改子组 -->
<Modal v-model="editGroupModal" title="修改子组" width="420">
<Form ref="editGroupForm" :model="editGroupFormData" :rules="editGroupRules" label-width="90">
<Form ref="editGroupForm" :model="editGroupFormData" :rules="editGroupRules" :label-width="90">
<FormItem label="子组名称" prop="groupName">
<Input v-model="editGroupFormData.groupName"></Input>
</FormItem>
@@ -217,10 +217,10 @@
<Input v-model="editGroupFormData.sort"></Input>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="editGroupModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitEditGroup">提交</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗6:移动子组 -->
@@ -233,10 +233,10 @@
<span>移动至:</span>
<my-tree :treeData="treeData" @on-tree-click="movetreeLink"></my-tree>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="moveGroupModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmMoveGroup">确定</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗7:批量删除确认 -->
@@ -244,10 +244,10 @@
<Alert type="warning" show-icon>
确定是否删除教师?<br/>删除后将不可恢复。
</Alert>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="batchDeleteModal = false" style="margin-right:10px">取消</Button>
<Button type="error" @click="confirmBatchDelete">确定</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗8:移动教师弹窗 -->
@@ -294,15 +294,15 @@
</div>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="moveTeacherModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitMoveTeacher">确定</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗9:【设置教师的状态】 匹配设计图 -->
<Modal v-model="setStatusModal" title="设置教师的状态" width="380">
<Form ref="statusForm" :model="statusFormData" label-width="120">
<Form ref="statusForm" :model="statusFormData" :label-width="120">
<FormItem label="选择设置状态">
<RadioGroup v-model="statusFormData.status">
<Radio label="0">正常</Radio>
@@ -310,25 +310,26 @@
</RadioGroup>
</FormItem>
</Form>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="setStatusModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="submitSetStatus">确定</Button>
</div>
</div></template>
</Modal>
<!-- 弹窗10:转出教师弹窗 -->
<Modal v-model="transferTeacherModal" title="转出教师" width="420">
<p>确认将选中教师做转出操作?转出后不在本校编制内</p>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="transferTeacherModal = false" style="margin-right:10px">取消</Button>
<Button type="primary" @click="confirmTransferTeacher">确定</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import myTree from "../../components/my_tree";
import { Button } from "view-ui-plus";
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'TeacherInfoManage',
@@ -401,10 +402,8 @@ export default {
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) }
})
return h(Button, {type: 'text', icon: 'md-create', size: 'large',
onClick: () => this.editSingleTeacher(params.row, params.index)})
}
}
],
@@ -941,7 +940,7 @@ export default {
gap: 8px;
border-bottom: 1px solid #eee;
}
.tree-sidebar >>> .ivu-tree {
.tree-sidebar :deep(.ivu-tree) {
flex: 1;
overflow-y: auto;
padding: 8px;
+13 -14
View File
@@ -42,18 +42,18 @@
</Select>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="teacherModalVisible = false">取消</Button>
<Button type="primary" @click="submitSelectTeacher">提交</Button>
</div>
</div></template>
</Modal>
<!-- 批量导入弹窗 -->
<Modal v-model="importModalVisible" title="批量导入任教信息" width="520">
<div class="main-contents">
<Form ref="UpParmas.datapre" :model="UpParmas.datapre" :label-width="100" inline>
<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>
<Input type="text" style="width:90%;" v-model="UpParmas.fileName" class="import-input" placeholder="未选中任何文件.." readonly></Input>
<Upload class="fr btn"
:action="UpParmas.actionUrl"
:data="UpParmas.datapre"
@@ -63,7 +63,7 @@
:before-upload="handleUpload"
:on-success="importFile"
>
<i-button icon="md-add"></i-button>
<Button icon="md-add"></Button>
</Upload>
</FormItem>
<FormItem v-if="UpParmas.fileName" style="margin-bottom:10px">
@@ -74,8 +74,8 @@
<Alert show-icon>
操作说明:
<Icon type="ios-bulb-outline" slot="icon" size="20"></Icon>
<template slot="desc" style="font-size: 12px;">
<template #icon><Icon type="ios-bulb-outline" size="20"></Icon></template>
<template #desc style="font-size: 12px;">
请先上传EXCEL文件,若成功则显示导入成功数据,否则显示错误的数据及错误原因;<br>
EXCEL文件的格式():工作表(WorkSheet)的名称应设为缺省的[Sheet1];<br>
<a @click="MoBanExceldaochu" style="font-size:12px;">点击链接下载[任教导入模板.xls]</a>
@@ -88,14 +88,15 @@
<Icon type="ios-loading" size=30 class="demo-spin-icon-load"></Icon>
<div>上传中...</div>
</Spin>
<div slot="footer">
<template #footer><div>
<i-Button @click="UpfileModalcloseclick" style="margin-left: 8px">关闭</i-Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import { Button } from "view-ui-plus";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'TeachManage',
@@ -380,11 +381,9 @@ export default {
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) }
})
return h(Button, {type: 'text', icon: 'ios-refresh', size: 'large',
onClick: () => this.handleResetRow(params.index),
attrs: { title: '重置本行所有任教' }})
}
}];
+8 -9
View File
@@ -44,7 +44,7 @@
<div class="page-area">
<span style="margin-right: 12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40]"
@@ -67,16 +67,17 @@
<my-tree :treeData="groupTreeData" @on-tree-click="treeLink"></my-tree>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="transferModalVisible = false">取消</Button>
<Button type="primary" @click="submitTransfer">提交</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import myTree from "../components/my_tree";
import { Button } from "view-ui-plus";
import myTree from "../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
name: 'TeacherTransferPage',
@@ -159,10 +160,8 @@
align: 'center',
width: 100,
render: (h, params) => {
return h('Button', {
props: { type: 'text' },
on: { click: () => this.openTransferModal(params.row) }
},'转入')
return h(Button, {type: 'text',
onClick: () => this.openTransferModal(params.row)},'转入')
}
}
]
@@ -297,7 +296,7 @@
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
.table-scroll-wrap :deep(.ivu-table) {
height: 100%;
}
+7 -8
View File
@@ -51,7 +51,7 @@
<div class="page-area">
<span style="margin-right: 12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40]"
@@ -74,15 +74,16 @@
<my-tree :treeData="targetTreeData" @on-tree-click="treeLink"></my-tree>
</div>
</div>
<div slot="footer" align="right">
<template #footer><div align="right">
<Button @click="moveModalVisible = false">取消</Button>
<Button type="primary" @click="submitMove">提交</Button>
</div>
</div></template>
</Modal>
</div>
</template>
<script>
import { Button } from "view-ui-plus";
import myTree from "../../components/my_tree";
import { self_viewport } from "@/mixins/self_viewport";
export default {
@@ -183,10 +184,8 @@ export default {
align: 'center',
width: 100,
render: (h, params) => {
return h('Button', {
props: { type: 'text' },
on: { click: () => this.openMoveModal(params.row) }
},'转入')
return h(Button, {type: 'text',
onClick: () => this.openMoveModal(params.row)},'转入')
}
}
]
@@ -320,7 +319,7 @@ export default {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
.table-scroll-wrap :deep(.ivu-table) {
height: 100%;
}
+4 -5
View File
@@ -57,7 +57,7 @@
<div class="page-area">
<span style="margin-right: 12px">共 {{ total }} 条</span>
<Page
:current.sync="pageNum"
v-model="pageNum"
:page-size="pageSize"
:total="total"
:page-size-opts="[10,20,40]"
@@ -81,15 +81,14 @@
: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">
<template #footer><div align="right">
<Button @click="moveModalVisible = false">取消</Button>
<Button type="primary" @click="submitMove">提交</Button>
</div>
</div></template>
</Modal>
</div>
</template>
@@ -265,7 +264,7 @@ export default {
flex: 1;
min-height: 0;
}
.table-scroll-wrap >>> .ivu-table {
.table-scroll-wrap :deep(.ivu-table) {
height: 100%;
}
+12
View File
@@ -0,0 +1,12 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_BASE_HOST: string;
readonly VITE_SSO_HOST: string;
readonly VITE_SERVICE_HOST: string;
readonly VITE_IS_MOCK: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
+27
View File
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"types": ["vite/client"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
+11
View File
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"types": ["node"]
},
"include": ["vite.config.ts"]
}
+87
View File
@@ -0,0 +1,87 @@
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import { createHtmlPlugin } from "vite-plugin-html";
/**
* 构建基线对齐 NoticeFront(云海子应用前端接入规范 v1.12):
* Vue3 + TS + view-ui-plus(1.3.24) + Vite;主题/皮肤/图标由 /static 通道下发。
*/
export default defineConfig(({ mode }) => {
const root = process.cwd();
return {
plugins: [
vue(),
createHtmlPlugin({
minify: true,
pages: [
{
entry: "src/main.ts",
template: "index.html",
filename: "index.html",
injectOptions: {
data: {
title: "学校管理",
injectScript: ``,
},
},
},
],
}),
],
resolve: {
alias: {
"@": path.resolve(root, "src"),
"@public": path.resolve(root, "public"),
},
extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],
},
server: {
host: "localhost",
port: 8088,
open: false,
proxy: {
// 本地后端:serviceUrl 在 dev 下为空 → /api/* 同源相对,由本代理转发
"/api": {
target: process.env.YH_API_PROXY_TARGET || "http://localhost:8101",
changeOrigin: true,
},
// 主题通道本地联调:默认线上通道,联调本地通道源用
// YH_THEME_PROXY_TARGET=http://localhost:9527 npm run dev
"/static": {
target: process.env.YH_THEME_PROXY_TARGET || "http://base.yunhaixingtong.com",
changeOrigin: true,
},
},
},
base: "./",
build: {
target: "modules",
outDir: "dist",
assetsDir: "assets",
sourcemap: false,
minify: "esbuild",
chunkSizeWarningLimit: 1500,
cssTarget: "chrome61",
rollupOptions: {
input: {
main: path.resolve(root, "index.html"),
},
output: {
chunkFileNames: "static/js/[name]-[hash].js",
entryFileNames: "static/js/[name]-[hash].js",
assetFileNames: (assetInfo: any) => {
if (assetInfo.type === "asset" && /\.(jpe?g|png|gif|svg)$/i.test(assetInfo.name)) {
return "static/img/[name].[hash][ext]";
}
if (assetInfo.type === "asset" && /\.(ttf|woff|woff2|eot)$/i.test(assetInfo.name)) {
return "static/fonts/[name].[hash][ext]";
}
return "static/[ext]/[name]-[hash].[ext]";
},
},
},
},
};
});
-112
View File
@@ -1,112 +0,0 @@
const path = require("path");
function resolve(dir) {
return path.join(__dirname, dir);
}
const isDev = process.env.NODE_ENV !== "production";
module.exports = {
publicPath: "./", //部署应用时的根路径(默认'/'),也可用相对路径(存在使用限制)
outputDir: "dist", //运行时生成的生产环境构建文件的目录(默认'dist',构建之前会被清除)
assetsDir: "", //静态资源目录(js、css、img、fonts),相对outputDir的目录(默认'')
indexPath: "index.html", //指定生成index.html的输出路径(相对outputDir)也可以是绝对路径
lintOnSave: false, //是否开启ESlint(保存时检查)
configureWebpack: {
//配置路径
resolve: {
alias: {
"@": resolve("/src"),
"@assets": resolve("/src/assets"),
"@public": resolve("/public")
},
extensions: [".js", ".json", ".css", ".scss", ".vue"]
},
externals: {
//采用地址栏的方式来引用
vue: "Vue",
axios: "axios"
}
// entry: {
// app: ["@babel/polyfill", "./src/main.js"],
// },
},
productionSourceMap: false, //生产环境是否生成 sourceMap 文件
pages: {
//pages里配置的路径和文件名在你的文档目录必须存在,否则启动服务会报错
index: {
//除了 entry 之外都是可选的
entry: "src/main.js", //page的主入口
template: "public/index.html", //模板来源
filename: "index.html", //在 dist/index.html 的输出
//title在template中使用:<title><%=htmlWebpackPlugin.options.title%></title>
title: "beschool",
chunks: ["chunk-vendors", "chunk-common", "index"]
// 在这个页面中包含的块,默认情况下会包含,提取出来的通用 chunk 和 vendor chunk
}
//subpage: 'src/main.js'
//官方解释:当使用只有入口的字符串格式时,模板会被推导为public/subpage.html
//若找不到就回退到public/index.html,输出文件名会被推导为subpage.html
},
css: {
extract: !isDev, //是否使用css分离插件 ExtractTextPlugin
sourceMap: isDev, //开启 CSS source maps
// modules: false, //启用CSS modules for all css / pre-processor files.
requireModuleExtension: true, //与modules相反
loaderOptions: {
//css预设器配置项
sass: {
// prependData: `@import "@assets/styles/varible.scss";`,
}
/* postcss: {
plugins: [
require('postcss-pxtorem')({
rootValue: 37.5, // 换算的基数
selectorBlackList: [], // 忽略转换正则匹配项
propList: ['*'],
}),
]
} */
}
},
devServer: {
//环境配置
host: "localhost",
port: 8088,
https: false, //是否开启https
hotOnly: true, //是否配置热更新
open: true, //配置自动启动浏览器
// 主题通道 dev 代理(A 类接入 §2.2 第一步):
// 本地开发时 /static/* 兜到主题通道(默认线上通道),联调本地通道源可覆盖:
// YH_THEME_PROXY_TARGET=http://localhost:9527 npm run dev
// 生产与通道同源,不依赖本代理。
proxy: {
"/static": {
target: process.env.YH_THEME_PROXY_TARGET || "http://base.yunhaixingtong.com",
changeOrigin: true,
ws: true
}
// 原有业务接口代理示例(按需启用):
// '/api': {
// target: 'http://basedebug.bestsch.com/BschHome',
// ws: true, //是否跨域
// changeOrigin: true,
// // pathRewrite: { '^/api': '' }
// }
},
overlay: {
//关闭eslint
warnings: false,
errors: false
}
},
pluginOptions: {
// 第三方插件配置
// ...
},
transpileDependencies: [
"view-design"
// "vue-grid-layout",
// "vue-grid-layout.common.js"
] //ie兼容相关
};