Commit 15797eb3 authored by baoxiaode's avatar baoxiaode

个推

parent 7a09fd1f
Pipeline #11064 passed with stage
......@@ -7,8 +7,8 @@ module.exports = {
'plugin:vue/essential'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
'no-console': 'off',
'no-debugger': 'off'
},
parserOptions: {
parser: 'babel-eslint'
......
......@@ -875,7 +875,7 @@ const routes = [{
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
base: '/wys/',
routes
})
......
......@@ -3,7 +3,7 @@ import request from '@/utils/axios'
/*get请求*/
export function getFun(url,params) {
return request({
url: '/hse/app-api/'+url,
url: '/hse/wys/app-api/'+url,
method: 'get',
params
})
......@@ -11,7 +11,7 @@ export function getFun(url,params) {
/* post请求 */
export function postFun(url,data) {
return request({
url: '/hse/app-api/'+url,
url: '/hse/wys/app-api/'+url,
method: 'post',
data
})
......@@ -43,7 +43,7 @@ export function putFunTwo(url,data) {
/* put请求 */
export function putFun(url,data) {
return request({
url: '/hse/app-api/'+url,
url: '/hse/wys/app-api/'+url,
method: 'put',
data
})
......
......@@ -11,7 +11,7 @@ export function getFun(url,params) {
/* post请求 */
export function postWork(url,data) {
return request({
url: '/hse/app-api/'+url,
url: '/hse/wys/app-api/'+url,
method: 'post',
data
})
......
......@@ -3,15 +3,30 @@ import { getCurrentInstance } from 'vue'
class UniPush {
constructor() {
this.isUniApp = typeof uni !== 'undefined'
this.is5PlusApp = typeof plus !== 'undefined' && !this.isUniApp
this.is5PlusApp = false // 初始化为 false,在 init 中检测
this.clientId = null
this.pushEnabled = false
this.forceNotificationMode = false // 是否使用 force_notification:true 模式
this.messageListeners = [] // 消息接收监听器列表
this.clickListeners = [] // 消息点击监听器列表
this.init()
}
async init() {
// 优先检查 URL 参数中的 deviceId (用于 Webview 嵌套场景)
this._checkUrlParams()
// 等待 5+ 环境就绪
if (!this.isUniApp) {
await this.waitForPlusReady()
this.is5PlusApp = typeof plus !== 'undefined'
}
if (!this.isUniApp && !this.is5PlusApp) {
if (this.clientId) {
console.log('当前为Web环境,已从URL获取clientId:', this.clientId)
return
}
console.warn('当前环境不是uni-app或5+App,无法使用推送功能')
return
}
......@@ -19,24 +34,13 @@ class UniPush {
try {
// 获取客户端标识
const info = await this.getClientInfo()
// 显示客户端ID(仅开发环境使用)
// if (process.env.NODE_ENV === 'development') {
if (this.isUniApp) {
uni.showModal({
title: 'uni-push客户端ID',
content:JSON.stringify(info),
showCancel: false
})
} else if (this.is5PlusApp && plus.nativeUI) {
plus.nativeUI.alert(JSON.stringify(info), () => {}, '5+App推送客户端ID')
}
// }
console.log('推送客户端信息:', info)
this.clientId = info.clientId
console.log('推送客户端ID:', this.clientId)
// 监听推送消息
console.log('开始监听推送消息')
this.setupPushListener()
this.pushEnabled = true
......@@ -45,72 +49,120 @@ class UniPush {
}
}
waitForPlusReady() {
return new Promise((resolve) => {
if (typeof plus !== 'undefined') {
resolve()
} else {
document.addEventListener('plusready', () => {
resolve()
}, false)
// 超时保护,避免非 5+ 环境一直等待
setTimeout(resolve, 3000)
}
})
}
// 获取客户端信息
getClientInfo() {
async getClientInfo() {
console.log('直接调用5+app')
this._get5PlusPushClientId();
console.log('直接调用5+app结束')
if (this.isUniApp) {
console.log('直接调用uni-app')
return this._getUniPushClientId()
}
if (this.is5PlusApp) {
// 再次确保 plus 就绪
console.log('直接调用5+app2')
if (typeof plus === 'undefined') await this.waitForPlusReady()
return this._get5PlusPushClientId()
}
throw new Error('不支持的环境 (uni=' + typeof uni + ', plus=' + typeof plus + ')')
}
// 获取 UniApp 推送标识
_getUniPushClientId() {
return new Promise((resolve, reject) => {
if (this.isUniApp) {
// uni-app 环境
uni.getPushClientId({
success: (res) => {
resolve({
clientId: res.cid,
platform: 'uniapp'
})
},
fail: (err) => {
reject(err)
}
})
} else if (this.is5PlusApp) {
// 5+App 环境 - 优先尝试使用原生推送API
if (plus.push && typeof plus.push.getClientInfoAsync === 'function') {
plus.push.getClientInfoAsync((result) => {
if (result && result.clientid) {
// 成功获取到推送客户端ID
resolve({
clientId: result.clientid,
platform: '5plus',
appid: result.appid,
token: result.token
})
} else {
// 原生推送不可用,回退到自定义生成
this.fallbackToCustomClientId(resolve, reject)
}
}, (error) => {
// 获取失败,回退到自定义生成
console.warn('plus.push.getClientInfoAsync 失败:', error)
this.fallbackToCustomClientId(resolve, reject)
})
} else {
// 原生推送API不可用,使用自定义生成
this.fallbackToCustomClientId(resolve, reject)
uni.getPushClientId({
success: (res) => resolve({ clientId: res.cid, platform: 'uniapp' }),
fail: reject
})
})
}
// 获取 5+App 推送标识
async _get5PlusPushClientId() {
try {
plus.push.getClientInfoAsync((info) => {
console.log('5+App 推送客户端信息:', info)
let cid = info["clientid"];
});
if (!plus.push || typeof plus.push.getClientInfoAsync !== 'function') {
throw new Error('原生推送API不可用')
}
const result = await new Promise((resolve, reject) => {
plus.push.getClientInfoAsync(resolve, reject)
})
if (result && result.clientid) {
return {
clientId: result.clientid,
platform: '5plus',
appid: result.appid,
token: result.token
}
} else {
reject(new Error('不支持的环境'))
}
})
throw new Error('未获取到 clientid')
} catch (error) {
console.warn('plus.push.getClientInfoAsync 失败:', error)
this._handlePushConfigError(error)
return this._getFallbackIdentity()
}
}
// 回退到自定义客户端ID生成
fallbackToCustomClientId(resolve, reject) {
// 处理推送配置错误
_handlePushConfigError(error) {
const errorMsgLower = (error && error.message) ? error.message.toLowerCase() : ''
if (errorMsgLower.includes('check appkey') || errorMsgLower.includes('appid')) {
const msg = '【严重错误】UniPush配置无效!\n1. 请检查 manifest.json 中 AppID/AppKey/AppSecret 是否与 DCloud 后台一致。\n2. 包名(Package Name)必须与后台配置一致。\n3. 修改配置后,必须【重新制作自定义基座】才能生效!'
console.error(msg)
if (plus.nativeUI) {
plus.nativeUI.toast('配置错误:请检查AppKey并重新打基座', {duration: 'long'})
}
} else if (errorMsgLower.includes('no push platform')) {
const msg = '【严重错误】Push模块未启用!请在HBuilderX的manifest.json中 -> App模块配置 -> 勾选 Push(消息推送) 模块。'
console.error(msg)
if (plus.nativeUI) {
plus.nativeUI.toast('配置错误:未勾选Push模块', {duration: 'long'})
}
}
}
// 获取回退标识(设备信息)
_getFallbackIdentity() {
try {
const deviceInfo = plus.device
const clientId = this.generate5PlusClientId()
resolve({
return {
clientId: clientId,
platform: '5plus',
deviceId: deviceInfo.uuid || deviceInfo.imei || 'unknown',
fallback: true // 标记为回退方案
})
fallback: true
}
} catch (error) {
reject(new Error('5+App环境获取设备信息失败'))
throw new Error('5+App环境获取设备信息失败')
}
}
// 设置推送监听
setupPushListener() {
console.log('开始设置推送监听')
if (this.isUniApp) {
console.log('开始监听uni-app推送消息')
// uni-app 环境监听
uni.onPushMessage((res) => {
console.log('推送消息类型:', res.type, '内容:', res)
......@@ -126,13 +178,18 @@ class UniPush {
}
})
} else if (this.is5PlusApp) {
console.log('开始监听5+App推送消息')
// 5+App 环境监听 - 使用原生推送监听
this.setup5PlusPushListener()
}else{
console.warn('当前环境不支持推送监听')
}
}
// 设置5+App推送监听
setup5PlusPushListener() {
console.log('开始设置5+App推送监听')
console.log('plus.push:', plus.push)
if (plus.push && typeof plus.push.addEventListener === 'function') {
// 监听接收消息事件
plus.push.addEventListener('receive', (msg) => {
......@@ -157,7 +214,30 @@ class UniPush {
// 解析消息内容
const pushData = this.parse5PlusPushData(message)
// 触发自定义事件
// 创建本地通知(如果是透传消息,需要在通知栏显示)
// 仅在接收到消息且非点击事件时创建
if (this.is5PlusApp && plus.push) {
const content = pushData.content || '您有一条新消息'
const payload = typeof pushData.data === 'string' ? pushData.data : JSON.stringify(pushData.data)
const options = {
title: pushData.title || '新消息',
cover: false
}
// 创建本地消息
plus.push.createMessage(content, payload, options)
}
// 触发监听器
this.messageListeners.forEach(listener => {
try {
listener(pushData)
} catch (e) {
console.error('执行消息监听器失败:', e)
}
})
// 兼容旧版单回调
if (typeof this.onMessage === 'function') {
this.onMessage(pushData)
}
......@@ -169,6 +249,15 @@ class UniPush {
const pushData = this.parse5PlusPushData(message)
// 触发点击监听器
this.clickListeners.forEach(listener => {
try {
listener(pushData)
} catch (e) {
console.error('执行点击监听器失败:', e)
}
})
// 根据消息内容跳转到对应页面
if (pushData.page) {
// 这里可以添加页面跳转逻辑
......@@ -212,14 +301,30 @@ class UniPush {
// 消息会直接显示在通知栏,用户点击后才会触发 handlePushClick
// 如果不使用 force_notification:true,可以在此处显示本地通知
if (!this.forceNotificationMode && this.isUniApp && message.data) {
uni.showNotification({
title: message.data.title || '新消息',
content: message.data.content || '您有一条新消息',
payload: message.data
})
console.log('是否使用 force_notification:', this.forceNotificationMode)
console.log('是否是UniApp:', this.isUniApp)
console.log('消息数据:', message.data)
if (this.isUniApp && message.data) {
// 使用 uni.createPushMessage 创建本地通知
if (uni.createPushMessage) {
uni.createPushMessage({
title: message.data.title || '新消息',
content: message.data.content || '您有一条新消息',
payload: message.data,
cover: false
})
}
}
// 触发监听器
this.messageListeners.forEach(listener => {
try {
listener(message)
} catch (e) {
console.error('执行消息监听器失败:', e)
}
})
// 触发自定义事件
if (typeof this.onMessage === 'function') {
this.onMessage(message)
......@@ -228,9 +333,18 @@ class UniPush {
// 处理推送点击
handlePushClick(message) {
// 触发点击监听器
this.clickListeners.forEach(listener => {
try {
listener(message)
} catch (e) {
console.error('执行点击监听器失败:', e)
}
})
// 根据消息内容跳转到对应页面
if (message.data && message.data.page) {
// this.navigateToPage(message.data.page, message.data.params)
// this.navigateToPage(message.data.page, message.data.params)
}
// 触发自定义点击事件
......@@ -239,6 +353,48 @@ class UniPush {
}
}
/**
* 添加消息接收监听器
* @param {Function} callback (message) => {}
*/
addMessageListener(callback) {
if (typeof callback === 'function') {
this.messageListeners.push(callback)
}
}
/**
* 移除消息接收监听器
* @param {Function} callback
*/
removeMessageListener(callback) {
const index = this.messageListeners.indexOf(callback)
if (index > -1) {
this.messageListeners.splice(index, 1)
}
}
/**
* 添加消息点击监听器
* @param {Function} callback (message) => {}
*/
addClickListener(callback) {
if (typeof callback === 'function') {
this.clickListeners.push(callback)
}
}
/**
* 移除消息点击监听器
* @param {Function} callback
*/
removeClickListener(callback) {
const index = this.clickListeners.indexOf(callback)
if (index > -1) {
this.clickListeners.splice(index, 1)
}
}
// 页面跳转
navigateToPage(page, params = {}) {
if (!this.isUniApp) return
......@@ -284,7 +440,9 @@ class UniPush {
// 请求推送权限
requestPermission() {
return new Promise((resolve, reject) => {
if (this.isUniApp) {
console.log('请求UniApp推送权限')
uni.requestPushPermission({
success: (res) => {
resolve(res)
......@@ -294,6 +452,7 @@ class UniPush {
}
})
} else if (this.is5PlusApp) {
console.log('请求5+App推送权限')
// 5+App 环境通常不需要额外请求权限
// 直接返回成功,实际权限由系统控制
resolve({ status: 'granted' })
......@@ -392,6 +551,26 @@ class UniPush {
console.log(`force_notification 模式: ${enabled ? '已启用' : '已禁用'}`)
}
// 检查 URL 参数
_checkUrlParams() {
try {
const getParam = (name) => {
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i')
const r = window.location.search.substr(1).match(reg)
if (r != null) return unescape(r[2])
return null
}
const cid = getParam('deviceId') || getParam('clientid') || getParam('cid')
if (cid) {
this.clientId = cid
console.log('从URL获取到clientId:', cid)
}
} catch (e) {
console.warn('解析URL参数失败', e)
}
}
// 销毁推送实例
destroy() {
if (this.isUniApp) {
......@@ -404,4 +583,4 @@ class UniPush {
// 创建全局推送实例
const pushInstance = new UniPush()
export default pushInstance
\ No newline at end of file
export default pushInstance
import pushInstance from './push'
import { getToken } from './auth'
let socket = null
let reconnectTimer = null
let lockReconnect = false
let currentUserId = null // 存储 userId
const RECONNECT_INTERVAL = 5000
// 监听应用前后台切换
// 当应用切回前台时,如果 WebSocket 断开,则尝试重连
if (document) {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
console.log('App returned to foreground, checking WebSocket status...')
if (!socket || socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) {
console.log('WebSocket disconnected, attempting to reconnect...')
if (lastUrl) { // 需要保存最近一次连接的 URL
initWebSocket(lastUrl, currentUserId)
}
}
}
})
}
let lastUrl = null // 保存最近一次连接的 URL
export const initWebSocket = (url, userId) => {
if (!url) {
console.warn('WebSocket URL is missing')
return
}
lastUrl = url // 保存 URL 供重连使用
// 更新 currentUserId
if (userId) {
currentUserId = userId
}
// 如果已存在连接,且状态为 OPEN 或 CONNECTING,则复用现有连接
if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
console.log('WebSocket connection already exists')
// 如果传入了新的 userId,尝试重新发送认证消息
if (userId && currentUserId === userId && socket.readyState === WebSocket.OPEN) {
console.log('Resending userId to existing WebSocket:', currentUserId)
sendWebSocketMessage({
type: 'auth',
userId: currentUserId
})
}
return
}
// 如果已存在关闭或正在关闭的连接,先清理
// 注意:如果是异常关闭(如网络中断),socket可能处于CLOSED状态,此时也需要清理
if (socket) {
try {
// 移除所有监听器,防止内存泄漏或旧连接回调触发
socket.onopen = null
socket.onmessage = null
socket.onclose = null
socket.onerror = null
// 只有在未关闭的情况下才尝试关闭
if (socket.readyState !== WebSocket.CLOSED && socket.readyState !== WebSocket.CLOSING) {
socket.close()
}
} catch (e) {
console.warn('Error closing existing socket:', e)
}
socket = null
}
try {
socket = new WebSocket(url)
initEventHandle(url)
} catch (e) {
console.error('WebSocket connection failed:', e)
reconnect(url)
}
}
const initEventHandle = (url) => {
socket.onopen = () => {
console.log('WebSocket connected')
// 重置重连状态
lockReconnect = false
// 连接成功后发送 userId
if (currentUserId) {
console.log('Sending userId to WebSocket:', currentUserId)
sendWebSocketMessage({
type: 'auth',
userId: currentUserId
})
}
}
socket.onmessage = (event) => {
console.log('WebSocket received:', event.data)
try {
const message = event.data
// 尝试解析消息
let payload = {}
try {
payload = JSON.parse(message)
} catch (e) {
// 如果不是JSON,则作为纯文本内容
payload = { content: message }
}
// 构造符合 handle5PlusPushMessage 预期的对象
// handle5PlusPushMessage 会调用 parse5PlusPushData,它期望 message.payload 或 message 本身
const pushMessage = {
payload: payload,
content: payload.content || message,
title: payload.title || '新消息'
}
pushInstance.handle5PlusPushMessage(pushMessage)
} catch (e) {
console.error('WebSocket message handling error:', e)
}
}
socket.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason)
// 只有非正常关闭才重连(例如网络中断或服务器断开)
// 正常关闭 (1000) 或页面卸载时不应重连
if (event.code !== 1000) {
reconnect(url)
}
}
socket.onerror = (error) => {
console.error('WebSocket error:', error)
reconnect(url)
}
}
const reconnect = (url) => {
if (lockReconnect) return
lockReconnect = true
if (reconnectTimer) {
clearTimeout(reconnectTimer)
}
console.log(`Attempting to reconnect in ${RECONNECT_INTERVAL}ms...`)
reconnectTimer = setTimeout(() => {
// 重连时使用保存的 currentUserId
initWebSocket(url, currentUserId)
lockReconnect = false
}, RECONNECT_INTERVAL)
}
export const closeWebSocket = () => {
if (reconnectTimer) clearTimeout(reconnectTimer)
lockReconnect = true // 防止触发重连
if (socket) {
socket.onclose = null
socket.close()
socket = null
}
}
// 发送消息方法
export const sendWebSocketMessage = (data) => {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(typeof data === 'string' ? data : JSON.stringify(data))
} else {
console.error('WebSocket is not connected')
}
}
......@@ -40,11 +40,13 @@
<script>
import { getFun, postFun } from '@/service/table'
import pushInstance from '@/utils/push'
import { setToken } from '@/utils/auth'
import { encrypt, decrypt } from '@/utils/jsencrypt'
import { crypto } from '@/utils/crypto'
import { setUserInfo, getUserInfo } from '@/utils/userInfo'
import pushInstance from '@/utils/push'
import { setUserInfo ,getUserInfo} from '@/utils/userInfo'
import { initWebSocket } from '@/utils/websocket'
export default {
data() {
return {
......@@ -56,23 +58,10 @@ export default {
clientId: null
};
},
mounted() {
if (localStorage.getItem('pwd')) {
this.password = decrypt(localStorage.getItem('pwd'))
this.username = localStorage.getItem('username')
}
if (pushInstance.clientId) {
this.clientId = pushInstance.clientId;
uni.showModal({
title: '客户端ID: ' + this.clientId,
success(res) {
if (res.confirm) {
console.log('用户点击确定')
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
async mounted() {
if(localStorage.getItem('pwd')){
this.password=decrypt(localStorage.getItem('pwd'))
this.username=localStorage.getItem('username')
}
},
created() {
......@@ -95,7 +84,6 @@ export default {
position: 'static',
});
plus.webview.currentWebview().append(barcode);
//barcode.start();开始扫码识别(我们把这句代码注释了,因为我们不需要扫描任何东西)
},
......@@ -103,6 +91,7 @@ export default {
var obj = {
username: this.username,
password: this.password,
deviceId: this.clientId || this.deviceId || ''
}
this.$toast.loading({
message: '登录中...',
......@@ -128,6 +117,15 @@ export default {
setUserInfo(userInfo)
localStorage.setItem('pwd', encrypt(this.password))
localStorage.setItem('username', this.username)
// 初始化 WebSocket 连接
// 后端端口: 8099, Context Path: /hse/wy, Endpoint: /wfWarnSocket
// const wsUrl = 'ws://192.168.4.246:8099/hse/wy/wfWarnSocket';
// const wsUrl = 'ws://localhost:8099/hse/wy/wfWarnSocket';
const wsUrl = 'wss://skpt.bcdh.com.cn:8099/ws';
console.log('正在连接 WebSocket:', wsUrl)
initWebSocket(wsUrl, userInfo.userId)
if (!Response2.data.initializePassword) {
this.$router.push('/save-workbench')
} else {
......
<template>
<div>
<LHeader :text="text"></LHeader>
<!-- 内容列表 -->
<div class="title">2022年4月26日</div>
<!-- 内容列表 改为获取当前时间-->
<div class="title">{{currentDate}}</div>
<!-- <p class="title-text">巡查发起人:{{assignName}}</p> -->
<p class="title-text">系统提示:请按时完成巡查工作。</p>
<div class="con-list">
......@@ -47,6 +46,7 @@ export default {
value:'',
text: "巡查记录",
assignName:'',
currentDate: '', // 初始化currentDate
contentList: [
],
......@@ -90,9 +90,20 @@ export default {
};
},
mounted() {
this.initCurrentDate()
this.loading()
},
methods: {
initCurrentDate() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
this.currentDate = `${year}${month}${day}${hours}:${minutes}:${seconds}`;
},
loading(){
getFun("patrol/cycle/list").then((res) => {
......
module.exports = {
devServer: {
open: false,
// 跨域
proxy: {
'/app-api': {
target: 'http://192.168.4.157:8096',
// target: 'http://192.168.15.146:8096',
// changeOrigin: true,
// logLevel:'debug',
}
publicPath: '/wys/',
devServer: {
open: false,
port: 8080, // 前端服务端口(与你的请求地址一致)
https: false,
proxy: {
// 匹配前端请求的 /hse 前缀
'/hse/wys': {
target: 'http://192.168.4.232:8099',
changeOrigin: true,
logLevel: 'debug'
},
'/hse': {
ws: true,
target: 'http://192.168.4.232:8099', // 尝试统一到 232
changeOrigin: true,
logLevel: 'debug',
pathRewrite: {
'^/hse/app-api': '/hse/wy/app-api'
},
https: true
// 修正后端返回的 Cookie 路径(确保前端能正常携带)
onProxyRes: function (proxyRes, req, res) {
if (proxyRes.headers['set-cookie']) {
proxyRes.headers['set-cookie'] = proxyRes.headers['set-cookie'].map(cookie => {
// 后端返回的 Cookie Path 是 /hse/wy,改为前端的 /hse
return cookie.replace(/Path=\/hse\/wy/, 'Path=/hse');
});
}
}
}
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment