签字不显示问题

This commit is contained in:
2026-10-27 20:11:19 +08:00
parent f17f6153b5
commit 3059603186
3 changed files with 360 additions and 197 deletions
@@ -35,22 +35,58 @@ module.exports = {
}, },
data() { data() {
return { return {
//前端唯一标识 当然后端还有用户id作为唯一标识 // 二维码携带本次组件实例的标识,与服务端当前登录用户共同确定签字记录。
id: new Date().getTime() + Math.floor(Math.random() * 10000) + 1, id: new Date().getTime() + Math.floor(Math.random() * 10000) + 1,
signatureAddress: null, signatureAddress: null,
interval: null, interval: null,
connectionWaitResolve: null,
unsubscribeSignature: null,
signatureContent: null, signatureContent: null,
loading: true, loading: true,
} }
}, },
methods: { methods: {
async init() { /**
const {code, data} = await this.$axios.post("/platform/signature/get") * 无请求参数,返回 Promise;响应 code 为0表示成功,data.signature 为已有签字图片地址。
if (code === 0) { * 请求结束后由 mounted 的 finally 统一关闭 loading。
this.signatureContent = data && data.signature */
this.$emit("input", this.signatureContent) init() {
}
this.signatureAddress = origin + "/platform/signature/scanPcCode?id=" + this.id this.signatureAddress = origin + "/platform/signature/scanPcCode?id=" + this.id
return this.$axios.post("/platform/signature/get")
.then((res) => {
if (this._isBeingDestroyed || this._isDestroyed) {
return
}
if (res.code !== 0) {
throw new Error("签字信息加载失败")
}
this.signatureContent = res.data && res.data.signature
this.$emit("input", this.signatureContent)
})
},
// 返回等待完成的 Promise;连接成功、等待30秒或组件销毁时结束等待。
waitForConnection() {
return new Promise((resolve) => {
const startedAt = Date.now()
this.connectionWaitResolve = resolve
const checkReady = () => {
this.interval = null
if (this._isBeingDestroyed || this._isDestroyed ||
(webSocketPubSub.ws && webSocketPubSub.ws.readyState === WebSocket.OPEN)) {
this.connectionWaitResolve = null
resolve()
return
}
if (Date.now() - startedAt >= 30000) {
this.connectionWaitResolve = null
this.$message.warning("签字消息连接超时,请刷新页面重试")
resolve()
return
}
this.interval = setTimeout(checkReady, 100)
}
checkReady()
})
}, },
signatureAgain() { signatureAgain() {
const val = sessionStorage.getItem("h5-scan-code-signature-" + this.id) const val = sessionStorage.getItem("h5-scan-code-signature-" + this.id)
@@ -58,25 +94,42 @@ module.exports = {
this.$emit("input", val) this.$emit("input", val)
} }
}, },
async mounted() { mounted() {
await this.init() this.loading = true
webSocketPubSub.subscribe("h5-scan-code-signature", (data) => { // 先登记消息回调,连接成功后由公共连接加入房间,避免请求期间遗漏订阅。
console.info("ws:收到签字成功消息:", data) this.unsubscribeSignature = webSocketPubSub.subscribe("h5-scan-code-signature", (data) => {
this.signatureContent = data.value this.signatureContent = data.value
this.$emit("input", data.value) this.$emit("input", data.value)
}) })
this.init()
// 可选:轮询或 watch ready 状态 .then(() => {
const checkReady = () => { if (!this._isBeingDestroyed && !this._isDestroyed) {
if (webSocketPubSub.ws && webSocketPubSub.ws.readyState === WebSocket.OPEN) { return this.waitForConnection()
console.info("ws.readyState is open"); }
this.loading = false; })
console.log(this.signatureAddress) .catch(() => {
} else { if (!this._isBeingDestroyed && !this._isDestroyed) {
setTimeout(checkReady, 100); this.$message.error("签字信息加载失败,请刷新页面重试")
} }
}; })
checkReady(); .finally(() => {
if (!this._isBeingDestroyed && !this._isDestroyed) {
this.loading = false
}
})
},
beforeDestroy() {
// pjax 切换页面时取消轮询和订阅,旧组件不得继续接收签字消息。
clearTimeout(this.interval)
this.interval = null
if (this.connectionWaitResolve) {
this.connectionWaitResolve()
this.connectionWaitResolve = null
}
if (this.unsubscribeSignature) {
this.unsubscribeSignature()
this.unsubscribeSignature = null
}
} }
} }
</script> </script>
+142 -86
View File
@@ -443,50 +443,76 @@
constructor() { constructor() {
this.ws = null this.ws = null
this.subscribers = new Map() this.subscribers = new Map()
this.isSubscribed = false // 添加订阅状态标记 this.isSubscribed = false
this.connectTimer = null
this.reconnectTimer = null
this.pingTimer = null
this.isDisposed = false
this.init() this.init()
} }
// 页面只创建一个心跳任务;每次连接自行绑定事件及握手超时。
init() { init() {
this.setupWindowEvents()
this.setupPing()
this.connect()
}
// 正在连接或关闭时等待对应事件,避免同时创建多个连接。
connect() {
if (this.isDisposed || this.reconnectTimer !== null ||
(this.ws && this.ws.readyState !== WebSocket.CLOSED)) {
return
}
try { try {
this.connect() const WS_URL = window.location.host + "${base}/websocket"
const protocol = window.location.protocol === "http:" ? "ws://" : "wss://"
this.ws = new WebSocket(protocol + WS_URL)
this.isSubscribed = false
this.setupEventListeners() this.setupEventListeners()
this.setupPing() const socket = this.ws
this.setupWindowEvents() // 握手最多等待30秒,心跳不得提前关闭仍处于 CONNECTING 的连接。
} catch (ex) { this.connectTimer = setTimeout(() => {
console.info(ex) this.connectTimer = null
if (this.ws === socket && socket.readyState === WebSocket.CONNECTING) {
console.warn("WebSocket 握手超时,关闭当前连接后重试", socket.url)
socket.close()
}
}, 30000)
} catch (error) {
console.error("WebSocket 创建连接失败", error)
this.reconnect()
} }
} }
connect() { /**
const WS_URL = window.location.host + "${base}/websocket" * type 为消息 action 名称,callback 接收服务端消息对象;返回取消本次订阅的方法。
const protocol = window.location.protocol === "http:" ? "ws://" : "wss://" * 连接尚未建立时先保留回调,在 onopen 中统一加入当前用户的消息房间。
this.ws = new WebSocket(protocol + WS_URL) */
}
//订阅
subscribe(type, callback) { subscribe(type, callback) {
if (!this.subscribers.has(type)) { if (!this.subscribers.has(type)) {
this.subscribers.set(type, new Set()) this.subscribers.set(type, new Set())
} }
this.subscribers.get(type).add(callback) const callbacks = this.subscribers.get(type)
callbacks.add(callback)
setTimeout(() => { this.sendJoinMessage()
// 所有订阅完成后,发送 join 消息 return () => {
if (!this.isSubscribed && this.ws.readyState === WebSocket.OPEN) { callbacks.delete(callback)
this.isSubscribed = true if (callbacks.size === 0 && this.subscribers.get(type) === callbacks) {
this.sendJoinMessage() this.subscribers.delete(type)
} }
}, 2000) }
} }
// 事件处理 // 事件绑定到本次连接,旧连接的延迟事件不得影响新连接。
setupEventListeners() { setupEventListeners() {
// 消息处理 const socket = this.ws
this.ws.onmessage = (event) => { socket.onmessage = (event) => {
if (this.isDisposed || this.ws !== socket) {
return
}
try { try {
const data = JSON.parse(event.data) const data = JSON.parse(event.data)
// 触发订阅者的回调
if (this.subscribers.has(data.action)) { if (this.subscribers.has(data.action)) {
this.subscribers.get(data.action).forEach((callback) => { this.subscribers.get(data.action).forEach((callback) => {
try { try {
@@ -500,82 +526,112 @@
console.error("消息解析错误:", error) console.error("消息解析错误:", error)
} }
} }
socket.onopen = () => {
// 连接建立 if (this.isDisposed || this.ws !== socket) {
this.ws.onopen = () => { return
// 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 { clearTimeout(this.connectTimer)
this.connectTimer = null
this.sendJoinMessage()
this.ping()
console.info("WebSocket 连接已建立", socket.url)
}
socket.onerror = () => {
if (!this.isDisposed && this.ws === socket) {
console.warn("WebSocket 连接异常,等待关闭后重连", socket.url)
}
}
socket.onclose = (event) => {
if (this.isDisposed || this.ws !== socket) {
return
}
clearTimeout(this.connectTimer)
this.connectTimer = null
this.isSubscribed = false
console.warn("WebSocket 已关闭", event.code, event.reason)
this.reconnect() this.reconnect()
} }
} }
// 每个已建立的连接只加入一次房间,首次连接及断线恢复都补发已有订阅。
sendJoinMessage() {
if (this.isDisposed || this.isSubscribed || this.subscribers.size === 0 ||
!window.store || !store.state.room || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
return
}
this.send({
room: store.state.room,
action: "join"
})
this.isSubscribed = true
}
// 沿用原心跳间隔,仅对已建立的连接发送心跳。
setupPing() {
this.pingTimer = setInterval(() => this.ping(), (11624317 / 1000) * 2)
}
// CONNECTING/CLOSING 状态分别由握手超时和关闭事件处理。
ping() {
if (this.isDisposed) {
return
}
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
try {
this.ws.send("{}")
} catch (error) {
console.warn("WebSocket 心跳发送失败,等待关闭后重连", error)
this.ws.close()
}
} else if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
this.reconnect()
}
}
// 关闭事件和心跳共用一个重连任务,避免重复定时器创建并行连接。
reconnect() { reconnect() {
if (this.ws) { if (this.isDisposed || this.reconnectTimer !== null ||
this.ws.close() // 确保旧连接已关闭 (this.ws && this.ws.readyState !== WebSocket.CLOSED)) {
return
} }
setTimeout(() => { this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null
this.connect() this.connect()
this.setupEventListeners() }, 3000)
// 如果之前已订阅,重新发送join消息
if (this.isSubscribed) {
setTimeout(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.sendJoinMessage()
}
}, 1000)
}
}, 3000) // 延迟3秒重连
} }
// 窗口事件处理 // 真正离开页面时释放连接和定时器;进入往返缓存时保留页面状态。
setupWindowEvents() { setupWindowEvents() {
let _beforeUnload_time = 0 window.addEventListener("pagehide", (event) => {
window.onbeforeunload = () => { if (event.persisted) {
_beforeUnload_time = new Date().getTime() return
}
window.onunload = () => {
const _gap_time = new Date().getTime() - _beforeUnload_time
if (_gap_time <= 5) {
this.send({
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
action: "left"
})
} }
} this.isDisposed = true
clearTimeout(this.connectTimer)
clearTimeout(this.reconnectTimer)
clearInterval(this.pingTimer)
this.connectTimer = null
this.reconnectTimer = null
this.pingTimer = null
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
try {
this.send({
room: store.state.room,
action: "left"
})
} catch (error) {
console.warn("WebSocket 离开房间消息发送失败", error)
}
}
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
this.ws.close()
}
})
} }
// message 为消息对象或 JSON 字符串;连接未建立时不发送,返回值为 undefined。
send(message) { send(message) {
if (this.ws) { if (this.ws && this.ws.readyState === WebSocket.OPEN) {
const messageStr = typeof message === "string" ? message : JSON.stringify(message) const messageStr = typeof message === "string" ? message : JSON.stringify(message)
this.ws.send(messageStr) this.ws.send(messageStr)
} }
@@ -154,51 +154,76 @@
constructor() { constructor() {
this.ws = null this.ws = null
this.subscribers = new Map() this.subscribers = new Map()
this.isSubscribed = false // 添加订阅状态标记 this.isSubscribed = false
this.connectTimer = null
this.reconnectTimer = null
this.pingTimer = null
this.isDisposed = false
this.init() this.init()
} }
// 页面只创建一个心跳任务;每次连接自行绑定事件及握手超时。
init() { init() {
this.setupWindowEvents()
this.setupPing()
this.connect()
}
// 正在连接或关闭时等待对应事件,避免同时创建多个连接。
connect() {
if (this.isDisposed || this.reconnectTimer !== null ||
(this.ws && this.ws.readyState !== WebSocket.CLOSED)) {
return
}
try { try {
this.connect() const WS_URL = window.location.host + "${base}/websocket"
const protocol = window.location.protocol === "http:" ? "ws://" : "wss://"
this.ws = new WebSocket(protocol + WS_URL)
this.isSubscribed = false
this.setupEventListeners() this.setupEventListeners()
this.setupPing() const socket = this.ws
this.setupWindowEvents() // 握手最多等待30秒,心跳不得提前关闭仍处于 CONNECTING 的连接。
} catch (ex) { this.connectTimer = setTimeout(() => {
console.info(ex) this.connectTimer = null
if (this.ws === socket && socket.readyState === WebSocket.CONNECTING) {
console.warn("WebSocket 握手超时,关闭当前连接后重试", socket.url)
socket.close()
}
}, 30000)
} catch (error) {
console.error("WebSocket 创建连接失败", error)
this.reconnect()
} }
} }
connect() { /**
const WS_URL = window.location.host + "${base}/websocket" * type 为消息 action 名称,callback 接收服务端消息对象;返回取消本次订阅的方法。
// const WS_URL = 'zhgh.jshvc.edu.cn' + "${base}/websocket" * 连接尚未建立时先保留回调,在 onopen 中统一加入当前用户的消息房间。
const protocol = window.location.protocol === "http:" ? "ws://" : "wss://" */
this.ws = new WebSocket(protocol + WS_URL)
}
//订阅
subscribe(type, callback) { subscribe(type, callback) {
if (!this.subscribers.has(type)) { if (!this.subscribers.has(type)) {
this.subscribers.set(type, new Set()) this.subscribers.set(type, new Set())
} }
this.subscribers.get(type).add(callback) const callbacks = this.subscribers.get(type)
callbacks.add(callback)
setTimeout(() => { this.sendJoinMessage()
// 所有订阅完成后,发送 join 消息 return () => {
if (!this.isSubscribed && this.ws.readyState === WebSocket.OPEN) { callbacks.delete(callback)
this.isSubscribed = true if (callbacks.size === 0 && this.subscribers.get(type) === callbacks) {
this.sendJoinMessage() this.subscribers.delete(type)
} }
}, 1000) }
} }
// 事件处理 // 事件绑定到本次连接,旧连接的延迟事件不得影响新连接。
setupEventListeners() { setupEventListeners() {
// 消息处理 const socket = this.ws
this.ws.onmessage = (event) => { socket.onmessage = (event) => {
if (this.isDisposed || this.ws !== socket) {
return
}
try { try {
const data = JSON.parse(event.data) const data = JSON.parse(event.data)
// 触发订阅者的回调
if (this.subscribers.has(data.action)) { if (this.subscribers.has(data.action)) {
this.subscribers.get(data.action).forEach((callback) => { this.subscribers.get(data.action).forEach((callback) => {
try { try {
@@ -212,83 +237,112 @@
console.error("消息解析错误:", error) console.error("消息解析错误:", error)
} }
} }
socket.onopen = () => {
// 连接建立 if (this.isDisposed || this.ws !== socket) {
this.ws.onopen = () => { return
// this.ws.send(
// JSON.stringify({
// room: store.state.room,
// action: "join"
// })
// )
// 连接建立时不立即发送 join,等待订阅完成
this.ping()
console.info('websocket open success')
}
}
// 新增发送 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 { clearTimeout(this.connectTimer)
this.connectTimer = null
this.sendJoinMessage()
this.ping()
console.info("WebSocket 连接已建立", socket.url)
}
socket.onerror = () => {
if (!this.isDisposed && this.ws === socket) {
console.warn("WebSocket 连接异常,等待关闭后重连", socket.url)
}
}
socket.onclose = (event) => {
if (this.isDisposed || this.ws !== socket) {
return
}
clearTimeout(this.connectTimer)
this.connectTimer = null
this.isSubscribed = false
console.warn("WebSocket 已关闭", event.code, event.reason)
this.reconnect() this.reconnect()
} }
} }
// 每个已建立的连接只加入一次房间,首次连接及断线恢复都补发已有订阅。
sendJoinMessage() {
if (this.isDisposed || this.isSubscribed || this.subscribers.size === 0 ||
!window.store || !store.state.room || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
return
}
this.send({
room: store.state.room,
action: "join"
})
this.isSubscribed = true
}
// 沿用原心跳间隔,仅对已建立的连接发送心跳。
setupPing() {
this.pingTimer = setInterval(() => this.ping(), (11624317 / 1000) * 2)
}
// CONNECTING/CLOSING 状态分别由握手超时和关闭事件处理。
ping() {
if (this.isDisposed) {
return
}
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
try {
this.ws.send("{}")
} catch (error) {
console.warn("WebSocket 心跳发送失败,等待关闭后重连", error)
this.ws.close()
}
} else if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
this.reconnect()
}
}
// 关闭事件和心跳共用一个重连任务,避免重复定时器创建并行连接。
reconnect() { reconnect() {
if (this.ws) { if (this.isDisposed || this.reconnectTimer !== null ||
this.ws.close() // 确保旧连接已关闭 (this.ws && this.ws.readyState !== WebSocket.CLOSED)) {
return
} }
setTimeout(() => { this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null
this.connect() this.connect()
this.setupEventListeners() }, 1000)
// 如果之前已订阅,重新发送join消息
if (this.isSubscribed) {
setTimeout(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.sendJoinMessage()
}
}, 500)
}
}, 1000) // 延迟2秒重连
} }
// 窗口事件处理 // 真正离开页面时释放连接和定时器;进入往返缓存时保留页面状态。
setupWindowEvents() { setupWindowEvents() {
let _beforeUnload_time = 0 window.addEventListener("pagehide", (event) => {
window.onbeforeunload = () => { if (event.persisted) {
_beforeUnload_time = new Date().getTime() return
}
window.onunload = () => {
const _gap_time = new Date().getTime() - _beforeUnload_time
if (_gap_time <= 5) {
this.send({
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
action: "left"
})
} }
} this.isDisposed = true
clearTimeout(this.connectTimer)
clearTimeout(this.reconnectTimer)
clearInterval(this.pingTimer)
this.connectTimer = null
this.reconnectTimer = null
this.pingTimer = null
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
try {
this.send({
room: store.state.room,
action: "left"
})
} catch (error) {
console.warn("WebSocket 离开房间消息发送失败", error)
}
}
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
this.ws.close()
}
})
} }
// message 为消息对象或 JSON 字符串;连接未建立时不发送,返回值为 undefined。
send(message) { send(message) {
if (this.ws) { if (this.ws && this.ws.readyState === WebSocket.OPEN) {
const messageStr = typeof message === "string" ? message : JSON.stringify(message) const messageStr = typeof message === "string" ? message : JSON.stringify(message)
this.ws.send(messageStr) this.ws.send(messageStr)
} }