✨新增在线时长、单点登录
This commit is contained in:
parent
9b7a1e870c
commit
352161ac96
@ -28,7 +28,11 @@ watch(
|
||||
token,
|
||||
(val) => {
|
||||
if (val) {
|
||||
proxy.signalr.start()
|
||||
proxy.signalr.start().then(async (res) => {
|
||||
if (res) {
|
||||
await proxy.signalr.SR.invoke('logOut')
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -109,11 +109,9 @@
|
||||
}
|
||||
|
||||
.login-scan-container {
|
||||
padding: 0 20px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import directive from './directive' // directive
|
||||
// 注册指令
|
||||
import plugins from './plugins' // plugins
|
||||
import { downFile } from '@/utils/request'
|
||||
import signalR from '@/utils/signalR'
|
||||
import signalR from '@/signalr/signalr'
|
||||
import vueI18n from './i18n/index'
|
||||
import pinia from '@/store/index'
|
||||
|
||||
|
||||
89
src/signalr/analysis.js
Normal file
89
src/signalr/analysis.js
Normal file
@ -0,0 +1,89 @@
|
||||
// import signalr from './signalr'
|
||||
import { ElNotification, ElMessage, ElMessageBox } from 'element-plus'
|
||||
import useSocketStore from '@/store/modules/socket'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { webNotify } from '@/utils/index'
|
||||
|
||||
export default {
|
||||
onMessage(connection) {
|
||||
connection.on(MsgType.M001, (data) => {
|
||||
useSocketStore().setOnlineUsers(data)
|
||||
})
|
||||
|
||||
connection.on(MsgType.M002, (data) => {
|
||||
// useUserStore().saveConnId(data)
|
||||
})
|
||||
// 接收后台手动推送消息
|
||||
connection.on('receiveNotice', (title, data) => {
|
||||
ElNotification({
|
||||
type: 'info',
|
||||
title: title,
|
||||
message: data,
|
||||
dangerouslyUseHTMLString: true,
|
||||
duration: 0
|
||||
})
|
||||
webNotify({ title: title, body: data })
|
||||
})
|
||||
// 接收系统通知/公告
|
||||
connection.on('moreNotice', (data) => {
|
||||
if (data.code == 200) {
|
||||
useSocketStore().setNoticeList(data.data)
|
||||
}
|
||||
})
|
||||
|
||||
// 接收在线用户
|
||||
// connection.on('onlineUser', (data) => {
|
||||
// useSocketStore().setOnlineUsers(data)
|
||||
// })
|
||||
|
||||
// 接收封锁通知
|
||||
connection.on('lockUser', (data) => {
|
||||
ElMessageBox.alert(`你的账号已被锁定,剩余,${data.time}分,原因:${data.reason || '-'}`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
callback: () => {
|
||||
useUserStore()
|
||||
.logOut()
|
||||
.then(() => {
|
||||
location.href = import.meta.env.VITE_APP_ROUTER_PREFIX + 'index'
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
// 接收聊天数据
|
||||
connection.on('receiveChat', (data) => {
|
||||
const title = `来自${data.userName}的消息通知`
|
||||
useSocketStore().setChat(data)
|
||||
|
||||
if (data.userid != useUserStore().userId) {
|
||||
ElNotification({
|
||||
title: title,
|
||||
message: data.message,
|
||||
type: 'success',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
webNotify({ title: title, body: data.message })
|
||||
})
|
||||
|
||||
connection.on('onlineInfo', (data) => {
|
||||
useSocketStore().getOnlineInfo(data)
|
||||
})
|
||||
|
||||
connection.on('logOut', () => {
|
||||
useUserStore()
|
||||
.logOut()
|
||||
.then(() => {
|
||||
ElMessageBox.alert(`你的账号已在其他设备登录,如果不是你的操作请尽快修改密码`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
callback: () => {
|
||||
location.href = import.meta.env.VITE_APP_ROUTER_PREFIX + 'index'
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
const MsgType = {
|
||||
M001: 'onlineNum',
|
||||
M002: 'connId'
|
||||
}
|
||||
76
src/signalr/signalr.js
Normal file
76
src/signalr/signalr.js
Normal file
@ -0,0 +1,76 @@
|
||||
// 官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/signalr/javascript-client?view=aspnetcore-6.0&viewFallbackFrom=aspnetcore-2.2&tabs=visual-studio
|
||||
import * as signalR from '@microsoft/signalr'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import analysis from '@/signalr/analysis'
|
||||
|
||||
export default {
|
||||
// signalR对象
|
||||
SR: {},
|
||||
// 失败连接重试次数
|
||||
failNum: 4,
|
||||
init(url) {
|
||||
var socketUrl = window.location.origin + url
|
||||
const connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(socketUrl, { accessTokenFactory: () => getToken() })
|
||||
.withAutomaticReconnect() //自动重新连接
|
||||
.configureLogging(signalR.LogLevel.Warning)
|
||||
.build()
|
||||
this.SR = connection
|
||||
// 断线重连
|
||||
connection.onclose(async (error) => {
|
||||
console.error('断开连接了' + error)
|
||||
console.assert(connection.state === signalR.HubConnectionState.Disconnected)
|
||||
// 建议用户重新刷新浏览器
|
||||
await this.start()
|
||||
})
|
||||
|
||||
connection.onreconnected((connectionId) => {
|
||||
ElMessage({
|
||||
message: '与服务器通讯已连接成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
console.log('断线重新连接成功' + connectionId)
|
||||
})
|
||||
|
||||
connection.onreconnecting(async () => {
|
||||
console.log('断线重新连接中... ')
|
||||
|
||||
await this.start()
|
||||
})
|
||||
analysis.onMessage(connection)
|
||||
// this.receiveMsg(connection)
|
||||
// 启动
|
||||
// this.start();
|
||||
},
|
||||
/**
|
||||
* 调用 this.signalR.start().then(async () => { await this.SR.invoke("method")})
|
||||
* @returns
|
||||
*/
|
||||
async start() {
|
||||
try {
|
||||
console.log('signalR-1', this.SR.state)
|
||||
//使用async和await 或 promise的then 和catch 处理来自服务端的异常
|
||||
if (this.SR.state === signalR.HubConnectionState.Disconnected) {
|
||||
await this.SR.start()
|
||||
}
|
||||
|
||||
console.log('signalR-2', this.SR.state)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
this.failNum--
|
||||
// console.log(`失败重试剩余次数${that.failNum}`, error)
|
||||
if (this.failNum > 0 && this.SR.state.Disconnected) {
|
||||
setTimeout(async () => {
|
||||
await this.start()
|
||||
}, 5000)
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
// 接收消息处理
|
||||
receiveMsg(connection) {}
|
||||
}
|
||||
@ -3,7 +3,12 @@ const useSocketStore = defineStore('socket', {
|
||||
onlineNum: 0,
|
||||
onlineUsers: [],
|
||||
noticeList: [],
|
||||
noticeDot: false
|
||||
noticeDot: false,
|
||||
//在线用户信息
|
||||
onlineInfo: {},
|
||||
// 聊天数据
|
||||
chatList: [],
|
||||
leaveUser: {}
|
||||
}),
|
||||
actions: {
|
||||
//更新在线人数
|
||||
@ -15,14 +20,19 @@ const useSocketStore = defineStore('socket', {
|
||||
this.noticeList = data
|
||||
this.noticeDot = data.length > 0
|
||||
},
|
||||
// setOnlineUsers(data) {
|
||||
// const { onlineNum, users } = data
|
||||
// this.onlineUsers = users
|
||||
// this.onlineNum = onlineNum
|
||||
// },
|
||||
sendChat(data) {
|
||||
const { proxy } = getCurrentInstance()
|
||||
console.log(data)
|
||||
setOnlineUsers(data) {
|
||||
const { onlineClients, num, leaveUser } = data
|
||||
this.onlineUsers = onlineClients
|
||||
this.onlineNum = num
|
||||
if (leaveUser != null) {
|
||||
this.leaveUser = leaveUser
|
||||
}
|
||||
},
|
||||
getOnlineInfo(data) {
|
||||
this.onlineInfo = data
|
||||
},
|
||||
setChat(data) {
|
||||
this.chatList.push(data)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -19,20 +19,20 @@ export default {
|
||||
.build()
|
||||
this.SR = connection
|
||||
// 断线重连
|
||||
connection.onclose(async () => {
|
||||
console.log('断开连接了')
|
||||
connection.onclose(async (error) => {
|
||||
console.error('断开连接了' + error)
|
||||
console.assert(connection.state === signalR.HubConnectionState.Disconnected)
|
||||
// 建议用户重新刷新浏览器
|
||||
await this.start()
|
||||
})
|
||||
|
||||
connection.onreconnected(() => {
|
||||
connection.onreconnected((connectionId) => {
|
||||
ElMessage({
|
||||
message: '与服务器通讯已连接成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
console.log('断线重新连接成功')
|
||||
console.log('断线重新连接成功' + connectionId)
|
||||
})
|
||||
|
||||
connection.onreconnecting(async () => {
|
||||
@ -74,7 +74,7 @@ export default {
|
||||
// 接收消息处理
|
||||
receiveMsg(connection) {
|
||||
connection.on('onlineNum', (data) => {
|
||||
useSocketStore().setOnlineUserNum(data)
|
||||
useSocketStore().setOnlineUsers(data)
|
||||
})
|
||||
// 接收欢迎语
|
||||
connection.on('welcome', (data) => {
|
||||
@ -125,14 +125,35 @@ export default {
|
||||
// 接收聊天数据
|
||||
connection.on('receiveChat', (data) => {
|
||||
const title = `来自${data.userName}的消息通知`
|
||||
useSocketStore().setChat(data)
|
||||
|
||||
if (data.userid != useUserStore().userId) {
|
||||
ElNotification({
|
||||
title: title,
|
||||
message: data.message,
|
||||
type: 'success',
|
||||
duration: 0
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
webNotify({ title: title, body: data.message })
|
||||
})
|
||||
|
||||
webNotify({ title: title, body: data.message })
|
||||
connection.on('onlineInfo', (data) => {
|
||||
console.log('onlineInfo', data)
|
||||
useSocketStore().getOnlineInfo(data)
|
||||
})
|
||||
|
||||
connection.on('logOut', () => {
|
||||
useUserStore()
|
||||
.logOut()
|
||||
.then(() => {
|
||||
ElMessageBox.alert(`你的账号已在其他设备登录,如果不是你的操作请尽快修改密码`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
callback: () => {
|
||||
location.href = import.meta.env.VITE_APP_ROUTER_PREFIX + 'index'
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="home">
|
||||
<!-- 用户信息 -->
|
||||
<el-row :gutter="15">
|
||||
<el-col :md="24" :lg="18" :xl="24" class="mb10">
|
||||
<el-col :md="24" :lg="16" :xl="24" class="mb10">
|
||||
<el-card shadow="hover">
|
||||
<div class="user-item">
|
||||
<div class="user-item-left">
|
||||
@ -12,7 +12,10 @@
|
||||
<div class="user-item-right">
|
||||
<el-row>
|
||||
<el-col :xs="24" :md="24" class="right-title mb20 one-text-overflow">
|
||||
{{ userInfo.welcomeMessage }} <strong>{{ userInfo.nickName }}</strong> {{ userInfo.welcomeContent }}
|
||||
<div class="mb10">
|
||||
{{ userInfo.welcomeMessage }} <strong>{{ userInfo.nickName }}</strong>
|
||||
<span>({{ userInfo.welcomeContent }})</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
@ -24,10 +27,19 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :lg="6" class="mb10">
|
||||
<el-col :lg="8" class="mb10">
|
||||
<el-card style="height: 100%">
|
||||
<div class="text-warning mb10">{{ currentTime }} {{ weekName }}</div>
|
||||
<div>上次登录时间:{{ userInfo.loginDate }}</div>
|
||||
<div class="work-wrap">
|
||||
<div class="item">
|
||||
<div class="name">今日工作时长</div>
|
||||
<div class="mt10">{{ onlineInfo.onlineTime }}分</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="name">在线设备数</div>
|
||||
<div class="mt10">{{ onlineInfo.clientNum }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -94,6 +106,7 @@ import BarChart from './dashboard/BarChart'
|
||||
import CommonMenu from './components/CommonMenu'
|
||||
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import useSocketStore from '@/store/modules/socket'
|
||||
import { getWeek } from '@/utils/ruoyi'
|
||||
const showEdit = ref(false)
|
||||
const data = {
|
||||
@ -118,6 +131,9 @@ const { proxy } = getCurrentInstance()
|
||||
const userInfo = computed(() => {
|
||||
return useUserStore().userInfo
|
||||
})
|
||||
const onlineInfo = computed(() => {
|
||||
return useSocketStore().onlineInfo
|
||||
})
|
||||
const currentTime = computed(() => {
|
||||
return proxy.parseTime(new Date(), 'YYYY-MM-DD')
|
||||
})
|
||||
@ -165,6 +181,19 @@ function handleAdd() {
|
||||
height: 200px;
|
||||
// overflow-y: scroll;
|
||||
}
|
||||
|
||||
.work-wrap {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
|
||||
.item {
|
||||
text-align: center;
|
||||
|
||||
.name {
|
||||
color: #606666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.chart-wrapper {
|
||||
background: var(--base-bg-main);
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
<el-image :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
@ -59,6 +59,7 @@
|
||||
|
||||
<div class="login-scan-container">
|
||||
<div ref="imgContainerRef" id="imgContainer"></div>
|
||||
<div>请使用移动端app扫码登录</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -3,11 +3,10 @@
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">刷新</el-button>
|
||||
<!-- <el-button icon="Refresh" @click="resetQuery">{{ $t('btn.reset') }}</el-button> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="onlineUsers" ref="tableRef" border highlight-current-row>
|
||||
<el-table :data="onlineUsers" v-loading="loading" ref="tableRef" border highlight-current-row>
|
||||
<!-- <el-table-column prop="connnectionId" label="连接id"></el-table-column> -->
|
||||
<el-table-column label="No" type="index" width="50" align="center">
|
||||
<template #default="scope">
|
||||
@ -17,11 +16,12 @@
|
||||
<el-table-column prop="name" label="用户名" align="center" />
|
||||
<el-table-column label="登录地点" prop="location" align="center"> </el-table-column>
|
||||
<el-table-column label="登录IP" prop="userIP" align="center"></el-table-column>
|
||||
<el-table-column prop="browser" label="登录浏览器" width="250"></el-table-column>
|
||||
<el-table-column prop="browser" label="登录浏览器" width="210"></el-table-column>
|
||||
<el-table-column prop="platform" label="登录设备" align="center"></el-table-column>
|
||||
<el-table-column prop="loginTime" label="登录时间">
|
||||
<el-table-column prop="loginTime" label="登录时间" witdh="280px">
|
||||
<template #default="scope">
|
||||
{{ dayjs(scope.row.loginTime).format('MM/DD日HH:mm:ss') }}
|
||||
<div>在线时长:{{ scope.row.onlineTime }}分钟</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="160">
|
||||
@ -47,12 +47,11 @@ const queryParams = reactive({
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
// const total = computed(() => {
|
||||
// return useSocketStore().onlineNum
|
||||
// })
|
||||
// const onlineUsers = computed(() => {
|
||||
// return useSocketStore().onlineUsers
|
||||
// })
|
||||
const onlineNum = computed(() => {
|
||||
return useSocketStore().onlineNum
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const onlineUsers = ref([])
|
||||
const total = ref(0)
|
||||
function handleQuery() {
|
||||
@ -60,13 +59,14 @@ function handleQuery() {
|
||||
getList()
|
||||
}
|
||||
function getList() {
|
||||
// proxy.signalr.SR.invoke('GetOnlineUsers', queryParams.pageNum, queryParams.pageSize).catch(function (err) {
|
||||
// console.error(err.toString())
|
||||
// })
|
||||
loading.value = true
|
||||
listOnline(queryParams).then((res) => {
|
||||
if (res.code == 200) {
|
||||
total.value = res.data.totalNum
|
||||
onlineUsers.value = res.data.result
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
}, 200)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -81,7 +81,7 @@ function onChat(item) {
|
||||
inputErrorMessage: '消息内容不能为空'
|
||||
})
|
||||
.then(({ value }) => {
|
||||
proxy.signalr.SR.invoke('SendMessage', item.connnectionId, item.name, value).catch(function (err) {
|
||||
proxy.signalr.SR.invoke('sendMessage', item.connnectionId, item.userid, value).catch(function (err) {
|
||||
console.error(err.toString())
|
||||
})
|
||||
})
|
||||
@ -100,4 +100,14 @@ function onLock(row) {
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
watch(
|
||||
onlineNum,
|
||||
() => {
|
||||
handleQuery()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user