feat: 添加最近访问菜单功能并移除Websocket菜单
This commit is contained in:
@@ -498,19 +498,6 @@ export default defineMock([
|
|||||||
params: null,
|
params: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/function/websocket",
|
|
||||||
component: "demo/websocket",
|
|
||||||
name: "/function/websocket",
|
|
||||||
meta: {
|
|
||||||
title: "Websocket",
|
|
||||||
icon: "",
|
|
||||||
hidden: false,
|
|
||||||
keepAlive: true,
|
|
||||||
alwaysShow: false,
|
|
||||||
params: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/function/ai-command",
|
path: "/function/ai-command",
|
||||||
component: "demo/ai-command",
|
component: "demo/ai-command",
|
||||||
@@ -1571,21 +1558,6 @@ export default defineMock([
|
|||||||
perm: null,
|
perm: null,
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 90,
|
|
||||||
parentId: 89,
|
|
||||||
name: "Websocket",
|
|
||||||
type: "MENU",
|
|
||||||
routeName: null,
|
|
||||||
routePath: "/function/websocket",
|
|
||||||
component: "demo/websocket",
|
|
||||||
sort: 3,
|
|
||||||
visible: 1,
|
|
||||||
icon: "",
|
|
||||||
redirect: "",
|
|
||||||
perm: null,
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 91,
|
id: 91,
|
||||||
parentId: 89,
|
parentId: 89,
|
||||||
|
|||||||
@@ -4,4 +4,8 @@ export { useStomp, useDictSync, useOnlineCount } from "./websocket";
|
|||||||
export type { DictMessage, DictChangeMessage, DictChangeCallback } from "./websocket";
|
export type { DictMessage, DictChangeMessage, DictChangeCallback } from "./websocket";
|
||||||
|
|
||||||
// 表格相关
|
// 表格相关
|
||||||
export { useTableSelection } from "./table/useTableSelection";
|
export { useTableSelection } from "./useTableSelection";
|
||||||
|
|
||||||
|
// 最近访问菜单
|
||||||
|
export { useRecentMenus } from "./useRecentMenus";
|
||||||
|
export type { RecentMenuItem } from "./useRecentMenus";
|
||||||
|
|||||||
99
src/composables/useRecentMenus.ts
Normal file
99
src/composables/useRecentMenus.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最近访问菜单项
|
||||||
|
*/
|
||||||
|
export interface RecentMenuItem {
|
||||||
|
path: string;
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
visitedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STORAGE_KEY = "recent_menus";
|
||||||
|
const MAX_COUNT = 8;
|
||||||
|
|
||||||
|
// 全局状态
|
||||||
|
const recentMenus = ref<RecentMenuItem[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 localStorage 加载数据
|
||||||
|
*/
|
||||||
|
function loadFromStorage(): RecentMenuItem[] {
|
||||||
|
try {
|
||||||
|
const data = localStorage.getItem(STORAGE_KEY);
|
||||||
|
return data ? JSON.parse(data) : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存到 localStorage
|
||||||
|
*/
|
||||||
|
function saveToStorage(menus: RecentMenuItem[]) {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(menus));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
recentMenus.value = loadFromStorage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最近访问菜单 composable
|
||||||
|
*/
|
||||||
|
export function useRecentMenus() {
|
||||||
|
/**
|
||||||
|
* 清空所有记录
|
||||||
|
*/
|
||||||
|
function clearRecentMenus() {
|
||||||
|
recentMenus.value = [];
|
||||||
|
localStorage.removeItem(STORAGE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化访问时间
|
||||||
|
*/
|
||||||
|
function formatVisitTime(timestamp: number): string {
|
||||||
|
const now = Date.now();
|
||||||
|
const diff = now - timestamp;
|
||||||
|
|
||||||
|
if (diff < 60000) return "刚刚";
|
||||||
|
if (diff < 3600000) return `${Math.floor(diff / 60000)}分钟前`;
|
||||||
|
if (diff < 86400000) return `${Math.floor(diff / 3600000)}小时前`;
|
||||||
|
if (diff < 604800000) return `${Math.floor(diff / 86400000)}天前`;
|
||||||
|
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
return `${date.getMonth() + 1}-${date.getDate()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
recentMenus,
|
||||||
|
clearRecentMenus,
|
||||||
|
formatVisitTime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加最近访问记录(全局方法,供路由守卫调用)
|
||||||
|
*/
|
||||||
|
export function addRecentMenu(path: string, title: string, icon?: string) {
|
||||||
|
if (!path || !title) return;
|
||||||
|
|
||||||
|
// 过滤掉不需要记录的路径
|
||||||
|
const excludePaths = ["/dashboard", "/redirect", "/404", "/401", "/login", "/"];
|
||||||
|
if (excludePaths.some((p) => path === p || path.startsWith(p + "/"))) return;
|
||||||
|
|
||||||
|
// 移除已存在的相同路径
|
||||||
|
const filtered = recentMenus.value.filter((item) => item.path !== path);
|
||||||
|
|
||||||
|
// 添加到开头
|
||||||
|
const newItem: RecentMenuItem = {
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
visitedAt: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
recentMenus.value = [newItem, ...filtered].slice(0, MAX_COUNT);
|
||||||
|
saveToStorage(recentMenus.value);
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import router from "@/router";
|
|||||||
import { usePermissionStore, useUserStore } from "@/store";
|
import { usePermissionStore, useUserStore } from "@/store";
|
||||||
import { useTenantStoreHook } from "@/store/modules/tenant";
|
import { useTenantStoreHook } from "@/store/modules/tenant";
|
||||||
import { isTenantEnabled } from "@/utils/tenant";
|
import { isTenantEnabled } from "@/utils/tenant";
|
||||||
|
import { addRecentMenu } from "@/composables/useRecentMenus";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由权限守卫
|
* 路由权限守卫
|
||||||
@@ -78,8 +79,14 @@ export function setupPermissionGuard() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.afterEach(() => {
|
router.afterEach((to) => {
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
|
|
||||||
|
// 记录最近访问
|
||||||
|
if (to.meta?.title && to.path) {
|
||||||
|
const icon = typeof to.meta.icon === "string" ? to.meta.icon : undefined;
|
||||||
|
addRecentMenu(to.path, to.meta.title as string, icon);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -319,68 +319,63 @@
|
|||||||
<ECharts :options="visitTrendChartOptions" height="400px" />
|
<ECharts :options="visitTrendChartOptions" height="400px" />
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 最新动态-->
|
<!-- 最近访问 -->
|
||||||
<el-col :xs="24" :span="8">
|
<el-col :xs="24" :span="8">
|
||||||
<el-card>
|
<el-card>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex-x-between">
|
<div class="flex-x-between">
|
||||||
<span class="font-semibold">最新动态</span>
|
<span class="font-semibold">最近访问</span>
|
||||||
<el-link
|
<el-button
|
||||||
|
v-if="recentMenus.length > 0"
|
||||||
type="primary"
|
type="primary"
|
||||||
underline="never"
|
link
|
||||||
href="https://gitee.com/youlaiorg/vue3-element-admin/releases"
|
size="small"
|
||||||
target="_blank"
|
@click="clearRecentMenus"
|
||||||
>
|
>
|
||||||
完整记录
|
清空
|
||||||
<el-icon class="ml-0.5"><TopRight /></el-icon>
|
</el-button>
|
||||||
</el-link>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-scrollbar height="400px">
|
<div class="min-h-[400px] flex flex-col">
|
||||||
<el-timeline class="p-3">
|
<!-- 宫格显示 -->
|
||||||
<el-timeline-item
|
<div v-if="recentMenus.length > 0" class="grid grid-cols-2 gap-3">
|
||||||
v-for="(item, index) in vesionList"
|
<div
|
||||||
:key="index"
|
v-for="item in recentMenus"
|
||||||
:timestamp="item.date"
|
:key="item.path"
|
||||||
placement="top"
|
class="group flex items-center gap-2 px-3 py-2.5 bg-[--el-fill-color-lighter] rounded-lg cursor-pointer transition-all duration-200 hover:bg-[--el-color-primary-light-8]"
|
||||||
:color="index === 0 ? '#67C23A' : '#909399'"
|
@click="router.push(item.path)"
|
||||||
:hollow="index !== 0"
|
|
||||||
size="large"
|
|
||||||
>
|
>
|
||||||
<div
|
<!-- 图标 -->
|
||||||
class="p-4 mb-3 bg-[--el-fill-color-lighter] rounded-lg transition-all duration-200 hover:translate-x-1"
|
<div class="shrink-0 w-8 h-8 flex items-center justify-center">
|
||||||
:class="{
|
<el-icon
|
||||||
'bg-[--el-color-primary-light-9]! border border-[--el-color-primary-light-5]':
|
v-if="item.icon?.startsWith('el-icon-')"
|
||||||
index === 0,
|
class="text-lg text-[--el-color-primary]"
|
||||||
}"
|
>
|
||||||
>
|
<component :is="item.icon.replace('el-icon-', '')" />
|
||||||
<div class="flex items-center gap-2">
|
</el-icon>
|
||||||
<el-text tag="strong">{{ item.title }}</el-text>
|
<div
|
||||||
<el-tag v-if="item.tag" :type="index === 0 ? 'success' : 'info'" size="small">
|
v-else-if="item.icon"
|
||||||
{{ item.tag }}
|
:class="`i-svg:${item.icon} text-lg text-[--el-color-primary]`"
|
||||||
</el-tag>
|
/>
|
||||||
</div>
|
<el-icon v-else class="text-lg text-[--el-color-primary]"><Menu /></el-icon>
|
||||||
|
|
||||||
<el-text class="mb-3 text-xs leading-relaxed text-[--el-text-color-secondary]">
|
|
||||||
{{ item.content }}
|
|
||||||
</el-text>
|
|
||||||
|
|
||||||
<div v-if="item.link">
|
|
||||||
<el-link
|
|
||||||
:type="index === 0 ? 'primary' : 'info'"
|
|
||||||
:href="item.link"
|
|
||||||
target="_blank"
|
|
||||||
underline="never"
|
|
||||||
>
|
|
||||||
详情
|
|
||||||
<el-icon class="ml-0.5"><TopRight /></el-icon>
|
|
||||||
</el-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</el-timeline-item>
|
<!-- 标题 -->
|
||||||
</el-timeline>
|
<span class="text-sm truncate flex-1 leading-tight">
|
||||||
</el-scrollbar>
|
{{ item.title }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-else class="flex flex-col items-center justify-center flex-1 py-16">
|
||||||
|
<el-icon :size="48" class="text-[--el-text-color-placeholder] mb-4">
|
||||||
|
<Clock />
|
||||||
|
</el-icon>
|
||||||
|
<p class="text-sm text-[--el-text-color-secondary] mb-2">暂无访问记录</p>
|
||||||
|
<p class="text-xs text-[--el-text-color-placeholder]">访问的页面会自动记录在这里</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -395,17 +390,23 @@ defineOptions({
|
|||||||
|
|
||||||
import { dayjs } from "element-plus";
|
import { dayjs } from "element-plus";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
import StatisticsAPI from "@/api/system/statistics";
|
import StatisticsAPI from "@/api/system/statistics";
|
||||||
import type { VisitStatsDetail, VisitTrendDetail } from "@/types/api";
|
import type { VisitStatsDetail, VisitTrendDetail } from "@/types/api";
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { formatGrowthRate } from "@/utils";
|
import { formatGrowthRate } from "@/utils";
|
||||||
import { useTransition, useDateFormat } from "@vueuse/core";
|
import { useTransition, useDateFormat } from "@vueuse/core";
|
||||||
import { CircleCheck, CircleClose, Loading } from "@element-plus/icons-vue";
|
import { CircleCheck, CircleClose, Loading, Clock, Menu } from "@element-plus/icons-vue";
|
||||||
import { useOnlineCount } from "@/composables";
|
import { useOnlineCount, useRecentMenus } from "@/composables";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// 在线用户数量组件相关
|
// 在线用户数量组件相关
|
||||||
const { onlineUserCount, lastUpdateTime, isConnected, connectionState } = useOnlineCount();
|
const { onlineUserCount, lastUpdateTime, isConnected, connectionState } = useOnlineCount();
|
||||||
|
|
||||||
|
// 最近访问菜单
|
||||||
|
const { recentMenus, clearRecentMenus } = useRecentMenus();
|
||||||
|
|
||||||
// 格式化时间戳
|
// 格式化时间戳
|
||||||
const formattedTime = computed(() => {
|
const formattedTime = computed(() => {
|
||||||
if (!lastUpdateTime.value) return "--";
|
if (!lastUpdateTime.value) return "--";
|
||||||
@@ -429,45 +430,8 @@ const wsStatusClass = computed(() => {
|
|||||||
: "text-[--el-color-danger] bg-[--el-color-danger-light-9] border-[--el-color-danger-light-7]";
|
: "text-[--el-color-danger] bg-[--el-color-danger-light-9] border-[--el-color-danger-light-7]";
|
||||||
});
|
});
|
||||||
|
|
||||||
interface VersionItem {
|
|
||||||
id: string;
|
|
||||||
title: string; // 版本标题(如:v2.4.0)
|
|
||||||
date: string; // 发布时间
|
|
||||||
content: string; // 版本描述
|
|
||||||
link: string; // 详情链接
|
|
||||||
tag?: string; // 版本标签(可选)
|
|
||||||
}
|
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// 当前通知公告列表
|
|
||||||
const vesionList = ref<VersionItem[]>([
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
title: "v2.4.0",
|
|
||||||
date: "2021-09-01 00:00:00",
|
|
||||||
content: "实现基础框架搭建,包含权限管理、路由系统等核心功能。",
|
|
||||||
link: "https://gitee.com/youlaiorg/vue3-element-admin/releases",
|
|
||||||
tag: "里程碑",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
title: "v2.4.0",
|
|
||||||
date: "2021-09-01 00:00:00",
|
|
||||||
content: "实现基础框架搭建,包含权限管理、路由系统等核心功能。",
|
|
||||||
link: "https://gitee.com/youlaiorg/vue3-element-admin/releases",
|
|
||||||
tag: "里程碑",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
title: "v2.4.0",
|
|
||||||
date: "2021-09-01 00:00:00",
|
|
||||||
content: "实现基础框架搭建,包含权限管理、路由系统等核心功能。",
|
|
||||||
link: "https://gitee.com/youlaiorg/vue3-element-admin/releases",
|
|
||||||
tag: "里程碑",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 当前时间(用于计算问候语)
|
// 当前时间(用于计算问候语)
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
|
|
||||||
|
|||||||
@@ -1,242 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-link
|
|
||||||
href="https://gitee.com/youlaiorg/vue3-element-admin/blob/master/src/views/demo/websocket.vue"
|
|
||||||
type="primary"
|
|
||||||
target="_blank"
|
|
||||||
class="mb-[20px]"
|
|
||||||
>
|
|
||||||
示例源码 请点击>>>
|
|
||||||
</el-link>
|
|
||||||
<el-row :gutter="10">
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-card>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="18">
|
|
||||||
<el-input v-model="socketEndpoint" style="width: 200px" />
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
class="ml-5"
|
|
||||||
:disabled="isConnected"
|
|
||||||
@click="connectWebSocket"
|
|
||||||
>
|
|
||||||
连接
|
|
||||||
</el-button>
|
|
||||||
<el-button type="danger" :disabled="!isConnected" @click="disconnectWebSocket">
|
|
||||||
断开
|
|
||||||
</el-button>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6" class="text-right">
|
|
||||||
连接状态:
|
|
||||||
<el-tag v-if="isConnected" type="success">已连接</el-tag>
|
|
||||||
<el-tag v-else type="info">已断开</el-tag>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</el-card>
|
|
||||||
<!-- 广播消息发送部分 -->
|
|
||||||
<el-card class="mt-5">
|
|
||||||
<el-form label-width="90px">
|
|
||||||
<el-form-item label="消息内容">
|
|
||||||
<el-input v-model="topicMessage" type="textarea" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="sendToAll">发送广播</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
<!-- 点对点消息发送部分 -->
|
|
||||||
<el-card class="mt-5">
|
|
||||||
<el-form label-width="90px">
|
|
||||||
<el-form-item label="消息内容">
|
|
||||||
<el-input v-model="queneMessage" type="textarea" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="消息接收人">
|
|
||||||
<el-input v-model="receiver" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="sendToUser">发送点对点消息</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<!-- 消息接收显示部分 -->
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-card>
|
|
||||||
<div class="chat-messages-wrapper">
|
|
||||||
<div
|
|
||||||
v-for="(message, index) in messages"
|
|
||||||
:key="index"
|
|
||||||
:class="[
|
|
||||||
message.type === 'tip' ? 'system-notice' : 'chat-message',
|
|
||||||
{
|
|
||||||
'chat-message--sent': message.sender === userStore.userInfo.username,
|
|
||||||
'chat-message--received': message.sender !== userStore.userInfo.username,
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<template v-if="message.type != 'tip'">
|
|
||||||
<div class="chat-message__content">
|
|
||||||
<div
|
|
||||||
:class="{
|
|
||||||
'chat-message__sender': message.sender === userStore.userInfo.username,
|
|
||||||
'chat-message__receiver': message.sender !== userStore.userInfo.username,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ message.sender }}
|
|
||||||
</div>
|
|
||||||
<div class="text-gray-600">{{ message.content }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div v-else>{{ message.content }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useStomp } from "@/composables/websocket/useStomp";
|
|
||||||
import { useUserStoreHook } from "@/store/modules/user";
|
|
||||||
|
|
||||||
const userStore = useUserStoreHook();
|
|
||||||
// 用于手动调整 WebSocket 地址
|
|
||||||
const socketEndpoint = ref(import.meta.env.VITE_APP_WS_ENDPOINT);
|
|
||||||
// 同步连接状态"
|
|
||||||
interface MessageType {
|
|
||||||
type?: string;
|
|
||||||
sender?: string;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
const messages = ref<MessageType[]>([]);
|
|
||||||
// 广播消息内容
|
|
||||||
const topicMessage = ref("亲爱的朋友们,系统已恢复最新状态。");
|
|
||||||
// 点对点消息内容(默认示例)
|
|
||||||
const queneMessage = ref("Hi, " + userStore.userInfo.username + " 这里是点对点消息示例!");
|
|
||||||
const receiver = ref("root");
|
|
||||||
|
|
||||||
// 调用 useStomp hook,默认使用 socketEndpoint 和 token(此处用 getAccessToken())
|
|
||||||
const { isConnected, connect, subscribe, disconnect } = useStomp({
|
|
||||||
debug: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => isConnected.value,
|
|
||||||
(connected) => {
|
|
||||||
if (connected) {
|
|
||||||
// 连接成功后,订阅广播和点对点消息主题
|
|
||||||
subscribe("/topic/notice", (res) => {
|
|
||||||
messages.value.push({
|
|
||||||
sender: "Server",
|
|
||||||
content: res.body,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
subscribe("/user/queue/greeting", (res) => {
|
|
||||||
const messageData = JSON.parse(res.body) as MessageType;
|
|
||||||
messages.value.push({
|
|
||||||
sender: messageData.sender,
|
|
||||||
content: messageData.content,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
messages.value.push({
|
|
||||||
sender: "Server",
|
|
||||||
content: "Websocket 已连接",
|
|
||||||
type: "tip",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
messages.value.push({
|
|
||||||
sender: "Server",
|
|
||||||
content: "Websocket 已断开",
|
|
||||||
type: "tip",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 连接 WebSocket
|
|
||||||
function connectWebSocket() {
|
|
||||||
connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 断开 WebSocket
|
|
||||||
function disconnectWebSocket() {
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送广播消息
|
|
||||||
function sendToAll() {
|
|
||||||
if (isConnected.value) {
|
|
||||||
// 直接使用订阅模式处理广播消息
|
|
||||||
subscribe("/app/broadcast", () => {});
|
|
||||||
messages.value.push({
|
|
||||||
sender: userStore.userInfo.username,
|
|
||||||
content: topicMessage.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送点对点消息
|
|
||||||
function sendToUser() {
|
|
||||||
if (isConnected.value) {
|
|
||||||
// 使用订阅模式处理点对点消息
|
|
||||||
subscribe(`/app/sendToUser/${receiver.value}`, () => {});
|
|
||||||
messages.value.push({
|
|
||||||
sender: userStore.userInfo.username,
|
|
||||||
content: queneMessage.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
connectWebSocket();
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
disconnectWebSocket();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.chat-messages-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.chat-message {
|
|
||||||
max-width: 80%;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
&--sent {
|
|
||||||
align-self: flex-end;
|
|
||||||
background-color: #dcf8c6;
|
|
||||||
}
|
|
||||||
&--received {
|
|
||||||
align-self: flex-start;
|
|
||||||
background-color: #e8e8e8;
|
|
||||||
}
|
|
||||||
&__content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
color: var(--el-text-color-primary); // 使用主题文本颜色
|
|
||||||
}
|
|
||||||
&__sender {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
&__receiver {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.system-notice {
|
|
||||||
align-self: center;
|
|
||||||
padding: 5px 10px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
background-color: var(--el-fill-color-lighter);
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -203,6 +203,10 @@ const formComponents = {
|
|||||||
animation: featureFade 0.8s ease-out;
|
animation: featureFade 0.8s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .auth-feature {
|
||||||
|
color: rgba(240, 245, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.auth-view__wrapper {
|
.auth-view__wrapper {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
Reference in New Issue
Block a user