Files
zhgh_ypi/src/main/resources/views/layouts/platform_h5.html
T

548 lines
25 KiB
HTML

<!doctype html>
<html lang="${lang,escape}">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"/>
<title>智慧工会</title>
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vant/index.css"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/h5.css"/>
<link rel="stylesheet" href="https://cdn.staticfile.net/animate.css/4.1.1/animate.css"/>
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/pdf/pdfh5.css">
<link rel="stylesheet" href="https://res.wx.qq.com/open/libs/weui/2.2.0/weui.min.css">
<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>
<script src="${base!}/assets/platform/plugins/vant/index.js"></script>
<script src="${base!}/assets/platform/plugins/moment/moment.min.js"></script>
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
<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>
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index.css"/>
<!-- import ElementUI -->
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
<!-- 引入 form-create 和 designer -->
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js?v=0.0.1"></script>
<script src="${base!}/assets/platform/plugins/form-create/designer/index.umd.js"></script>
<!--图片预览-->
<link rel="stylesheet" href="${base!}/assets/platform/plugins/viewerjs/viewer.css" />
<script src="${base!}/assets/platform/plugins/viewerjs/viewer.js"></script>
<!--pdf平铺展示-->
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdf.js" type="text/javascript" charset="utf-8"></script>
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdf.worker.js" type="text/javascript" charset="utf-8"></script>
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdfh5.js" type="text/javascript" charset="utf-8"></script>
<!--h5扫码-->
<script src="${base!}/assets/platform/plugins/html5-qrcode/html5-qrcode.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/util/coordinateUtil.js"></script>
<script src="${base!}/assets/platform/js/main.js"></script>
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
<script src="${base!}/assets/platform/js/util/h5AutoShowError.js"></script>
<!--富文本编辑器-->
<link rel="stylesheet" href="${base!}/assets/platform/plugins/wangEditor4/wangEditor.css"/>
<script src="${base!}/assets/platform/plugins/wangEditor4/wangEditor.js"></script>
<!--签字canvas-->
<script src="${base!}/assets/platform/plugins/smooth-signature/index.umd.min.js"></script>
<!--g2plot-->
<script src="${base!}/assets/platform/plugins/g2plot/g2plot.min.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/goober@2.1.10/dist/goober.umd.js"></script>-->
<script
type="text/javascript"
src="https://webapi.amap.com/maps?v=2.0&key=57f0d098ba1b881ecc436c4cfd23bbbf&plugin=AMap.PolyEditor,AMap.Geolocation"
></script>
<script src="https://map.qq.com/api/gljs?v=2.exp&key=MLLBZ-GQECI-ASXG7-5GNOZ-XW2OF-H5BVH"></script>
<script nonce="${cspNonce!}">
Vue.config.devtools = true
// 禁用vant的表单验证行内错误信息显示
vant.Form.props.showErrorMessage.default = false
vant.ActionSheet.props.lazyRender.default = false
function toggleShowBar() {
if (["/platform/h5/home", "/platform/home"].includes(window.location.pathname)) {
document.querySelector("body>.main-page").classList.add("page-showtabbar")
} else {
document.querySelector("body>.main-page").classList.remove("page-showtabbar")
}
}
// toast多例模式
vant.Toast.allowMultiple()
// H5 统一弹框历史栈:仅接入的页面启用,捕获阶段消费弹框返回,防止 PJAX 重载页面。
window.h5PopupHistory = (() => {
let active = null
let serial = 0
let pending = null
let registered = null
let releasing = Promise.resolve()
let skippingClosedHistory = false
const sync = (closedKeys) => {
if (active) active.onChange(active.keys.slice(), closedKeys)
}
// 回退到指定层级,Promise 在 popstate 同步完成后结束,提交/跳转可安全等待。
const rewind = (depth) => {
if (pending) return pending.promise.then(() => rewind(depth))
if (!active || active.keys.length <= depth) return Promise.resolve()
let resolveBack
const promise = new Promise((resolve) => { resolveBack = resolve })
pending = { promise, resolve: resolveBack }
window.history.go(depth - active.keys.length)
return promise
}
window.addEventListener("popstate", (event) => {
// 清除前进进入的残留记录时,紧随其后的回退也不能触发 PJAX。
if (skippingClosedHistory) {
skippingClosedHistory = false
event.stopImmediatePropagation()
return
}
const mark = event.state && event.state.h5Popup
if ((!active || !active.keys.length) && !mark) return
// 只处理当前页面弹框记录;普通业务页面的返回继续由原 PJAX 处理。
event.stopImmediatePropagation()
if (mark && (!active || mark.owner !== active.id)) {
skippingClosedHistory = true
window.history.go(-mark.depth)
return
}
const depth = mark ? mark.depth : 0
if (depth > active.keys.length) {
// 已关闭的弹框历史不重新打开,前进到残留记录时退回业务页记录。
skippingClosedHistory = true
window.history.go(active.keys.length - depth)
return
}
const closedKeys = active.keys.slice(depth)
active.keys = active.keys.slice(0, depth)
const completed = pending
pending = null
sync(closedKeys)
if (completed) completed.resolve()
}, true)
return {
/** 注册页面同步回调,参数为仍打开的弹框 key 数组;返回页面专用操作句柄。 */
register(onChange) {
if (registered) throw new Error("上一个页面的弹框历史尚未注销")
const owner = { id: Date.now() + "-" + (++serial), keys: [], onChange, disposed: false }
registered = owner
// 旧页面注销时先释放注册资格;新页面的弹框操作等待旧历史回退完成,不能接管其 popstate。
const ready = active ? releasing.then(() => {
if (!owner.disposed) active = owner
}) : Promise.resolve()
if (!active) active = owner
const runOwned = (work) => {
const run = () => active === owner && !owner.disposed ? work() : undefined
return active === owner ? run() : ready.then(run)
}
let destroyed = null
return {
// key 是页面内唯一弹框名称;相同弹框重复打开不会重复压栈。
open(key) {
const open = () => {
if (active !== owner || owner.disposed || owner.keys.includes(key)) return
owner.keys.push(key)
window.history.pushState({ h5Popup: { owner: owner.id, depth: owner.keys.length } }, "", window.location.href)
}
return Promise.resolve(runOwned(() => pending ? pending.promise.then(open) : open()))
},
// 关闭指定弹框及其上层弹框,返回 Promise<void>,保持后进先出。
close(key) {
return Promise.resolve(runOwned(() => {
const index = owner.keys.indexOf(key)
return index < 0 ? undefined : rewind(index)
}))
},
clear() {
return Promise.resolve(runOwned(() => rewind(0)))
},
destroy() {
if (destroyed) return destroyed
owner.disposed = true
if (registered === owner) registered = null
// 无弹框时同步注销,保证 PJAX 同次替换中下一个页面可以立即注册。
if (active === owner && !owner.keys.length && !pending) {
active = null
destroyed = Promise.resolve()
return destroyed
}
// 新页面可能尚在等待更早页面释放历史;连续切换也必须按顺序完成注销。
destroyed = ready.then(() => active === owner ? rewind(0) : undefined).then(() => {
if (active === owner) active = null
})
releasing = destroyed
return destroyed
}
}
},
hasOpen() { return !!(active && active.keys.length) },
back() { return active ? rewind(Math.max(0, active.keys.length - 1)) : Promise.resolve() },
clear() { return rewind(0) }
}
})()
// 带弹框离开页面时先回收历史,再交给原链接/PJAX,避免返回到已关闭的弹框。
document.addEventListener("click", (event) => {
const link = event.target.closest && event.target.closest("a[href]")
if (!link || !window.h5PopupHistory.hasOpen() || event.defaultPrevented ||
event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey ||
link.target === "_blank" || link.hasAttribute("download")) return
const href = link.getAttribute("href")
if (!href || href.startsWith("#") || href.startsWith("javascript:")) return
event.preventDefault()
event.stopImmediatePropagation()
window.h5PopupHistory.clear().then(() => link.click())
}, true)
//初始化pjax
;(function initPjax() {
if ($.support.pjax) {
$(document).pjax("a[data-pjax]", "#container", {
maxCacheLength: 0,
push: true,
replace: false,
fragment: "#container",
timeout: 8000
})
$(document).on("pjax:send", function () {
NProgress.start()
window.customStyleList = document.querySelectorAll('style[id^="style-"]')
})
$(document).on("pjax:complete", function () {
if (window.customStyleList) {
window.customStyleList.forEach((style) => {
// 页面替换或前一次清理可能已移除节点,仅清理仍挂载的旧样式。
if (style.parentNode) style.parentNode.removeChild(style)
})
window.customStyleList = null
}
NProgress.done()
toggleShowBar()
})
// 普通页面前进/后退交给 PJAX 自带的 popstate.pjax 处理。
// 弹框历史只有 h5Popup 标记,没有页面 url/container,不能再次发起页面请求。
}
})()
function pjaxReplace(url, data = null) {
if (window.h5PopupHistory.hasOpen()) {
window.h5PopupHistory.clear().then(() => pjaxReplace(url, data))
return
}
$.pjax({
url: url,
container: "#container",
maxCacheLength: 0,
push: true,
replace: false,
fragment: "#container",
timeout: 8000,
data: data
})
}
function createListLoading(icon = "https://preview.qiantucdn.com/58pic/35/39/61/62K58PICb88i68HEwVnm5_PIC2018.gif!qt480") {
return vant.Toast.loading({
icon: icon,
forbidClick: true,
loadingType: "spinner",
duration: 0,
className: "custom-list-loading"
})
}
function historyBack(func) {
// 弹框存在时只退一层;等待历史同步期间的重复点击不会穿透到业务页面。
if (window.h5PopupHistory.hasOpen()) {
window.h5PopupHistory.back()
return
}
if (window.history.length > 1) {
window.history.back()
} else {
window.location.href = "/platform/h5/home"
}
func && typeof func === "function" && func()
}
Vue.mixin({
methods: {
historyBack: function(func = ()=>{}) {
historyBack(func);
}
},
created() {
// 只处理有 pageForm 且 pageForm 有 approval 字段的组件
if (
this.pageForm &&
typeof this.pageForm === 'object' &&
('approval' in this.pageForm || 'isAudit' in this.pageForm)
) {
// 从 URL 查询参数中读取 approval
const urlParams = new URLSearchParams(window.location.search);
const approvalParam = urlParams.get('tab');
if (approvalParam === 'done') {
this.$set(this.pageForm, 'approval', true);
this.$set(this.pageForm, 'isAudit', true);
} else if (approvalParam === 'todo') {
this.$set(this.pageForm, 'approval', false);
this.$set(this.pageForm, 'isAudit', false);
}
}
}
})
</script>
<!--ws-->
<script nonce="${cspNonce!}" 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(() => {
// 所有订阅完成后,发送 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 nonce="${cspNonce!}">
const store = new Vuex.Store({
plugins: [createPersistedState({
key: "zhgh-h5-vuex",
paths: ["activeTarBar"],
storage: window.localStorage
})],
state: {
user: JSON.parse(window.sessionStorage.getItem("user")),
//聊天室
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
//当前激活的tarBar
activeTarBar: null
},
mutations: {
//设置用户信息
setUser(state, payload) {
state.user = payload
},
setActiveTarBar(state, payload) {
state.activeTarBar = payload
}
},
actions: {
logout() {
window.localStorage.removeItem("zhgh-h5-vuex")
window.location.href = "/platform/login/logout"
}
}
})
$.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>
<script nonce="${cspNonce!}" type="text/javascript">
//挂载
Vue.prototype.$moment = moment
Vue.prototype.$businessTool = businessTool
Vue.prototype.$commonUtil = commonUtil
Vue.prototype.$coordinateUtil = coordinateUtil
Vue.prototype.$auth = commonUtil.authService()
Vue.prototype.$axios = commonUtil.axiosService()
Vue.prototype.$downLoad = commonUtil.downLoadService.bind(commonUtil)
Vue.prototype.$pjaxReplace = pjaxReplace
</script>
<script nonce="${cspNonce!}">
Vue.component("rich-text", httpVueLoader("/components/plugins/sysRichTextView/index.vue?v=" + new Date().getTime()))
Vue.component("h5-file-upload", httpVueLoader("/components/plugins/sysUpload/h5Index.vue?v=" + new Date().getTime()))
Vue.component("h5-signature", httpVueLoader("/components/plugins/sysSignature/h5Index.vue?v=" + new Date().getTime()))
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + new Date().getTime()))
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + new Date().getTime()))
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + new Date().getTime()))
Vue.component("year-van-dropdown-item", httpVueLoader("/components/plugins/vantMore/yearVanDropDownItem.vue?v=" + new Date().getTime()))
Vue.component("van-text-dialog", httpVueLoader("/components/plugins/vantMore/vantTextDialog.vue?v=" + new Date().getTime()))
Vue.component("enum-tag", httpVueLoader("/components/plugins/sysEnum/EnumTag.vue?v=" + new Date().getTime()))
Vue.component("table-list", httpVueLoader("/components/plugins/h5/TableList.vue?v=" + new Date().getTime()))
Vue.component("table-column", httpVueLoader("/components/plugins/h5/TableColumn.vue?v=" + new Date().getTime()))
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/H5Index.vue?v=" + new Date().getTime()))
</script>
</head>
<body>
<div class="main-page page-showtabbar">
<div class="page-wrapper">
<div id="container">${layoutContent}</div>
</div>
</div>
<script nonce="${cspNonce!}">
toggleShowBar()
</script>
</body>
</html>