..
This commit is contained in:
@@ -1,520 +1,323 @@
|
||||
<!doctype html>
|
||||
<html lang="${lang,escape}">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="description" content="${AppName!}" />
|
||||
<title>${AppName!}</title>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- import CSS -->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/themify-icons.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css?v=1.0.1" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/a.css?v=1.0.0" />
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<!-- import Vue before Element -->
|
||||
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex/vuex.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex-persistedstate/vuex-persistedstate.umd.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
|
||||
<style>
|
||||
.sub-app-container {
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
min-height: calc(100vh - 64px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
<!-- import ElementUI -->
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/i18n/${lang,escape}.js"></script>
|
||||
.sidebar-menu {
|
||||
width: 250px;
|
||||
background: rgb(35 85 120);
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
height: calc(100vh - 64px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
<!-- import Jquery -->
|
||||
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<!-- pjax是异步加载html片段的工具,模拟前端路由机制 -->
|
||||
<script src="${base!}/assets/platform/plugins/pjax/jquery.pjax.js"></script>
|
||||
<!--nprogress 配合pjax使用-->
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/nprogress/0.2.0/nprogress.css" />
|
||||
<script src="${base!}/assets/platform/plugins/nprogress/nprogress.js"></script>
|
||||
<!-- Array数组增加findIndex等方法,TreeTable用得到,兼容IE8 -->
|
||||
<script src="${base!}/assets/platform/plugins/polyfill/polyfill.js"></script>
|
||||
<!-- 时间日期处理工具类 -->
|
||||
<script src="${base!}/assets/platform/plugins/moment/moment.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/toastr/toastr.js"></script>
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/toastr/toastr.css" />
|
||||
<!--axios-->
|
||||
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
|
||||
<!--图片预览-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/viewerjs/viewer.css" />
|
||||
<script src="${base!}/assets/platform/plugins/viewerjs/viewer.js"></script>
|
||||
<!--签字canvas-->
|
||||
<script src="${base!}/assets/platform/plugins/smooth-signature/index.umd.min.js"></script>
|
||||
<!--二维码-->
|
||||
<script src="${base!}/assets/platform/plugins/vue-qrcode/index.js"></script>
|
||||
<!--富文本编辑器-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/wangEditor4/wangEditor.css" />
|
||||
<script src="${base!}/assets/platform/plugins/wangEditor4/wangEditor.js"></script>
|
||||
<!--g2plot-->
|
||||
<script src="${base!}/assets/platform/plugins/g2plot/g2plot.min.js"></script>
|
||||
<script src="${base!}/components/plugins/sysIconSelector/iconSelect.js"></script>
|
||||
<!--MinDash-->
|
||||
<script src="${base!}/assets/platform/plugins/min-dash/min-dash.js"></script>
|
||||
<!--Sortable-->
|
||||
<script src="${base!}/assets/platform/plugins/sortablejs/sortable.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuedraggable/vuedraggable.umd.min.js"></script>
|
||||
<!--vxe-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-pc-ui.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-table.css" />
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/xe-utils.js"></script>-->
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/vxe-pc-ui.js"></script>-->
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/vue-table.js"></script>-->
|
||||
.menu-header {
|
||||
padding: 20px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
<!-- <link rel="stylesheet" href="https://vxeui.com/umd/vxe-pc-ui@3.1.25/lib/style.min.css" />-->
|
||||
<!-- <link rel="stylesheet" href="https://vxeui.com/umd/vxe-table@3.9.0/lib/style.min.css" />-->
|
||||
<script src="https://vxeui.com/umd/xe-utils@3.5.30/dist/xe-utils.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-pc-ui@3.1.25/lib/index.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-table@3.9.0/lib/index.umd.min.js"></script>
|
||||
.menu-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
<!-- 引入 form-create 和 designer -->
|
||||
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
|
||||
#sidebar-menu .el-scrollbar {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
|
||||
<script src="${base!}/assets/platform/js/main.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/initTableMixins.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
|
||||
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
|
||||
#sidebar-menu .el-scrollbar .el-scrollbar__wrap {
|
||||
overflow-x: hidden;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
<script>
|
||||
window._AMapSecurityConfig = {
|
||||
securityJsCode: "4fa1e1aeabba7eb9518129cf57ab17c1"
|
||||
}
|
||||
</script>
|
||||
<!-- <script-->
|
||||
<!-- type="text/javascript"-->
|
||||
<!-- src="https://webapi.amap.com/maps?v=2.0&key=57f0d098ba1b881ecc436c4cfd23bbbf&plugin=AMap.PolyEditor,AMap.Geolocation,AMap.PlaceSearch"-->
|
||||
<!-- ></script>-->
|
||||
#sidebar-menu .el-menu {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
<script type="text/javascript">
|
||||
Vue.config.devtools = false
|
||||
//ELEMENTUI
|
||||
ELEMENT.locale(ELEMENT.lang["${lang,escape}"])
|
||||
ELEMENT.Dialog.props.closeOnClickModal.default = false
|
||||
ELEMENT.Dialog.props.top.default = "50px"
|
||||
ELEMENT.Table.props.border.default = true
|
||||
ELEMENT.TableColumn.props.showOverflowTooltip = { type: Boolean, default: true }
|
||||
Vue.use(ELEMENT, {
|
||||
zIndex: 20000
|
||||
})
|
||||
#sidebar-menu .el-menu .el-submenu__title {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
Vue.use(FcDesigner)
|
||||
Vue.use(FcDesigner.formCreate)
|
||||
#sidebar-menu .el-menu .el-submenu__title i,
|
||||
#sidebar-menu .el-menu .el-menu-item i {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
// formCreate.create([], {
|
||||
// submitBtn: {
|
||||
// show: false
|
||||
// }
|
||||
// })
|
||||
</script>
|
||||
#sidebar-menu .el-menu .el-submenu__title:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
<!--ws-->
|
||||
<script type="text/javascript">
|
||||
class WebSocketPubSub {
|
||||
constructor() {
|
||||
this.ws = null
|
||||
this.subscribers = new Map()
|
||||
this.isSubscribed = false // 添加订阅状态标记
|
||||
this.init()
|
||||
}
|
||||
#sidebar-menu .el-menu .el-menu-item {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
init() {
|
||||
try {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
this.setupPing()
|
||||
this.setupWindowEvents()
|
||||
} catch (ex) {
|
||||
console.info(ex)
|
||||
}
|
||||
}
|
||||
#sidebar-menu .el-menu .el-menu-item.is-active {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
connect() {
|
||||
const WS_URL = window.location.host + "${base}/websocket"
|
||||
const protocol = window.location.protocol === "http:" ? "ws://" : "wss://"
|
||||
this.ws = new WebSocket(protocol + WS_URL)
|
||||
}
|
||||
#sidebar-menu .el-menu .el-menu-item:focus,
|
||||
.el-menu-item:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
//订阅
|
||||
subscribe(type, callback) {
|
||||
if (!this.subscribers.has(type)) {
|
||||
this.subscribers.set(type, new Set())
|
||||
}
|
||||
this.subscribers.get(type).add(callback)
|
||||
#sidebar-menu .el-menu .el-menu-item a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(this.ws.readyState)
|
||||
// 所有订阅完成后,发送 join 消息
|
||||
if (!this.isSubscribed && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.isSubscribed = true
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
.sub-app-container-main-content {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
background: #f6f6f6;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
setupEventListeners() {
|
||||
// 消息处理
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
// 触发订阅者的回调
|
||||
if (this.subscribers.has(data.action)) {
|
||||
this.subscribers.get(data.action).forEach((callback) => {
|
||||
try {
|
||||
callback(data)
|
||||
} catch (error) {
|
||||
console.error("订阅者回调执行错误:", error)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("消息解析错误:", error)
|
||||
}
|
||||
}
|
||||
.content-header {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
// 连接建立
|
||||
this.ws.onopen = () => {
|
||||
// this.ws.send(
|
||||
// JSON.stringify({
|
||||
// room: store.state.room,
|
||||
// action: "join"
|
||||
// })
|
||||
// )
|
||||
// 连接建立时不立即发送 join,等待订阅完成
|
||||
this.ping()
|
||||
}
|
||||
}
|
||||
.content-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// 新增发送 join 消息的方法
|
||||
sendJoinMessage() {
|
||||
this.send({
|
||||
room: store.state.room,
|
||||
action: "join"
|
||||
})
|
||||
}
|
||||
.content-subtitle {
|
||||
color: #7f8c8d;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
// 心跳检测
|
||||
setupPing() {
|
||||
setInterval(() => this.ping(), (11624317 / 1000) * 2)
|
||||
}
|
||||
.content-body {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
ping() {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this.ws.send("{}")
|
||||
} catch (e) {
|
||||
this.reconnect()
|
||||
}
|
||||
} else {
|
||||
this.reconnect()
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.sub-app-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close() // 确保旧连接已关闭
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
// 如果之前已订阅,重新发送join消息
|
||||
if (this.isSubscribed) {
|
||||
setTimeout(() => {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}, 3000) // 延迟3秒重连
|
||||
}
|
||||
.sidebar-menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 窗口事件处理
|
||||
setupWindowEvents() {
|
||||
let _beforeUnload_time = 0
|
||||
window.onbeforeunload = () => {
|
||||
_beforeUnload_time = new Date().getTime()
|
||||
}
|
||||
window.onunload = () => {
|
||||
const _gap_time = new Date().getTime() - _beforeUnload_time
|
||||
if (_gap_time <= 5) {
|
||||
this.send({
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
action: "left"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
.sub-app-container-main-content {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
send(message) {
|
||||
if (this.ws) {
|
||||
const messageStr = typeof message === "string" ? message : JSON.stringify(message)
|
||||
this.ws.send(messageStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
window.webSocketPubSub = new WebSocketPubSub()
|
||||
</script>
|
||||
|
||||
<!--vuex-->
|
||||
<script type="text/javascript">
|
||||
console.log(${json(@auth.getPrincipalProperty('birthday'))})
|
||||
|
||||
window.sessionStorage.setItem("user",JSON.stringify(${json(@auth.getLogonUser())}))
|
||||
window.store = new Vuex.Store({
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
key: "zhgh-vuex",
|
||||
paths: ["openedMenus", "activeMenuIndex", "activeMenuModuleId"],
|
||||
storage: window.localStorage
|
||||
})
|
||||
],
|
||||
state: {
|
||||
user: JSON.parse(window.sessionStorage.getItem("user")),
|
||||
//当前激活的菜单
|
||||
activeMenuIndex: null,
|
||||
//打开菜单的数组
|
||||
openedMenus: [],
|
||||
//激活的菜单模块
|
||||
activeMenuModuleId: null,
|
||||
//点击过的菜单(历史标签页使用)
|
||||
historyMenuIds: [],
|
||||
//消息列表
|
||||
messages: [],
|
||||
//聊天室
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
//签字
|
||||
signature: {}
|
||||
},
|
||||
getters: {
|
||||
fullMenus: (state) => {
|
||||
return state.user.menus
|
||||
},
|
||||
pcModules: (state) => {
|
||||
return state.user.pcModuleMenus
|
||||
},
|
||||
historyMenuIds: (state) => {
|
||||
return state.historyMenuIds
|
||||
}
|
||||
},
|
||||
|
||||
mutations: {
|
||||
//设置用户信息
|
||||
setUser(state, payload) {
|
||||
state.user = payload
|
||||
},
|
||||
//设置激活的菜单
|
||||
setActiveMenuIndex(state, payload) {
|
||||
state.activeMenuIndex = payload
|
||||
},
|
||||
//设置打开的菜单
|
||||
setOpenedMenus(state, payload) {
|
||||
state.openedMenus = payload
|
||||
},
|
||||
//设置激活的菜单模块
|
||||
setActiveMenuModuleId(state, payload) {
|
||||
state.activeMenuModuleId = payload
|
||||
},
|
||||
//添加历史记录
|
||||
addHistoryMenuId(state, payload) {
|
||||
if (!payload) {
|
||||
return
|
||||
}
|
||||
const exists = state.historyMenuIds.includes(payload)
|
||||
if (!exists) {
|
||||
state.historyMenuIds.push(payload)
|
||||
}
|
||||
},
|
||||
//移除某条历史记录
|
||||
removeHistoryMenuId(state, payload) {
|
||||
state.historyMenuIds = state.historyMenuIds.filter((id) => id !== payload)
|
||||
},
|
||||
//移除多条历史记录
|
||||
removeHistoryMenuIds(state, payload) {
|
||||
state.historyMenuIds = state.historyMenuIds.filter((id) => !payload.includes(id))
|
||||
},
|
||||
//清空历史记录
|
||||
clearHistoryMenuIds(state) {
|
||||
state.historyMenuIds = []
|
||||
state.activeMenuIndex = null
|
||||
state.openedMenus = []
|
||||
pjaxReplace("/platform/home")
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
pjaxRoute({ commit, dispatch, state, getters }, href) {
|
||||
const { pathname, search } = new URL(href, window.location.origin)
|
||||
const matchMenu = getters.fullMenus.find((menu) => menu.href === pathname)
|
||||
if (!matchMenu) {
|
||||
commit("setActiveMenuIndex", null)
|
||||
commit("setOpenedMenus", [])
|
||||
if (!state.activeModuleId && getters.pcModules) {
|
||||
commit("setActiveMenuModuleId", getters.pcModules[0].id)
|
||||
}
|
||||
pjaxReplace(pathname === "/platform/home" ? pathname : pathname)
|
||||
return
|
||||
}
|
||||
//拷贝一份
|
||||
let cloneMenu = JSON.parse(JSON.stringify(matchMenu))
|
||||
//找出所有父菜单包括自己
|
||||
let parentMenus = []
|
||||
while (cloneMenu && cloneMenu.parentId) {
|
||||
cloneMenu = getters.fullMenus.find((m) => m.id === cloneMenu.parentId)
|
||||
if (cloneMenu) {
|
||||
parentMenus.unshift(cloneMenu)
|
||||
}
|
||||
}
|
||||
parentMenus.push(matchMenu)
|
||||
|
||||
//获取到该菜单所属的模块 从一级菜单获取
|
||||
const moduleId = parentMenus[0]?.moduleId
|
||||
//判断下是否跨模块跳转
|
||||
if (moduleId !== state.activeMenuModuleId) {
|
||||
commit("setActiveMenuModuleId", moduleId)
|
||||
}
|
||||
Vue.nextTick(()=>{
|
||||
//重新设置展开菜单
|
||||
commit("setOpenedMenus", parentMenus.map((menu) => menu.id))
|
||||
//设置当前激活菜单
|
||||
commit("setActiveMenuIndex", matchMenu.id)
|
||||
//判断是否加入到历史点击菜单
|
||||
commit("addHistoryMenuId", matchMenu.id)
|
||||
//跳转
|
||||
pjaxReplace(matchMenu.href ? matchMenu.href + (search || "") : "/platform/home")
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--beetl全局变量方法-->
|
||||
<script type="text/javascript">
|
||||
const base = "${base!}"
|
||||
const APP_DOMAIN = "${AppDomain!}"
|
||||
</script>
|
||||
|
||||
<!--vue挂载-->
|
||||
<script type="text/javascript">
|
||||
Vue.prototype.$moment = moment
|
||||
Vue.prototype.$businessTool = businessTool
|
||||
Vue.prototype.$commonUtil = commonUtil
|
||||
Vue.prototype.$auth = commonUtil.authService()
|
||||
Vue.prototype.$axios = commonUtil.axiosService()
|
||||
Vue.prototype.$downLoad = commonUtil.downLoadService
|
||||
</script>
|
||||
|
||||
<!--vue全局组件-->
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
Vue.component("guava", httpVueLoader("/components/plugins/Guava.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-select", httpVueLoader("/components/plugins/sysDict/DictSelect.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-upload", httpVueLoader("/components/plugins/sysUpload/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-select", httpVueLoader("/components/plugins/sysUserSelect/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("table-tool", httpVueLoader("/components/plugins/sysTableTool/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("signature", httpVueLoader("/components/plugins/sysSignature/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("pc-signature", httpVueLoader("/components/plugins/sysSignature/pc.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search", httpVueLoader("/components/plugins/sysPageFormSearch/search.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-item", httpVueLoader("/components/plugins/sysPageFormSearch/searchItem.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-query", httpVueLoader("/components/plugins/sysPageFormSearch/searchQuery.vue?v=" + new Date().getTime()))
|
||||
Vue.component("el-spin", httpVueLoader("/components/plugins/spin/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-opinion-textarea", httpVueLoader("/components/plugins/sysUserAuditOpinion/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("flow-audit", httpVueLoader("/components/plugins/flow/audit/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("icon-selector", httpVueLoader("/components/plugins/sysIconSelector/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("custom-form-field", httpVueLoader("/components/plugins/customFormField/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dynamic-Table-form-eval", httpVueLoader("/components/plugins/sysDynamicTableFormEval/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("condition-group", httpVueLoader("/components/plugins/conditionGroup/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("excel-import", httpVueLoader("/components/plugins/sysImport/excelImport.vue?v=" + new Date().getTime()))
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div class="ele-admin-layout ele-admin-fixed-body ele-admin-side-dark ele-admin-show-tabs ele-admin-responsive">
|
||||
<div class="ele-admin-header" id="ele-admin-header" v-cloak>
|
||||
<div class="ele-admin-logo">
|
||||
<img src="${AppLogo!}" alt="logo" @click="$store.dispatch('pjaxRoute','/platform/home')" />
|
||||
</div>
|
||||
<div class="ele-admin-header-tool">
|
||||
<div class="ele-admin-header-tool-item collapse-menu" style="color: var(--color-white)" @click="collapseMenu">
|
||||
<i class="el-icon-s-unfold"></i>
|
||||
</div>
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<!--菜单模块-->
|
||||
<header-menu-module></header-menu-module>
|
||||
</div>
|
||||
</div>
|
||||
<!--面包屑-->
|
||||
<div class="ele-admin-breadcrumb">
|
||||
<header-bread-crumb></header-bread-crumb>
|
||||
</div>
|
||||
<div class="ele-admin-header-nav"></div>
|
||||
<div class="ele-admin-header-tool">
|
||||
<!--消息通知-->
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<header-message></header-message>
|
||||
</div>
|
||||
<!--个人中心/退出-->
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<header-user-avatar></header-user-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-admin-main">
|
||||
<div class="ele-admin-sidebar" id="ele-admin-sidebar" v-cloak>
|
||||
<sidebar-left-menu></sidebar-left-menu>
|
||||
</div>
|
||||
<div class="ele-admin-body">
|
||||
<div id="ele-admin-tabs">
|
||||
<header-tab-page></header-tab-page>
|
||||
</div>
|
||||
<div class="ele-admin-content">
|
||||
<div class="ele-admin-content-view">
|
||||
<div class="ele-body" id="container">${layoutContent}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ele-admin-shade"
|
||||
onclick="document.querySelector('body >div > div.ele-admin-layout').classList.add('ele-admin-collapse')"
|
||||
></div>
|
||||
</div>
|
||||
<div class="sub-app-container" id="sub-app-container">
|
||||
<!-- 左侧菜单区域 -->
|
||||
<nav class="sidebar-menu" id="sidebar-menu">
|
||||
<div class="menu-header">
|
||||
<h2 class="menu-title">子系统</h2>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
<!--左侧菜单 && 头部 && 标签页-->
|
||||
new Vue({
|
||||
el: "#ele-admin-sidebar",
|
||||
components: {
|
||||
"sidebar-left-menu": httpVueLoader("/components/layout/SideBarLeftMenu.vue?v=" + new Date().getTime())
|
||||
}
|
||||
|
||||
<el-scrollbar>
|
||||
<el-menu unique-opened :default-active="activeMenuIndex" :default-openeds="openedMenus" @select="menuSelect">
|
||||
<menu-list :menus="leftMenus" :depth="0"></menu-list>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</nav>
|
||||
|
||||
<!-- 右侧主体区域 -->
|
||||
<main class="sub-app-container-main-content" id="sub-app-container-main-content">
|
||||
<!-- <header class="content-header">-->
|
||||
<!-- <h1 class="content-title"></h1>-->
|
||||
<!-- <!– <p class="content-subtitle"></p>–>-->
|
||||
<!-- </header>-->
|
||||
|
||||
<div class="content-body" id="sub-app-container-main-content-body">${layoutContent!}</div>
|
||||
</main>
|
||||
|
||||
<!-- 子应用ID -->
|
||||
<div id="sub-app-id" style="display: none"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 初始化pjax加载
|
||||
;(function initPjax() {
|
||||
$(document).pjax("#sidebar-menu a[data-pjax]", "#sub-app-container-main-content-body", {
|
||||
maxCacheLength: 0,
|
||||
push: true,
|
||||
replace: false,
|
||||
fragment: "#sub-app-container-main-content-body",
|
||||
timeout: 8000
|
||||
})
|
||||
|
||||
$(document).on("pjax:send", function () {
|
||||
NProgress.configure({ parent: ".sub-app-container-main-content" })
|
||||
NProgress.start()
|
||||
$("#sub-app-container-main-content").hide()
|
||||
})
|
||||
|
||||
$(document).on("pjax:complete", function () {
|
||||
NProgress.done()
|
||||
$("#sub-app-container-main-content").show()
|
||||
setContentTitle()
|
||||
})
|
||||
|
||||
setContentTitle()
|
||||
})()
|
||||
|
||||
// 设置内容标题
|
||||
function setContentTitle() {
|
||||
// const menu = store.state.user.menus.find((v) => v.href === window.location.pathname)
|
||||
// $("#sub-app-container-main-content .content-title").text(menu?.name)
|
||||
try {
|
||||
const app = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app"))
|
||||
$("#sub-app-container #sidebar-menu .menu-header .menu-title").text(app?.name)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const menuLinks = document.querySelectorAll("#sidebar-menu a")
|
||||
menuLinks.forEach((link) => {
|
||||
link.addEventListener("click", function (e) {
|
||||
const pjax = this.hasAttribute("data-pjax")
|
||||
e.preventDefault()
|
||||
})
|
||||
new Vue({
|
||||
el: "#ele-admin-header",
|
||||
store,
|
||||
components: {
|
||||
"header-message": httpVueLoader("/components/layout/HeaderMessage.vue?v=" + new Date().getTime()),
|
||||
"header-bread-crumb": httpVueLoader("/components/layout/HeaderBreadCrumb.vue?v=" + new Date().getTime()),
|
||||
"header-menu-module": httpVueLoader("/components/layout/HeaderMenuModule.vue?v=" + new Date().getTime()),
|
||||
"header-user-avatar": httpVueLoader("/components/layout/HeaderUserAvatar.vue?v=" + new Date().getTime())
|
||||
},
|
||||
methods: {
|
||||
collapseMenu() {
|
||||
document.querySelector("body >div > div.ele-admin-layout").classList.remove("ele-admin-collapse")
|
||||
})
|
||||
})
|
||||
|
||||
new Vue({
|
||||
el: "#sidebar-menu",
|
||||
components: {
|
||||
"menu-list": {
|
||||
name: "menu-list",
|
||||
template:
|
||||
/*language=HTML*/
|
||||
`
|
||||
<div>
|
||||
<template v-for="(item, index) in menus">
|
||||
<el-submenu :index="item.id" v-if="item.children && item.children.length > 0">
|
||||
<template #title>
|
||||
<i :class="item.icon" v-if="item.icon" :size="18"></i>
|
||||
<i v-else class="el-icon-paperclip"></i>
|
||||
<span>{{ item.name }}</span>
|
||||
</template>
|
||||
<menu-list :menus="item.children" :depth="depth + 1" />
|
||||
</el-submenu>
|
||||
<el-menu-item :index="item.id" v-else>
|
||||
<a :href="item.href" data-pjax>
|
||||
<i :class="item.icon" v-if="item.icon" :size="18"></i>
|
||||
<i v-else :class="item.icon ? item.icon : 'el-icon-menu'"></i>
|
||||
<span>{{ item.name }}</span>
|
||||
</a>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
props: ["menus", "depth"]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menus: [],
|
||||
activeMenuIndex: null,
|
||||
openedMenus: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
leftMenus() {
|
||||
if (this.menus) {
|
||||
return this.menus
|
||||
} else {
|
||||
try {
|
||||
return JSON.parse(window.sessionStorage.getItem("zhgh_sub_app_menus"))
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
})
|
||||
new Vue({
|
||||
el: "#ele-admin-tabs",
|
||||
components: {
|
||||
"header-tab-page": httpVueLoader("/components/layout/HeaderTabPage.vue?v=" + new Date().getTime())
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
menuSelect(index, indexPath) {
|
||||
try {
|
||||
const app = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app"))
|
||||
window.sessionStorage.setItem("zhgh_sub_app_left_menu_active_index-" + app.id, index)
|
||||
window.sessionStorage.setItem("zhgh_sub_app_left_menu_opens-" + app.id, JSON.stringify(indexPath))
|
||||
} catch (e) {}
|
||||
},
|
||||
// 获取菜单
|
||||
getMenus(appId) {
|
||||
if (!appId) return
|
||||
$.get("/platform/sys/user/subAppMenus", { appId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.menus = res.data
|
||||
window.sessionStorage.setItem("zhgh_sub_app_menus", JSON.stringify(res.data))
|
||||
} else {
|
||||
this.menus = []
|
||||
}
|
||||
this.echoMenus()
|
||||
})
|
||||
},
|
||||
|
||||
setAppInfo(app) {
|
||||
if (app) {
|
||||
// 储存到缓存
|
||||
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(app))
|
||||
// 储存到div中
|
||||
document.getElementById("sub-app-id").innerText = app.id
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
// document.querySelector("#sub-app-container .content-title").innerText = app.name
|
||||
},
|
||||
|
||||
// 菜单回显
|
||||
echoMenus() {
|
||||
try {
|
||||
const app = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app"))
|
||||
this.activeMenuIndex = window.sessionStorage.getItem("zhgh_sub_app_left_menu_active_index-" + app.id)
|
||||
this.openedMenus = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app_left_menu_opens-" + app.id)) || []
|
||||
console.log(this.openedMenus)
|
||||
console.log(this.activeMenuIndex)
|
||||
} catch (e) {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 页面加载时获取到正确的菜单
|
||||
const pathname = window.location.pathname
|
||||
if (!pathname.startsWith("/platform/v4/subApp")) {
|
||||
$.get("/platform/sys/user/rootMenuByPath", { pathname }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.setAppInfo(res.data)
|
||||
this.getMenus(res.data?.id)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const appId = GetQueryString("appId")
|
||||
if (!appId) {
|
||||
return
|
||||
}
|
||||
this.getMenus(appId)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
|
||||
@@ -0,0 +1,520 @@
|
||||
<!doctype html>
|
||||
<html lang="${lang,escape}">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="description" content="${AppName!}" />
|
||||
<title>${AppName!}</title>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- import CSS -->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/themify-icons.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css?v=1.0.1" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/a.css?v=1.0.0" />
|
||||
|
||||
<!-- import Vue before Element -->
|
||||
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex/vuex.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex-persistedstate/vuex-persistedstate.umd.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
|
||||
|
||||
<!-- import ElementUI -->
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/i18n/${lang,escape}.js"></script>
|
||||
|
||||
<!-- import Jquery -->
|
||||
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<!-- pjax是异步加载html片段的工具,模拟前端路由机制 -->
|
||||
<script src="${base!}/assets/platform/plugins/pjax/jquery.pjax.js"></script>
|
||||
<!--nprogress 配合pjax使用-->
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/nprogress/0.2.0/nprogress.css" />
|
||||
<script src="${base!}/assets/platform/plugins/nprogress/nprogress.js"></script>
|
||||
<!-- Array数组增加findIndex等方法,TreeTable用得到,兼容IE8 -->
|
||||
<script src="${base!}/assets/platform/plugins/polyfill/polyfill.js"></script>
|
||||
<!-- 时间日期处理工具类 -->
|
||||
<script src="${base!}/assets/platform/plugins/moment/moment.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/toastr/toastr.js"></script>
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/toastr/toastr.css" />
|
||||
<!--axios-->
|
||||
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
|
||||
<!--图片预览-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/viewerjs/viewer.css" />
|
||||
<script src="${base!}/assets/platform/plugins/viewerjs/viewer.js"></script>
|
||||
<!--签字canvas-->
|
||||
<script src="${base!}/assets/platform/plugins/smooth-signature/index.umd.min.js"></script>
|
||||
<!--二维码-->
|
||||
<script src="${base!}/assets/platform/plugins/vue-qrcode/index.js"></script>
|
||||
<!--富文本编辑器-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/wangEditor4/wangEditor.css" />
|
||||
<script src="${base!}/assets/platform/plugins/wangEditor4/wangEditor.js"></script>
|
||||
<!--g2plot-->
|
||||
<script src="${base!}/assets/platform/plugins/g2plot/g2plot.min.js"></script>
|
||||
<script src="${base!}/components/plugins/sysIconSelector/iconSelect.js"></script>
|
||||
<!--MinDash-->
|
||||
<script src="${base!}/assets/platform/plugins/min-dash/min-dash.js"></script>
|
||||
<!--Sortable-->
|
||||
<script src="${base!}/assets/platform/plugins/sortablejs/sortable.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuedraggable/vuedraggable.umd.min.js"></script>
|
||||
<!--vxe-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-pc-ui.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-table.css" />
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/xe-utils.js"></script>-->
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/vxe-pc-ui.js"></script>-->
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/vue-table.js"></script>-->
|
||||
|
||||
<!-- <link rel="stylesheet" href="https://vxeui.com/umd/vxe-pc-ui@3.1.25/lib/style.min.css" />-->
|
||||
<!-- <link rel="stylesheet" href="https://vxeui.com/umd/vxe-table@3.9.0/lib/style.min.css" />-->
|
||||
<script src="https://vxeui.com/umd/xe-utils@3.5.30/dist/xe-utils.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-pc-ui@3.1.25/lib/index.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-table@3.9.0/lib/index.umd.min.js"></script>
|
||||
|
||||
<!-- 引入 form-create 和 designer -->
|
||||
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
|
||||
|
||||
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
|
||||
<script src="${base!}/assets/platform/js/main.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/initTableMixins.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
|
||||
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
|
||||
|
||||
<script>
|
||||
window._AMapSecurityConfig = {
|
||||
securityJsCode: "4fa1e1aeabba7eb9518129cf57ab17c1"
|
||||
}
|
||||
</script>
|
||||
<!-- <script-->
|
||||
<!-- type="text/javascript"-->
|
||||
<!-- src="https://webapi.amap.com/maps?v=2.0&key=57f0d098ba1b881ecc436c4cfd23bbbf&plugin=AMap.PolyEditor,AMap.Geolocation,AMap.PlaceSearch"-->
|
||||
<!-- ></script>-->
|
||||
|
||||
<script type="text/javascript">
|
||||
Vue.config.devtools = false
|
||||
//ELEMENTUI
|
||||
ELEMENT.locale(ELEMENT.lang["${lang,escape}"])
|
||||
ELEMENT.Dialog.props.closeOnClickModal.default = false
|
||||
ELEMENT.Dialog.props.top.default = "50px"
|
||||
ELEMENT.Table.props.border.default = true
|
||||
ELEMENT.TableColumn.props.showOverflowTooltip = { type: Boolean, default: true }
|
||||
Vue.use(ELEMENT, {
|
||||
zIndex: 20000
|
||||
})
|
||||
|
||||
Vue.use(FcDesigner)
|
||||
Vue.use(FcDesigner.formCreate)
|
||||
|
||||
// formCreate.create([], {
|
||||
// submitBtn: {
|
||||
// show: false
|
||||
// }
|
||||
// })
|
||||
</script>
|
||||
|
||||
<!--ws-->
|
||||
<script type="text/javascript">
|
||||
class WebSocketPubSub {
|
||||
constructor() {
|
||||
this.ws = null
|
||||
this.subscribers = new Map()
|
||||
this.isSubscribed = false // 添加订阅状态标记
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
try {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
this.setupPing()
|
||||
this.setupWindowEvents()
|
||||
} catch (ex) {
|
||||
console.info(ex)
|
||||
}
|
||||
}
|
||||
|
||||
connect() {
|
||||
const WS_URL = window.location.host + "${base}/websocket"
|
||||
const protocol = window.location.protocol === "http:" ? "ws://" : "wss://"
|
||||
this.ws = new WebSocket(protocol + WS_URL)
|
||||
}
|
||||
|
||||
//订阅
|
||||
subscribe(type, callback) {
|
||||
if (!this.subscribers.has(type)) {
|
||||
this.subscribers.set(type, new Set())
|
||||
}
|
||||
this.subscribers.get(type).add(callback)
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(this.ws.readyState)
|
||||
// 所有订阅完成后,发送 join 消息
|
||||
if (!this.isSubscribed && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.isSubscribed = true
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
setupEventListeners() {
|
||||
// 消息处理
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
// 触发订阅者的回调
|
||||
if (this.subscribers.has(data.action)) {
|
||||
this.subscribers.get(data.action).forEach((callback) => {
|
||||
try {
|
||||
callback(data)
|
||||
} catch (error) {
|
||||
console.error("订阅者回调执行错误:", error)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("消息解析错误:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 连接建立
|
||||
this.ws.onopen = () => {
|
||||
// this.ws.send(
|
||||
// JSON.stringify({
|
||||
// room: store.state.room,
|
||||
// action: "join"
|
||||
// })
|
||||
// )
|
||||
// 连接建立时不立即发送 join,等待订阅完成
|
||||
this.ping()
|
||||
}
|
||||
}
|
||||
|
||||
// 新增发送 join 消息的方法
|
||||
sendJoinMessage() {
|
||||
this.send({
|
||||
room: store.state.room,
|
||||
action: "join"
|
||||
})
|
||||
}
|
||||
|
||||
// 心跳检测
|
||||
setupPing() {
|
||||
setInterval(() => this.ping(), (11624317 / 1000) * 2)
|
||||
}
|
||||
|
||||
ping() {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this.ws.send("{}")
|
||||
} catch (e) {
|
||||
this.reconnect()
|
||||
}
|
||||
} else {
|
||||
this.reconnect()
|
||||
}
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close() // 确保旧连接已关闭
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
// 如果之前已订阅,重新发送join消息
|
||||
if (this.isSubscribed) {
|
||||
setTimeout(() => {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}, 3000) // 延迟3秒重连
|
||||
}
|
||||
|
||||
// 窗口事件处理
|
||||
setupWindowEvents() {
|
||||
let _beforeUnload_time = 0
|
||||
window.onbeforeunload = () => {
|
||||
_beforeUnload_time = new Date().getTime()
|
||||
}
|
||||
window.onunload = () => {
|
||||
const _gap_time = new Date().getTime() - _beforeUnload_time
|
||||
if (_gap_time <= 5) {
|
||||
this.send({
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
action: "left"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send(message) {
|
||||
if (this.ws) {
|
||||
const messageStr = typeof message === "string" ? message : JSON.stringify(message)
|
||||
this.ws.send(messageStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
window.webSocketPubSub = new WebSocketPubSub()
|
||||
</script>
|
||||
|
||||
<!--vuex-->
|
||||
<script type="text/javascript">
|
||||
console.log(${json(@auth.getPrincipalProperty('birthday'))})
|
||||
|
||||
window.sessionStorage.setItem("user",JSON.stringify(${json(@auth.getLogonUser())}))
|
||||
window.store = new Vuex.Store({
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
key: "zhgh-vuex",
|
||||
paths: ["openedMenus", "activeMenuIndex", "activeMenuModuleId"],
|
||||
storage: window.localStorage
|
||||
})
|
||||
],
|
||||
state: {
|
||||
user: JSON.parse(window.sessionStorage.getItem("user")),
|
||||
//当前激活的菜单
|
||||
activeMenuIndex: null,
|
||||
//打开菜单的数组
|
||||
openedMenus: [],
|
||||
//激活的菜单模块
|
||||
activeMenuModuleId: null,
|
||||
//点击过的菜单(历史标签页使用)
|
||||
historyMenuIds: [],
|
||||
//消息列表
|
||||
messages: [],
|
||||
//聊天室
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
//签字
|
||||
signature: {}
|
||||
},
|
||||
getters: {
|
||||
fullMenus: (state) => {
|
||||
return state.user.menus
|
||||
},
|
||||
pcModules: (state) => {
|
||||
return state.user.pcModuleMenus
|
||||
},
|
||||
historyMenuIds: (state) => {
|
||||
return state.historyMenuIds
|
||||
}
|
||||
},
|
||||
|
||||
mutations: {
|
||||
//设置用户信息
|
||||
setUser(state, payload) {
|
||||
state.user = payload
|
||||
},
|
||||
//设置激活的菜单
|
||||
setActiveMenuIndex(state, payload) {
|
||||
state.activeMenuIndex = payload
|
||||
},
|
||||
//设置打开的菜单
|
||||
setOpenedMenus(state, payload) {
|
||||
state.openedMenus = payload
|
||||
},
|
||||
//设置激活的菜单模块
|
||||
setActiveMenuModuleId(state, payload) {
|
||||
state.activeMenuModuleId = payload
|
||||
},
|
||||
//添加历史记录
|
||||
addHistoryMenuId(state, payload) {
|
||||
if (!payload) {
|
||||
return
|
||||
}
|
||||
const exists = state.historyMenuIds.includes(payload)
|
||||
if (!exists) {
|
||||
state.historyMenuIds.push(payload)
|
||||
}
|
||||
},
|
||||
//移除某条历史记录
|
||||
removeHistoryMenuId(state, payload) {
|
||||
state.historyMenuIds = state.historyMenuIds.filter((id) => id !== payload)
|
||||
},
|
||||
//移除多条历史记录
|
||||
removeHistoryMenuIds(state, payload) {
|
||||
state.historyMenuIds = state.historyMenuIds.filter((id) => !payload.includes(id))
|
||||
},
|
||||
//清空历史记录
|
||||
clearHistoryMenuIds(state) {
|
||||
state.historyMenuIds = []
|
||||
state.activeMenuIndex = null
|
||||
state.openedMenus = []
|
||||
pjaxReplace("/platform/home")
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
pjaxRoute({ commit, dispatch, state, getters }, href) {
|
||||
const { pathname, search } = new URL(href, window.location.origin)
|
||||
const matchMenu = getters.fullMenus.find((menu) => menu.href === pathname)
|
||||
if (!matchMenu) {
|
||||
commit("setActiveMenuIndex", null)
|
||||
commit("setOpenedMenus", [])
|
||||
if (!state.activeModuleId && getters.pcModules) {
|
||||
commit("setActiveMenuModuleId", getters.pcModules[0].id)
|
||||
}
|
||||
pjaxReplace(pathname === "/platform/home" ? pathname : pathname)
|
||||
return
|
||||
}
|
||||
//拷贝一份
|
||||
let cloneMenu = JSON.parse(JSON.stringify(matchMenu))
|
||||
//找出所有父菜单包括自己
|
||||
let parentMenus = []
|
||||
while (cloneMenu && cloneMenu.parentId) {
|
||||
cloneMenu = getters.fullMenus.find((m) => m.id === cloneMenu.parentId)
|
||||
if (cloneMenu) {
|
||||
parentMenus.unshift(cloneMenu)
|
||||
}
|
||||
}
|
||||
parentMenus.push(matchMenu)
|
||||
|
||||
//获取到该菜单所属的模块 从一级菜单获取
|
||||
const moduleId = parentMenus[0]?.moduleId
|
||||
//判断下是否跨模块跳转
|
||||
if (moduleId !== state.activeMenuModuleId) {
|
||||
commit("setActiveMenuModuleId", moduleId)
|
||||
}
|
||||
Vue.nextTick(()=>{
|
||||
//重新设置展开菜单
|
||||
commit("setOpenedMenus", parentMenus.map((menu) => menu.id))
|
||||
//设置当前激活菜单
|
||||
commit("setActiveMenuIndex", matchMenu.id)
|
||||
//判断是否加入到历史点击菜单
|
||||
commit("addHistoryMenuId", matchMenu.id)
|
||||
//跳转
|
||||
pjaxReplace(matchMenu.href ? matchMenu.href + (search || "") : "/platform/home")
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--beetl全局变量方法-->
|
||||
<script type="text/javascript">
|
||||
const base = "${base!}"
|
||||
const APP_DOMAIN = "${AppDomain!}"
|
||||
</script>
|
||||
|
||||
<!--vue挂载-->
|
||||
<script type="text/javascript">
|
||||
Vue.prototype.$moment = moment
|
||||
Vue.prototype.$businessTool = businessTool
|
||||
Vue.prototype.$commonUtil = commonUtil
|
||||
Vue.prototype.$auth = commonUtil.authService()
|
||||
Vue.prototype.$axios = commonUtil.axiosService()
|
||||
Vue.prototype.$downLoad = commonUtil.downLoadService
|
||||
</script>
|
||||
|
||||
<!--vue全局组件-->
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
Vue.component("guava", httpVueLoader("/components/plugins/Guava.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-select", httpVueLoader("/components/plugins/sysDict/DictSelect.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-upload", httpVueLoader("/components/plugins/sysUpload/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-select", httpVueLoader("/components/plugins/sysUserSelect/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("table-tool", httpVueLoader("/components/plugins/sysTableTool/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("signature", httpVueLoader("/components/plugins/sysSignature/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("pc-signature", httpVueLoader("/components/plugins/sysSignature/pc.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search", httpVueLoader("/components/plugins/sysPageFormSearch/search.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-item", httpVueLoader("/components/plugins/sysPageFormSearch/searchItem.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-query", httpVueLoader("/components/plugins/sysPageFormSearch/searchQuery.vue?v=" + new Date().getTime()))
|
||||
Vue.component("el-spin", httpVueLoader("/components/plugins/spin/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-opinion-textarea", httpVueLoader("/components/plugins/sysUserAuditOpinion/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("flow-audit", httpVueLoader("/components/plugins/flow/audit/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("icon-selector", httpVueLoader("/components/plugins/sysIconSelector/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("custom-form-field", httpVueLoader("/components/plugins/customFormField/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dynamic-Table-form-eval", httpVueLoader("/components/plugins/sysDynamicTableFormEval/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("condition-group", httpVueLoader("/components/plugins/conditionGroup/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("excel-import", httpVueLoader("/components/plugins/sysImport/excelImport.vue?v=" + new Date().getTime()))
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div class="ele-admin-layout ele-admin-fixed-body ele-admin-side-dark ele-admin-show-tabs ele-admin-responsive">
|
||||
<div class="ele-admin-header" id="ele-admin-header" v-cloak>
|
||||
<div class="ele-admin-logo">
|
||||
<img src="${AppLogo!}" alt="logo" @click="$store.dispatch('pjaxRoute','/platform/home')" />
|
||||
</div>
|
||||
<div class="ele-admin-header-tool">
|
||||
<div class="ele-admin-header-tool-item collapse-menu" style="color: var(--color-white)" @click="collapseMenu">
|
||||
<i class="el-icon-s-unfold"></i>
|
||||
</div>
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<!--菜单模块-->
|
||||
<header-menu-module></header-menu-module>
|
||||
</div>
|
||||
</div>
|
||||
<!--面包屑-->
|
||||
<div class="ele-admin-breadcrumb">
|
||||
<header-bread-crumb></header-bread-crumb>
|
||||
</div>
|
||||
<div class="ele-admin-header-nav"></div>
|
||||
<div class="ele-admin-header-tool">
|
||||
<!--消息通知-->
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<header-message></header-message>
|
||||
</div>
|
||||
<!--个人中心/退出-->
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<header-user-avatar></header-user-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-admin-main">
|
||||
<div class="ele-admin-sidebar" id="ele-admin-sidebar" v-cloak>
|
||||
<sidebar-left-menu></sidebar-left-menu>
|
||||
</div>
|
||||
<div class="ele-admin-body">
|
||||
<div id="ele-admin-tabs">
|
||||
<header-tab-page></header-tab-page>
|
||||
</div>
|
||||
<div class="ele-admin-content">
|
||||
<div class="ele-admin-content-view">
|
||||
<div class="ele-body" id="container">${layoutContent}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ele-admin-shade"
|
||||
onclick="document.querySelector('body >div > div.ele-admin-layout').classList.add('ele-admin-collapse')"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
<!--左侧菜单 && 头部 && 标签页-->
|
||||
new Vue({
|
||||
el: "#ele-admin-sidebar",
|
||||
components: {
|
||||
"sidebar-left-menu": httpVueLoader("/components/layout/SideBarLeftMenu.vue?v=" + new Date().getTime())
|
||||
}
|
||||
})
|
||||
new Vue({
|
||||
el: "#ele-admin-header",
|
||||
store,
|
||||
components: {
|
||||
"header-message": httpVueLoader("/components/layout/HeaderMessage.vue?v=" + new Date().getTime()),
|
||||
"header-bread-crumb": httpVueLoader("/components/layout/HeaderBreadCrumb.vue?v=" + new Date().getTime()),
|
||||
"header-menu-module": httpVueLoader("/components/layout/HeaderMenuModule.vue?v=" + new Date().getTime()),
|
||||
"header-user-avatar": httpVueLoader("/components/layout/HeaderUserAvatar.vue?v=" + new Date().getTime())
|
||||
},
|
||||
methods: {
|
||||
collapseMenu() {
|
||||
document.querySelector("body >div > div.ele-admin-layout").classList.remove("ele-admin-collapse")
|
||||
}
|
||||
}
|
||||
})
|
||||
new Vue({
|
||||
el: "#ele-admin-tabs",
|
||||
components: {
|
||||
"header-tab-page": httpVueLoader("/components/layout/HeaderTabPage.vue?v=" + new Date().getTime())
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,630 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.apps-hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/*border-radius: 8px 8px 0 0;*/
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.apps-hero img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.apps-container {
|
||||
display: flex;
|
||||
/*height: calc(100vh - 64px - 48px - 180px);*/
|
||||
background-color: #fff;
|
||||
/*border-radius: 0 0 8px 8px;*/
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.apps-sidebar {
|
||||
width: 220px;
|
||||
background-color: #f7f7f7;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.apps-sidebar-item {
|
||||
padding: 16px 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.apps-sidebar-item.active {
|
||||
background-color: #e6f7ff;
|
||||
color: var(--color-primary);
|
||||
border-right: 2px solid #1890ff;
|
||||
}
|
||||
|
||||
.apps-sidebar-item:hover:not(.active) {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.apps-content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.apps-header {
|
||||
padding: 0 0 20px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.apps-search-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.apps-search {
|
||||
position: relative;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.apps-search input {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 15px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.apps-search .search-icon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.apps-filter {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.apps-filter-title {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.apps-filter-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.filter-tag {
|
||||
padding: 5px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-tag.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.filter-tag:not(.active) {
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filter-tag:not(.active):hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.apps-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
grid-auto-rows: min-content;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.app-card {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.app-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 15px;
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.app-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.app-icon i {
|
||||
color: var(--color-primary);
|
||||
font-size: 38px;
|
||||
}
|
||||
|
||||
.favorite-icon {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 18px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.favorite-icon.active {
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.favorite-icon:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.app-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.app-desc {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.alphabet-filter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.alphabet-item {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.alphabet-item.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.alphabet-item:not(.active) {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.alphabet-item:hover:not(.active) {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.app-status {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #ff9800;
|
||||
color: #fff;
|
||||
padding: 2px 10px;
|
||||
font-size: 12px;
|
||||
border-bottom-left-radius: 8px;
|
||||
}
|
||||
|
||||
.apps-wrapper {
|
||||
/*margin: 0 auto;*/
|
||||
/*margin: 24px;*/
|
||||
/*padding: 24px;*/
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.no-results {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 300px;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.no-results-content {
|
||||
text-align: center;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.no-results i {
|
||||
color: #c0c4cc;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.no-results .title {
|
||||
font-size: 16px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-results .hint {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.page-item.disabled {
|
||||
color: #c0c4cc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.page-item.disabled:hover {
|
||||
border-color: #d9d9d9;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="apps-wrapper" id="app">
|
||||
<div class="apps-hero">
|
||||
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i8hmpaq7wmewu9c7w3pt9qhu3gb8bkcvow0" alt="应用中心" />
|
||||
</div>
|
||||
|
||||
<div class="apps-container">
|
||||
<!-- Left Sidebar -->
|
||||
<div class="apps-sidebar">
|
||||
<div
|
||||
v-for="category in allCategories"
|
||||
:key="category.id"
|
||||
:class="['apps-sidebar-item', activeCategory === category.id ? 'active' : '']"
|
||||
@click="changeCategory(category.id)"
|
||||
>
|
||||
<i :class="category.icon"></i>
|
||||
{{ category.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="apps-content">
|
||||
<div class="apps-header">
|
||||
<div class="apps-search-container">
|
||||
<div class="apps-search">
|
||||
<input type="text" v-model="searchKeyword" @keyup.enter="search" placeholder="请输入内容" />
|
||||
<i class="el-icon-search search-icon" @click="search"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 字母过滤 -->
|
||||
<div class="apps-filter">
|
||||
<div class="apps-filter-title">首字母:</div>
|
||||
<div class="apps-filter-tags alphabet-filter">
|
||||
<div :class="['filter-tag', activeAlphabet === '' ? 'active' : '']" @click="changeAlphabet('')">全部</div>
|
||||
<div
|
||||
v-for="letter in alphabets"
|
||||
:key="letter"
|
||||
:class="['alphabet-item', activeAlphabet === letter ? 'active' : '']"
|
||||
@click="changeAlphabet(letter)"
|
||||
>
|
||||
{{ letter }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 应用网格 -->
|
||||
<div class="apps-grid" v-if="applications.length > 0">
|
||||
<div v-for="app in applications" :key="app.id" class="app-card" @click.stop="openApp(app)">
|
||||
<div :class="['app-icon', app.iconType]">
|
||||
<i v-if="app.icon && !app.picIcon" :class="app.icon"></i>
|
||||
<img v-else-if="app.picIcon" :src="app.picIcon" :alt="app.name || ''" />
|
||||
<i v-else class="fa fa-skype"></i>
|
||||
</div>
|
||||
<div class="app-info">
|
||||
<div class="app-title">{{ app.name }}</div>
|
||||
<div class="app-desc" v-if="app.department">{{ app.department }}</div>
|
||||
</div>
|
||||
<div class="app-status" v-if="app.status">{{ app.status }}</div>
|
||||
<div
|
||||
:class="['favorite-icon', app.isFavorite ? 'active' : '']"
|
||||
@click.stop="toggleFavorite(app.id)"
|
||||
:title="app.isFavorite ? '取消收藏' : '收藏'"
|
||||
>
|
||||
<i :class="app.isFavorite ? 'el-icon-star-on' : 'el-icon-star-off'"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div class="loading-container" v-if="loading">
|
||||
<i class="el-icon-loading"></i>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
<!-- 无搜索结果 -->
|
||||
<div class="no-results" v-else-if="applications.length === 0">
|
||||
<div class="no-results-content">
|
||||
<i class="el-icon-document" style="font-size: 48px"></i>
|
||||
<p class="title">没有找到匹配的应用</p>
|
||||
<p class="hint">请尝试不同的搜索词或筛选条件</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data: {
|
||||
// 应用列表
|
||||
applications: [],
|
||||
// 分类列表
|
||||
categories: [
|
||||
{ id: "all", name: "全部应用", icon: "fa fa-th-large" },
|
||||
{ id: "favorites", name: "我的收藏", icon: "fa fa-star" },
|
||||
{ id: "recommended", name: "推荐应用", icon: "fa fa-thumbs-up" }
|
||||
],
|
||||
// 动态加载的分类
|
||||
dynamicCategories: [],
|
||||
// 当前选中的分类
|
||||
activeCategory: "all",
|
||||
// 字母表筛选
|
||||
alphabets: [
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z"
|
||||
],
|
||||
activeAlphabet: "",
|
||||
// 搜索关键词
|
||||
searchKeyword: "",
|
||||
// 分页相关
|
||||
currentPage: 1,
|
||||
pageSize: 15,
|
||||
totalPage: 1,
|
||||
total: 0,
|
||||
// 加载状态
|
||||
loading: true
|
||||
},
|
||||
computed: {
|
||||
// 所有分类(固定分类 + 动态分类)
|
||||
allCategories() {
|
||||
return [...this.categories, ...this.dynamicCategories]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 页面加载时获取分类和应用数据
|
||||
this.loadCategories()
|
||||
this.loadApps()
|
||||
},
|
||||
methods: {
|
||||
// 加载分类数据
|
||||
loadCategories() {
|
||||
$.get("/platform/v4/apps/categories")
|
||||
.then((result) => {
|
||||
if (result.code === 0) {
|
||||
this.dynamicCategories = result.data || []
|
||||
} else {
|
||||
console.error("获取应用分类失败:", result.msg)
|
||||
}
|
||||
})
|
||||
.fail((error) => {
|
||||
console.error("获取应用分类异常:", error)
|
||||
})
|
||||
},
|
||||
|
||||
// 加载应用数据
|
||||
loadApps() {
|
||||
this.loading = true
|
||||
|
||||
// 构建请求参数
|
||||
const params = {
|
||||
categoryId:
|
||||
this.activeCategory === "all"
|
||||
? ""
|
||||
: this.activeCategory === "favorites" || this.activeCategory === "recommended"
|
||||
? this.activeCategory
|
||||
: this.activeCategory,
|
||||
letter: this.activeAlphabet,
|
||||
keyword: this.searchKeyword
|
||||
}
|
||||
|
||||
// 将参数转换为URL查询参数
|
||||
const queryString = Object.keys(params)
|
||||
.filter((key) => params[key] !== null && params[key] !== undefined && params[key] !== "")
|
||||
.map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(params[key]))
|
||||
.join("&")
|
||||
|
||||
// 如果是"我的收藏"分类
|
||||
if (this.activeCategory === "favorites") {
|
||||
this.loadFavorites()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是"推荐应用"分类
|
||||
if (this.activeCategory === "recommended") {
|
||||
$.get("/platform/v4/apps/recommended")
|
||||
.then((result) => {
|
||||
if (result.code === 0) {
|
||||
this.applications = result.data.list || []
|
||||
} else {
|
||||
console.error("获取推荐应用失败:", result.msg)
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
.fail((error) => {
|
||||
console.error("获取推荐应用异常:", error)
|
||||
this.loading = false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 发送请求获取普通应用列表
|
||||
$.get("/platform/v4/apps/list?" + queryString)
|
||||
.then((result) => {
|
||||
if (result.code === 0) {
|
||||
this.applications = result.data || []
|
||||
} else {
|
||||
console.error("获取应用列表失败:", result.msg)
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
.fail((error) => {
|
||||
console.error("获取应用列表异常:", error)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 加载收藏的应用
|
||||
loadFavorites() {
|
||||
$.get("/platform/v4/apps/favorite")
|
||||
.then((result) => {
|
||||
if (result.code === 0) {
|
||||
this.applications = result.data || []
|
||||
} else {
|
||||
console.error("获取收藏应用失败:", result.msg)
|
||||
}
|
||||
})
|
||||
.fail((error) => {
|
||||
console.error("获取收藏应用异常:", error)
|
||||
})
|
||||
},
|
||||
|
||||
// 切换应用收藏状态
|
||||
toggleFavorite(appId) {
|
||||
const app = this.applications.find((a) => a.id === appId)
|
||||
if (!app) return
|
||||
|
||||
const url = app.isFavorite ? "/platform/v4/apps/removeFavorite" : "/platform/v4/apps/addFavorite"
|
||||
const params = { appId: appId }
|
||||
|
||||
$.post(url, params)
|
||||
.then((result) => {
|
||||
if (result.code === 0) {
|
||||
app.isFavorite = !app.isFavorite
|
||||
this.$message.success(app.isFavorite ? "收藏成功" : "取消收藏成功")
|
||||
} else {
|
||||
console.error(app.isFavorite ? "取消收藏失败:" : "收藏失败:", result.msg)
|
||||
}
|
||||
})
|
||||
.fail((error) => {
|
||||
console.error(app.isFavorite ? "取消收藏异常:" : "收藏异常:", error)
|
||||
})
|
||||
},
|
||||
|
||||
// 切换应用分类
|
||||
changeCategory(categoryId) {
|
||||
this.activeCategory = categoryId
|
||||
this.currentPage = 1
|
||||
|
||||
// 如果是"我的收藏"分类
|
||||
if (categoryId === "favorites") {
|
||||
this.loadFavorites()
|
||||
} else {
|
||||
this.loadApps()
|
||||
}
|
||||
},
|
||||
|
||||
// 切换应用字母
|
||||
changeAlphabet(letter) {
|
||||
this.activeAlphabet = letter
|
||||
this.currentPage = 1
|
||||
this.loadApps()
|
||||
},
|
||||
|
||||
// 搜索
|
||||
search() {
|
||||
this.loadApps()
|
||||
},
|
||||
|
||||
// 打开应用
|
||||
openApp(app) {
|
||||
// 储存到缓存
|
||||
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(app))
|
||||
// 新标签页打开
|
||||
window.open("/platform/v4/subApp?appId=" + app.id, "_blank")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,619 @@
|
||||
<!doctype html>
|
||||
<html lang="${lang,escape}">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="description" content="${AppName!}" />
|
||||
<title>${AppName!}</title>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<!-- import CSS -->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/root.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/themify-icons.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css" />
|
||||
|
||||
<!-- import Vue before Element -->
|
||||
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex/vuex.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex-persistedstate/vuex-persistedstate.umd.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
|
||||
|
||||
<!-- import ElementUI -->
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/i18n/${lang,escape}.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/override.css" />
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css" />
|
||||
|
||||
<!-- import Jquery -->
|
||||
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<!-- pjax是异步加载html片段的工具,模拟前端路由机制 -->
|
||||
<script src="${base!}/assets/platform/plugins/pjax/jquery.pjax.js"></script>
|
||||
<!--nprogress 配合pjax使用-->
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/nprogress/0.2.0/nprogress.css" />
|
||||
<script src="${base!}/assets/platform/plugins/nprogress/nprogress.js"></script>
|
||||
|
||||
<!-- 农历插件 -->
|
||||
<script src="${base!}/assets/platform/plugins/lunar/lunar.js"></script>
|
||||
<!-- 时间日期处理工具类 -->
|
||||
<script src="${base!}/assets/platform/plugins/moment/moment.min.js"></script>
|
||||
<!--axios-->
|
||||
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
|
||||
<!--图片预览-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/viewerjs/viewer.css" />
|
||||
<script src="${base!}/assets/platform/plugins/viewerjs/viewer.js"></script>
|
||||
<!--签字canvas-->
|
||||
<script src="${base!}/assets/platform/plugins/smooth-signature/index.umd.min.js"></script>
|
||||
<!--二维码-->
|
||||
<script src="${base!}/assets/platform/plugins/vue-qrcode/index.js"></script>
|
||||
<!--富文本编辑器-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/wangEditor4/wangEditor.css" />
|
||||
<script src="${base!}/assets/platform/plugins/wangEditor4/wangEditor.js"></script>
|
||||
<!--g2plot-->
|
||||
<script src="${base!}/assets/platform/plugins/g2plot/g2plot.min.js"></script>
|
||||
<script src="${base!}/components/plugins/sysIconSelector/iconSelect.js"></script>
|
||||
<!--MinDash-->
|
||||
<script src="${base!}/assets/platform/plugins/min-dash/min-dash.js"></script>
|
||||
<!--Sortable-->
|
||||
<script src="${base!}/assets/platform/plugins/sortablejs/sortable.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuedraggable/vuedraggable.umd.min.js"></script>
|
||||
<!--vxe-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-pc-ui.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-table.css" />
|
||||
|
||||
<script src="https://vxeui.com/umd/xe-utils@3.5.30/dist/xe-utils.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-pc-ui@3.1.25/lib/index.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-table@3.9.0/lib/index.umd.min.js"></script>
|
||||
|
||||
<!-- 引入 form-create 和 designer -->
|
||||
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
|
||||
|
||||
<!-- <script src="${base!}/assets/platform/plugins/snaker/SnakerflowDesigner.umd.min.js"></script>-->
|
||||
|
||||
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
|
||||
<script src="${base!}/assets/platform/js/main.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/initTableMixins.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
|
||||
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
window._AMapSecurityConfig = {
|
||||
securityJsCode: "4fa1e1aeabba7eb9518129cf57ab17c1"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
Vue.config.devtools = false
|
||||
ELEMENT.locale(ELEMENT.lang["${lang,escape}"])
|
||||
ELEMENT.Dialog.props.closeOnClickModal.default = false
|
||||
ELEMENT.Dialog.props.top.default = "50px"
|
||||
ELEMENT.Table.props.border.default = true
|
||||
ELEMENT.TableColumn.props.showOverflowTooltip = { type: Boolean, default: true }
|
||||
Vue.use(ELEMENT, {
|
||||
zIndex: 20000
|
||||
})
|
||||
Vue.use(FcDesigner)
|
||||
Vue.use(FcDesigner.formCreate)
|
||||
</script>
|
||||
|
||||
<!--ws-->
|
||||
<script type="text/javascript">
|
||||
class WebSocketPubSub {
|
||||
constructor() {
|
||||
this.ws = null
|
||||
this.subscribers = new Map()
|
||||
this.isSubscribed = false // 添加订阅状态标记
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
try {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
this.setupPing()
|
||||
this.setupWindowEvents()
|
||||
} catch (ex) {
|
||||
console.info(ex)
|
||||
}
|
||||
}
|
||||
|
||||
connect() {
|
||||
const WS_URL = window.location.host + "${base}/websocket"
|
||||
const protocol = window.location.protocol === "http:" ? "ws://" : "wss://"
|
||||
this.ws = new WebSocket(protocol + WS_URL)
|
||||
}
|
||||
|
||||
//订阅
|
||||
subscribe(type, callback) {
|
||||
if (!this.subscribers.has(type)) {
|
||||
this.subscribers.set(type, new Set())
|
||||
}
|
||||
this.subscribers.get(type).add(callback)
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(this.ws.readyState)
|
||||
// 所有订阅完成后,发送 join 消息
|
||||
if (!this.isSubscribed && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.isSubscribed = true
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
setupEventListeners() {
|
||||
// 消息处理
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
// 触发订阅者的回调
|
||||
if (this.subscribers.has(data.action)) {
|
||||
this.subscribers.get(data.action).forEach((callback) => {
|
||||
try {
|
||||
callback(data)
|
||||
} catch (error) {
|
||||
console.error("订阅者回调执行错误:", error)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("消息解析错误:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 连接建立
|
||||
this.ws.onopen = () => {
|
||||
// this.ws.send(
|
||||
// JSON.stringify({
|
||||
// room: store.state.room,
|
||||
// action: "join"
|
||||
// })
|
||||
// )
|
||||
// 连接建立时不立即发送 join,等待订阅完成
|
||||
this.ping()
|
||||
}
|
||||
}
|
||||
|
||||
// 新增发送 join 消息的方法
|
||||
sendJoinMessage() {
|
||||
this.send({
|
||||
room: store.state.room,
|
||||
action: "join"
|
||||
})
|
||||
}
|
||||
|
||||
// 心跳检测
|
||||
setupPing() {
|
||||
setInterval(() => this.ping(), (11624317 / 1000) * 2)
|
||||
}
|
||||
|
||||
ping() {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this.ws.send("{}")
|
||||
} catch (e) {
|
||||
this.reconnect()
|
||||
}
|
||||
} else {
|
||||
this.reconnect()
|
||||
}
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close() // 确保旧连接已关闭
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
// 如果之前已订阅,重新发送join消息
|
||||
if (this.isSubscribed) {
|
||||
setTimeout(() => {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}, 3000) // 延迟3秒重连
|
||||
}
|
||||
|
||||
// 窗口事件处理
|
||||
setupWindowEvents() {
|
||||
let _beforeUnload_time = 0
|
||||
window.onbeforeunload = () => {
|
||||
_beforeUnload_time = new Date().getTime()
|
||||
}
|
||||
window.onunload = () => {
|
||||
const _gap_time = new Date().getTime() - _beforeUnload_time
|
||||
if (_gap_time <= 5) {
|
||||
this.send({
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
action: "left"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send(message) {
|
||||
if (this.ws) {
|
||||
const messageStr = typeof message === "string" ? message : JSON.stringify(message)
|
||||
this.ws.send(messageStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.webSocketPubSub = new WebSocketPubSub()
|
||||
</script>
|
||||
|
||||
<!--vuex-->
|
||||
<script type="text/javascript">
|
||||
window.store = new Vuex.Store({
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
key: "zhgh-vuex"
|
||||
})
|
||||
],
|
||||
state: {
|
||||
user: JSON.parse(window.sessionStorage.getItem("user")),
|
||||
//消息列表
|
||||
messages: [],
|
||||
//聊天室
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
//签字
|
||||
signature: {}
|
||||
},
|
||||
getters: {
|
||||
fullMenus: (state) => {
|
||||
return state.user.menus
|
||||
}
|
||||
},
|
||||
|
||||
mutations: {
|
||||
//设置用户信息
|
||||
setUser(state, payload) {
|
||||
state.user = payload
|
||||
}
|
||||
},
|
||||
actions: {}
|
||||
})
|
||||
|
||||
$.get("/platform/sys/user/getLogonUser").then((res) => {
|
||||
if (res.code === 0) {
|
||||
window.sessionStorage.setItem("user", JSON.stringify(res.data))
|
||||
store.commit("setUser", res.data)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--beetl全局变量方法-->
|
||||
<script type="text/javascript">
|
||||
const base = "${base!}"
|
||||
const APP_DOMAIN = "${AppDomain!}"
|
||||
</script>
|
||||
|
||||
<!--vue挂载-->
|
||||
<script type="text/javascript">
|
||||
Vue.prototype.$moment = moment
|
||||
Vue.prototype.$businessTool = businessTool
|
||||
Vue.prototype.$commonUtil = commonUtil
|
||||
Vue.prototype.$auth = commonUtil.authService()
|
||||
Vue.prototype.$axios = commonUtil.axiosService()
|
||||
Vue.prototype.$downLoad = commonUtil.downLoadService
|
||||
</script>
|
||||
|
||||
<!--vue全局组件-->
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
Vue.component("guava", httpVueLoader("/components/plugins/Guava.vue?v=" + new Date().getTime()))
|
||||
Vue.component("enum-tag", httpVueLoader("/components/plugins/sysEnum/EnumTag.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-select", httpVueLoader("/components/plugins/sysDict/DictSelect.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-upload", httpVueLoader("/components/plugins/sysUpload/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-select", httpVueLoader("/components/plugins/sysUserSelect/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("table-tool", httpVueLoader("/components/plugins/sysTableTool/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("signature", httpVueLoader("/components/plugins/sysSignature/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("pc-signature", httpVueLoader("/components/plugins/sysSignature/pc.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search", httpVueLoader("/components/plugins/sysPageFormSearch/search.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-item", httpVueLoader("/components/plugins/sysPageFormSearch/searchItem.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-query", httpVueLoader("/components/plugins/sysPageFormSearch/searchQuery.vue?v=" + new Date().getTime()))
|
||||
Vue.component("el-spin", httpVueLoader("/components/plugins/spin/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-opinion-textarea", httpVueLoader("/components/plugins/sysUserAuditOpinion/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("flow-audit", httpVueLoader("/components/plugins/flow/audit/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("icon-selector", httpVueLoader("/components/plugins/sysIconSelector/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("custom-form-field", httpVueLoader("/components/plugins/customFormField/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dynamic-Table-form-eval", httpVueLoader("/components/plugins/sysDynamicTableFormEval/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("condition-group", httpVueLoader("/components/plugins/conditionGroup/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("excel-import", httpVueLoader("/components/plugins/sysImport/excelImport.vue?v=" + new Date().getTime()))
|
||||
Vue.component("flow-form-button", httpVueLoader("/components/plugins/flowable/formButton.vue?v=" + new Date().getTime()))
|
||||
Vue.component("snaker-flow", httpVueLoader("/components/plugins/snaker/snakerFlow.vue?v=" + new Date().getTime()))
|
||||
Vue.component("snaker-flow-task-form-action", httpVueLoader("/components/plugins/snaker/snakerFlowTaskFormAction.vue?v=" + new Date().getTime()))
|
||||
Vue.component("snaker-flow-history-approval", httpVueLoader("/components/plugins/snaker/snakerFlowHisApproval.vue?v=" + new Date().getTime()))
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.v4-header {
|
||||
background-color: rgb(0, 109, 185);
|
||||
box-shadow: 0 2px 10px rgba(0, 109, 185, 0.3);
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.v4-logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 40px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v4-logo {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.v4-nav {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v4-nav-item {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-decoration: none;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease;
|
||||
column-gap: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v4-nav-item:hover,
|
||||
.v4-nav-item.active {
|
||||
color: #ffffff;
|
||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.v4-nav-item.active::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-user-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
height: 100%;
|
||||
max-width: 310px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.v4-notification {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.v4-notification:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.v4-notification i {
|
||||
font-size: 18px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.v4-notification:hover i {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #ffffff;
|
||||
color: rgb(0, 109, 185);
|
||||
border-radius: 10px;
|
||||
padding: 0 6px;
|
||||
font-size: 12px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.v4-user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 12px;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.v4-user-info:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.v4-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgb(0, 109, 185);
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.v4-user-name {
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v4-logout {
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
padding: 0 14px;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v4-logout:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-content {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.v4-left-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.v4-nav {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.v4-user-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v4-user-section {
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.v4-nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="v4-header">
|
||||
<div class="v4-left-section">
|
||||
<div class="v4-logo-container">
|
||||
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i843ts1nwl3w2n5iw31ohn2n10cripk3gw0" alt="Logo" class="v4-logo" />
|
||||
</div>
|
||||
|
||||
<nav class="v4-nav">
|
||||
<a href="/platform/v4/home" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-home"></i>
|
||||
首页
|
||||
</a>
|
||||
<a href="/platform/v4/apps" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-th-large"></i>
|
||||
应用中心
|
||||
</a>
|
||||
<a href="/flow/todoCenter" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-th-large"></i>
|
||||
待办中心
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="v4-user-section">
|
||||
<div class="v4-user-info">
|
||||
<span class="v4-user-name">${@auth.getPrincipalProperty('username')} (${@auth.getPrincipalProperty('loginname')})</span>
|
||||
<!-- <i class="fa fa-angle-down"></i> -->
|
||||
</div>
|
||||
<div class="v4-logout">
|
||||
<i class="fa fa-sign-out"></i>
|
||||
退出
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="v4-content">
|
||||
<!-- 页面内容区域 -->
|
||||
<div class="ele-body" id="container">${layoutContent}</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// 处理导航项的active状态
|
||||
$(document).ready(function () {
|
||||
// 初始化:根据当前URL设置active状态
|
||||
function setActiveNavItem() {
|
||||
var currentPath = window.location.pathname
|
||||
$(".v4-nav-item").removeClass("active")
|
||||
$(".v4-nav-item").each(function () {
|
||||
if ($(this).attr("href") === currentPath) {
|
||||
$(this).addClass("active")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 页面加载时设置active状态
|
||||
setActiveNavItem()
|
||||
|
||||
// 为导航项添加点击事件
|
||||
$(".v4-nav-item").on("click", function () {
|
||||
$(".v4-nav-item").removeClass("active")
|
||||
$(this).addClass("active")
|
||||
})
|
||||
|
||||
$(document).pjax(".v4-nav a[data-pjax]", "#container", {
|
||||
maxCacheLength: 0,
|
||||
push: false,
|
||||
replace: true,
|
||||
fragment: "#container",
|
||||
timeout: 8000
|
||||
})
|
||||
|
||||
// 使用pjax的事件来处理导航切换
|
||||
$(document).on("pjax:complete", function () {
|
||||
setActiveNavItem()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,310 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/themify-icons.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css" />
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/fonts/element-icons.woff" />
|
||||
|
||||
<!-- import Vue before Element -->
|
||||
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex/vuex.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex-persistedstate/vuex-persistedstate.umd.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
|
||||
|
||||
<!-- import ElementUI -->
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/i18n/${lang,escape}.js"></script>
|
||||
|
||||
<!-- import Jquery -->
|
||||
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<!-- pjax是异步加载html片段的工具,模拟前端路由机制 -->
|
||||
<script src="${base!}/assets/platform/plugins/pjax/jquery.pjax.js"></script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.v4-header {
|
||||
background-color: rgb(0, 109, 185);
|
||||
box-shadow: 0 2px 10px rgba(0, 109, 185, 0.3);
|
||||
padding: 0 24px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.v4-logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 40px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v4-logo {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.v4-nav {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v4-nav-item {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-decoration: none;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.v4-nav-item:hover,
|
||||
.v4-nav-item.active {
|
||||
color: #ffffff;
|
||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.v4-nav-item.active::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-user-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v4-notification {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.v4-notification:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.v4-notification i {
|
||||
font-size: 18px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.v4-notification:hover i {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #ffffff;
|
||||
color: rgb(0, 109, 185);
|
||||
border-radius: 10px;
|
||||
padding: 0 6px;
|
||||
font-size: 12px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.v4-user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 12px;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.v4-user-info:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.v4-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgb(0, 109, 185);
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.v4-user-name {
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-logout {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
cursor: pointer;
|
||||
padding: 0 14px;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.v4-logout:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-content {
|
||||
/* padding: 24px; */
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.v4-left-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.v4-header {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.v4-nav {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.v4-user-section {
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.v4-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v4-user-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="v4-header">
|
||||
<div class="v4-left-section">
|
||||
<div class="v4-logo-container">
|
||||
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i843ts1nwl3w2n5iw31ohn2n10cripk3gw0" alt="Logo" class="v4-logo" />
|
||||
</div>
|
||||
|
||||
<nav class="v4-nav">
|
||||
<a href="/platform/v4/home" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-home"></i>
|
||||
首页
|
||||
</a>
|
||||
<a href="/platform/v4/apps" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-th-large"></i>
|
||||
应用中心
|
||||
</a>
|
||||
<a href="/platform/v4/apps" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-comments"></i>
|
||||
咨询中心
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="v4-user-section">
|
||||
<div class="v4-user-info">
|
||||
<div class="v4-avatar">
|
||||
<i class="fa fa-user"></i>
|
||||
</div>
|
||||
<span class="v4-user-name">用户名</span>
|
||||
<i class="fa fa-angle-down"></i>
|
||||
</div>
|
||||
<div class="v4-logout">
|
||||
<i class="fa fa-sign-out"></i>
|
||||
退出
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="v4-content">
|
||||
<!-- 页面内容区域 -->
|
||||
<div class="ele-body" id="container">${layoutContent}</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// 处理导航项的active状态
|
||||
$(document).ready(function() {
|
||||
// 初始化:根据当前URL设置active状态
|
||||
function setActiveNavItem() {
|
||||
var currentPath = window.location.pathname;
|
||||
$('.v4-nav-item').removeClass('active');
|
||||
$('.v4-nav-item').each(function() {
|
||||
if ($(this).attr('href') === currentPath) {
|
||||
$(this).addClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载时设置active状态
|
||||
setActiveNavItem();
|
||||
|
||||
// 使用pjax的事件来处理导航切换
|
||||
$(document).on('pjax:complete', function() {
|
||||
setActiveNavItem();
|
||||
});
|
||||
|
||||
// 为导航项添加点击事件
|
||||
$('.v4-nav-item').on('click', function() {
|
||||
$('.v4-nav-item').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,300 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.sub-app-container {
|
||||
/*max-width: 80%;*/
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
/*border-radius: 8px;*/
|
||||
/*box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);*/
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
min-height: calc(100vh - 64px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
width: 250px;
|
||||
background: rgb(64 109 157);
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-header {
|
||||
padding: 20px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu-link {
|
||||
padding: 12px 20px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
border-left: 3px solid transparent;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-link > span {
|
||||
white-space: nowrap; /* 禁止换行 */
|
||||
overflow: hidden; /* 隐藏超出部分 */
|
||||
text-overflow: ellipsis; /* 显示省略号 */
|
||||
flex: 1; /* 让文字部分占据剩余空间 */
|
||||
}
|
||||
|
||||
.menu-link:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-left-color: #fff;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.menu-link.active {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-left-color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.submenu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.submenu.active {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.submenu .menu-link {
|
||||
padding-left: 50px;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.submenu .menu-link:hover {
|
||||
padding-left: 55px;
|
||||
}
|
||||
|
||||
.submenu .submenu .menu-link {
|
||||
padding-left: 70px;
|
||||
}
|
||||
|
||||
.submenu .submenu .menu-link:hover {
|
||||
padding-left: 75px;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.menu-toggle.active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 30px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content-subtitle {
|
||||
color: #7f8c8d;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content-body {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sub-app-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="sub-app-container" id="sub-app-container">
|
||||
<!-- 左侧菜单区域 -->
|
||||
<nav class="sidebar-menu">
|
||||
<div class="menu-header">
|
||||
<h2 class="menu-title">福利管理系统</h2>
|
||||
</div>
|
||||
|
||||
<!--# if(!isEmpty(menus)) { #-->
|
||||
<ul class="menu-list">
|
||||
<!--# for(menu in menus){ #-->
|
||||
<li class="menu-item" data-id="${menu.id}">
|
||||
<!--# if(!isEmpty(menu.children)) { #-->
|
||||
<a href="${menu.href!}" class="menu-link">
|
||||
<!--# if(!isEmpty(menu.icon)) { #-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } else {#-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } #-->
|
||||
<span>${menu.name}</span>
|
||||
<i class="menu-toggle fa fa-chevron-down"></i>
|
||||
</a>
|
||||
<ul class="submenu">
|
||||
<!--# for(child in menu.children){ #-->
|
||||
<li class="menu-item" data-id="${child.id}">
|
||||
<a href="${child.href!}" data-pjax class="menu-link">
|
||||
<!--# if(!isEmpty(child.icon)) { #-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } else {#-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } #-->
|
||||
<span>${child.name}</span>
|
||||
</a>
|
||||
</li>
|
||||
<!--# } #-->
|
||||
</ul>
|
||||
<!--# } else { #-->
|
||||
<a href="${menu.href!}" data-pjax class="menu-link">
|
||||
<!--# if(!isEmpty(menu.icon)) { #-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } else {#-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } #-->
|
||||
<span>${menu.name}</span>
|
||||
</a>
|
||||
<!--# } #-->
|
||||
</li>
|
||||
<!--# } #-->
|
||||
</ul>
|
||||
<!--# } #-->
|
||||
</nav>
|
||||
|
||||
<!-- 右侧主体区域 -->
|
||||
<main class="main-content">
|
||||
<header class="content-header">
|
||||
<h1 class="content-title">福利项目管理</h1>
|
||||
<!-- <p class="content-subtitle"></p>-->
|
||||
</header>
|
||||
|
||||
<div class="content-body" id="sub-app-container-main-content">${layoutContent!}</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const menuLinks = document.querySelectorAll(".menu-link")
|
||||
const contentTitle = document.querySelector(".content-title")
|
||||
menuLinks.forEach((link) => {
|
||||
link.addEventListener("click", function (e) {
|
||||
// 检查是否有 data-pjax 属性
|
||||
const pjax = this.hasAttribute("data-pjax")
|
||||
|
||||
if (pjax) {
|
||||
// 如果有 pjax 属性,不阻止默认行为,让 pjax 处理
|
||||
// 但仍然处理菜单样式更新
|
||||
} else {
|
||||
// 如果没有 pjax 属性,阻止默认跳转
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
// 移除所有active状态
|
||||
menuLinks.forEach((l) => l.classList.remove("active"))
|
||||
|
||||
if (!pjax) {
|
||||
// 子目录
|
||||
const submenu = this.nextElementSibling
|
||||
// 子目录有没有展开
|
||||
const isActive = submenu && submenu.classList.contains("active")
|
||||
|
||||
// 关闭所有子菜单
|
||||
document.querySelectorAll(".submenu").forEach((sm) => {
|
||||
sm.classList.remove("active")
|
||||
})
|
||||
document.querySelectorAll(".menu-toggle").forEach((mt) => {
|
||||
mt.classList.remove("active")
|
||||
})
|
||||
|
||||
// 切换当前子菜单状态
|
||||
if (submenu && !isActive) {
|
||||
submenu.classList.add("active")
|
||||
const menuToggle = this.querySelector(".menu-toggle")
|
||||
if (menuToggle) {
|
||||
// 给 menu-toggle 增加 class
|
||||
menuToggle.classList.add("active")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 业务菜单
|
||||
// 更新主内容区域标题
|
||||
// const menuText = this.textContent.trim()
|
||||
// contentTitle.textContent = menuText
|
||||
}
|
||||
|
||||
// 添加当前点击的active状态
|
||||
this.classList.add("active")
|
||||
})
|
||||
})
|
||||
|
||||
$(document).pjax("#sub-app-container .sidebar-menu a[data-pjax]", "#sub-app-container-main-content", {
|
||||
maxCacheLength: 0,
|
||||
push: true,
|
||||
replace: false,
|
||||
fragment: "#sub-app-container-main-content",
|
||||
timeout: 8000
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
Reference in New Issue
Block a user