refactor: ♻️ 通知公告、字典重构问题修复和优化
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vue3-element-admin",
|
"name": "vue3-element-admin",
|
||||||
"version": "2.16.0",
|
"version": "2.16.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class NoticeAPI {
|
|||||||
/** 获取我的通知分页列表 */
|
/** 获取我的通知分页列表 */
|
||||||
static getMyNoticePage(queryParams?: NoticePageQuery) {
|
static getMyNoticePage(queryParams?: NoticePageQuery) {
|
||||||
return request<any, PageResult<NoticePageVO[]>>({
|
return request<any, PageResult<NoticePageVO[]>>({
|
||||||
url: `${NOTICE_BASE_URL}/my/page`,
|
url: `${NOTICE_BASE_URL}/my-page`,
|
||||||
method: "get",
|
method: "get",
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<template v-if="tag">
|
<template v-if="tagType">
|
||||||
<el-tag :type="tag">{{ label }}</el-tag>
|
<el-tag :type="tagType" :size="tagSize">{{ label }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span>{{ label }}</span>
|
<span>{{ label }}</span>
|
||||||
@@ -10,26 +10,55 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import DictDataAPI from "@/api/dict-data";
|
import DictDataAPI from "@/api/dict-data";
|
||||||
import Cache from "@/utils/cache";
|
import Cache from "@/utils/cache";
|
||||||
|
import requestCache from "@/utils/requestCache"; // 导入共享的缓存
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
dictCode: String,
|
code: String,
|
||||||
value: [String, Number],
|
modelValue: [String, Number],
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: "default",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const label = ref("");
|
const label = ref("");
|
||||||
const tag = ref<
|
const tagType = ref<
|
||||||
"success" | "warning" | "info" | "primary" | "danger" | undefined
|
"success" | "warning" | "info" | "primary" | "danger" | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
const tagSize = ref(props.size as "default" | "large" | "small");
|
||||||
|
|
||||||
const dictCache = new Cache("dict_");
|
const dictCache = new Cache("dict_");
|
||||||
|
|
||||||
const getLabelAndTagByValue = async (dictCode: string, value: any) => {
|
const getLabelAndTagByValue = async (dictCode: string, value: any) => {
|
||||||
|
// 先从本地缓存中获取字典数据
|
||||||
let dictData = dictCache.getCache(dictCode);
|
let dictData = dictCache.getCache(dictCode);
|
||||||
|
|
||||||
|
// 如果本地缓存没有数据,则检查是否已经发起请求
|
||||||
if (!dictData) {
|
if (!dictData) {
|
||||||
dictData = await DictDataAPI.getOptions(dictCode);
|
if (!requestCache.has(dictCode)) {
|
||||||
dictCache.setCache(dictCode, dictData, 3 * 60 * 1000); // 缓存 3 分钟
|
// 发起请求并存入请求缓存,确保后续请求能复用此 Promise
|
||||||
|
const requestPromise = DictDataAPI.getOptions(dictCode)
|
||||||
|
.then((data) => {
|
||||||
|
dictCache.setCache(dictCode, data, 3 * 60 * 1000); // 缓存 3 分钟
|
||||||
|
requestCache.delete(dictCode); // 请求完成后删除请求缓存
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
requestCache.delete(dictCode); // 出错时也要删除缓存
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将当前请求存入请求缓存
|
||||||
|
requestCache.set(dictCode, requestPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待请求完成并获取数据
|
||||||
|
dictData = await requestCache.get(dictCode);
|
||||||
|
console.log(`Received data for ${dictCode}:`, dictData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查找对应的字典项
|
||||||
const dictEntry = dictData.find((item: any) => item.value == value);
|
const dictEntry = dictData.find((item: any) => item.value == value);
|
||||||
return {
|
return {
|
||||||
label: dictEntry ? dictEntry.label : "",
|
label: dictEntry ? dictEntry.label : "",
|
||||||
@@ -40,13 +69,16 @@ const getLabelAndTagByValue = async (dictCode: string, value: any) => {
|
|||||||
// 监听 props 的变化,获取并更新 label 和 tag
|
// 监听 props 的变化,获取并更新 label 和 tag
|
||||||
const fetchLabelAndTag = async () => {
|
const fetchLabelAndTag = async () => {
|
||||||
const result = await getLabelAndTagByValue(
|
const result = await getLabelAndTagByValue(
|
||||||
props.dictCode as string,
|
props.code as string,
|
||||||
props.value
|
props.modelValue
|
||||||
);
|
);
|
||||||
label.value = result.label;
|
label.value = result.label;
|
||||||
tag.value = result.tag;
|
tagType.value = result.tag;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 首次挂载时获取字典数据
|
||||||
onMounted(fetchLabelAndTag);
|
onMounted(fetchLabelAndTag);
|
||||||
watch(() => props.value, fetchLabelAndTag);
|
|
||||||
|
// 当 modelValue 发生变化时重新获取
|
||||||
|
watch(() => props.modelValue, fetchLabelAndTag);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
required: true,
|
required: false,
|
||||||
},
|
},
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -1,58 +1,176 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-dropdown class="flex-center h-full align-middle">
|
<el-dropdown class="flex-center wh-full align-middle">
|
||||||
<el-badge v-if="messages.length > 0" :value="messages.length" :max="99">
|
<div class="wh-full">
|
||||||
<div><i-ep-bell /></div>
|
<el-badge
|
||||||
</el-badge>
|
:offset="[-10, 15]"
|
||||||
<el-badge v-else>
|
v-if="notices.length > 0"
|
||||||
<i-ep-bell />
|
:value="notices.length"
|
||||||
</el-badge>
|
:max="99"
|
||||||
|
class="wh-full"
|
||||||
|
>
|
||||||
|
<i-ep-bell class="notification-icon h-full" />
|
||||||
|
</el-badge>
|
||||||
|
<el-badge :offset="[-10, 15]" v-else>
|
||||||
|
<i-ep-bell class="notification-icon h-full" />
|
||||||
|
</el-badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<div class="p-5">
|
<div class="p-2">
|
||||||
<template v-if="messages.length > 0">
|
<el-tabs v-model="activeTab">
|
||||||
<div
|
<el-tab-pane label="通知" name="notice">
|
||||||
class="w400px flex-x-between py-2"
|
<template v-if="notices.length > 0">
|
||||||
v-for="(item, index) in messages"
|
<div
|
||||||
:key="index"
|
class="w400px flex-x-between p-1"
|
||||||
>
|
v-for="(item, index) in notices"
|
||||||
<div>
|
:key="index"
|
||||||
<el-tag type="success" size="small">系统通知</el-tag>
|
|
||||||
<el-link
|
|
||||||
type="primary"
|
|
||||||
@click="readNotice(item.id)"
|
|
||||||
class="ml-1"
|
|
||||||
>
|
>
|
||||||
{{ item.title }}
|
<div class="flex-center">
|
||||||
|
<DictLabel
|
||||||
|
code="notice_type"
|
||||||
|
v-model="item.type"
|
||||||
|
size="small"
|
||||||
|
class="mr-1"
|
||||||
|
/>
|
||||||
|
<el-text
|
||||||
|
type="primary"
|
||||||
|
@click="readNotice(item.id)"
|
||||||
|
size="small"
|
||||||
|
class="w200px cursor-pointer"
|
||||||
|
truncated
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</el-text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ item.publishTime }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="flex-center h150px w350px">
|
||||||
|
<el-empty :image-size="50" description="暂无通知" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-divider />
|
||||||
|
<div class="flex-x-between">
|
||||||
|
<el-link type="primary" :underline="false" @click="viewMore">
|
||||||
|
<span class="text-xs">查看更多</span>
|
||||||
|
<el-icon class="text-xs">
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</el-link>
|
||||||
|
<el-link
|
||||||
|
v-if="notices.length > 0"
|
||||||
|
type="primary"
|
||||||
|
:underline="false"
|
||||||
|
@click="markAllAsRead"
|
||||||
|
>
|
||||||
|
<span class="text-xs">全部已读</span>
|
||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</el-tab-pane>
|
||||||
{{ item.publishTime }}
|
<el-tab-pane label="消息" name="message">
|
||||||
|
<template v-if="messages.length > 0">
|
||||||
|
<div
|
||||||
|
class="w400px flex-x-between p-1"
|
||||||
|
v-for="(item, index) in messages"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<DictLabel
|
||||||
|
code="notice_type"
|
||||||
|
v-model="item.type"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<el-link
|
||||||
|
type="primary"
|
||||||
|
@click="readNotice(item.id)"
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ item.publishTime }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="flex-center h150px w350px">
|
||||||
|
<el-empty :image-size="50" description="暂无消息" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-divider />
|
||||||
|
<div class="flex-x-between">
|
||||||
|
<el-link type="primary" :underline="false" @click="viewMore">
|
||||||
|
<span class="text-xs">查看更多</span>
|
||||||
|
<el-icon class="text-xs">
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</el-link>
|
||||||
|
<el-link
|
||||||
|
v-if="messages.length > 0"
|
||||||
|
type="primary"
|
||||||
|
:underline="false"
|
||||||
|
@click="markAllAsRead"
|
||||||
|
>
|
||||||
|
<span class="text-xs">全部已读</span>
|
||||||
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</el-tab-pane>
|
||||||
</template>
|
|
||||||
<template v-else>
|
<el-tab-pane label="待办" name="task">
|
||||||
<div class="flex-center h150px w350px">
|
<template v-if="tasks.length > 0">
|
||||||
<el-empty :image-size="30" description="暂无消息" />
|
<div
|
||||||
</div>
|
class="w400px flex-x-between p-1"
|
||||||
</template>
|
v-for="(item, index) in tasks"
|
||||||
<el-divider />
|
:key="index"
|
||||||
<div class="flex-x-between">
|
>
|
||||||
<el-link type="primary" :underline="false" @click="viewMore">
|
<div>
|
||||||
<span class="text-xs">查看更多</span>
|
<DictLabel
|
||||||
<el-icon class="text-xs">
|
code="notice_type"
|
||||||
<ArrowRight />
|
v-model="item.type"
|
||||||
</el-icon>
|
size="small"
|
||||||
</el-link>
|
/>
|
||||||
<el-link
|
<el-link
|
||||||
v-if="messages.length > 0"
|
type="primary"
|
||||||
type="primary"
|
@click="readNotice(item.id)"
|
||||||
:underline="false"
|
class="ml-1"
|
||||||
@click="markAllAsRead"
|
>
|
||||||
>
|
{{ item.title }}
|
||||||
<span class="text-xs">全部已读</span>
|
</el-link>
|
||||||
</el-link>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
|
{{ item.publishTime }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="flex-center h150px w350px">
|
||||||
|
<el-empty :image-size="50" description="暂无待办" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-divider />
|
||||||
|
<div class="flex-x-between">
|
||||||
|
<el-link type="primary" :underline="false" @click="viewMore">
|
||||||
|
<span class="text-xs">查看更多</span>
|
||||||
|
<el-icon class="text-xs">
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</el-link>
|
||||||
|
<el-link
|
||||||
|
v-if="tasks.length > 0"
|
||||||
|
type="primary"
|
||||||
|
:underline="false"
|
||||||
|
@click="markAllAsRead"
|
||||||
|
>
|
||||||
|
<span class="text-xs">全部已读</span>
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
@@ -62,18 +180,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import NoticeAPI, { NoticePageQuery, NoticePageVO } from "@/api/notice";
|
import NoticeAPI, { NoticePageVO } from "@/api/notice";
|
||||||
import WebSocketManager from "@/utils/socket";
|
import WebSocketManager from "@/utils/websocket";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
|
||||||
const messages = ref<NoticePageVO[]>([]);
|
const activeTab = ref("notice");
|
||||||
|
const notices = ref<NoticePageVO[]>([]);
|
||||||
|
const messages = ref<any[]>([]);
|
||||||
|
const tasks = ref<any[]>([]);
|
||||||
const noticeDetailRef = ref();
|
const noticeDetailRef = ref();
|
||||||
|
|
||||||
// 获取未读消息列表并连接 WebSocket
|
// 获取未读消息列表并连接 WebSocket
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 5, isRead: 0 }).then(
|
NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 5, isRead: 0 }).then(
|
||||||
(data) => {
|
(data) => {
|
||||||
messages.value = data.list;
|
notices.value = data.list;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -81,10 +202,12 @@ onMounted(() => {
|
|||||||
console.log("收到消息:", message);
|
console.log("收到消息:", message);
|
||||||
const data = JSON.parse(message);
|
const data = JSON.parse(message);
|
||||||
const id = data.id;
|
const id = data.id;
|
||||||
if (!messages.value.some((msg) => msg.id === id)) {
|
if (!notices.value.some((notice) => notice.id == id)) {
|
||||||
messages.value.unshift({
|
notices.value.unshift({
|
||||||
id,
|
id,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
|
type: data.type,
|
||||||
|
publishTime: data.publishTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
ElNotification({
|
ElNotification({
|
||||||
@@ -100,9 +223,9 @@ onMounted(() => {
|
|||||||
// 阅读通知公告
|
// 阅读通知公告
|
||||||
function readNotice(id: string) {
|
function readNotice(id: string) {
|
||||||
noticeDetailRef.value.openNotice(id);
|
noticeDetailRef.value.openNotice(id);
|
||||||
const index = messages.value.findIndex((msg) => msg.id === id);
|
const index = notices.value.findIndex((notice) => notice.id === id);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
messages.value.splice(index, 1); // 从消息列表中移除已读消息
|
notices.value.splice(index, 1); // 从消息列表中移除已读消息
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,9 +237,14 @@ function viewMore() {
|
|||||||
// 全部已读
|
// 全部已读
|
||||||
function markAllAsRead() {
|
function markAllAsRead() {
|
||||||
NoticeAPI.readAll().then(() => {
|
NoticeAPI.readAll().then(() => {
|
||||||
messages.value = [];
|
notices.value = [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.layout-top .notification-icon,
|
||||||
|
.layout-mix .notification-icon {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="app-main" :style="{ minHeight: minHeight }">
|
<section class="app-main" :style="{ height: height }">
|
||||||
<router-view>
|
<router-view>
|
||||||
<template #default="{ Component, route }">
|
<template #default="{ Component, route }">
|
||||||
<transition
|
<transition
|
||||||
@@ -19,8 +19,9 @@
|
|||||||
import { useSettingsStore, useTagsViewStore } from "@/store";
|
import { useSettingsStore, useTagsViewStore } from "@/store";
|
||||||
import variables from "@/styles/variables.module.scss";
|
import variables from "@/styles/variables.module.scss";
|
||||||
|
|
||||||
const cachedViews = computed(() => useTagsViewStore().cachedViews); // 缓存页面集合
|
// 缓存页面集合
|
||||||
const minHeight = computed(() => {
|
const cachedViews = computed(() => useTagsViewStore().cachedViews);
|
||||||
|
const height = computed(() => {
|
||||||
if (useSettingsStore().tagsView) {
|
if (useSettingsStore().tagsView) {
|
||||||
return `calc(100vh - ${variables["navbar-height"]} - ${variables["tags-view-height"]})`;
|
return `calc(100vh - ${variables["navbar-height"]} - ${variables["tags-view-height"]})`;
|
||||||
} else {
|
} else {
|
||||||
@@ -32,6 +33,7 @@ const minHeight = computed(() => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.app-main {
|
.app-main {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow-y: auto;
|
||||||
background-color: var(--el-bg-color-page);
|
background-color: var(--el-bg-color-page);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ import "element-plus/theme-chalk/dark/css-vars.css";
|
|||||||
import "@/styles/index.scss";
|
import "@/styles/index.scss";
|
||||||
import "uno.css";
|
import "uno.css";
|
||||||
import "animate.css";
|
import "animate.css";
|
||||||
import { InstallCodeMirror } from "codemirror-editor-vue3";
|
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
// 注册插件
|
// 注册插件
|
||||||
app.use(setupPlugins);
|
app.use(setupPlugins);
|
||||||
app.use(InstallCodeMirror);
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import type { App } from "vue";
|
||||||
|
|
||||||
import { setupDirective } from "@/directive";
|
import { setupDirective } from "@/directive";
|
||||||
import { setupI18n } from "@/lang";
|
import { setupI18n } from "@/lang";
|
||||||
import { setupRouter } from "@/router";
|
import { setupRouter } from "@/router";
|
||||||
import { setupStore } from "@/store";
|
import { setupStore } from "@/store";
|
||||||
import type { App } from "vue";
|
|
||||||
import { setupElIcons } from "./icons";
|
import { setupElIcons } from "./icons";
|
||||||
import { setupPermission } from "./permission";
|
import { setupPermission } from "./permission";
|
||||||
|
import webSocketManager from "@/utils/websocket";
|
||||||
|
import { InstallCodeMirror } from "codemirror-editor-vue3";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install(app: App<Element>) {
|
install(app: App<Element>) {
|
||||||
@@ -20,5 +23,9 @@ export default {
|
|||||||
setupElIcons(app);
|
setupElIcons(app);
|
||||||
// 路由守卫
|
// 路由守卫
|
||||||
setupPermission();
|
setupPermission();
|
||||||
|
// 初始化 WebSocket
|
||||||
|
webSocketManager.setupWebSocket();
|
||||||
|
// 注册 CodeMirror
|
||||||
|
app.use(InstallCodeMirror);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
4
src/utils/requestCache.ts
Normal file
4
src/utils/requestCache.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// 创建一个共享的 requestCache
|
||||||
|
const requestCache = new Map<string, Promise<any>>();
|
||||||
|
|
||||||
|
export default requestCache;
|
||||||
@@ -1,22 +1,19 @@
|
|||||||
import { Client } from "@stomp/stompjs";
|
import { Client } from "@stomp/stompjs";
|
||||||
import { getToken } from "@/utils/auth";
|
import { getToken } from "@/utils/auth";
|
||||||
|
|
||||||
const MAX_RECONNECT_ATTEMPTS = 3;
|
|
||||||
const RECONNECT_DELAY_MS = 5000;
|
|
||||||
const HEARTBEAT_INTERVAL_MS = 30000;
|
|
||||||
|
|
||||||
class WebSocketManager {
|
class WebSocketManager {
|
||||||
private client: Client | null = null;
|
private client: Client | null = null;
|
||||||
private reconnectAttempts: number = 0;
|
|
||||||
private messageHandlers: Map<string, ((message: string) => void)[]> =
|
private messageHandlers: Map<string, ((message: string) => void)[]> =
|
||||||
new Map();
|
new Map();
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
private getOrCreateClient(onError?: (error: any) => void): Client {
|
// 初始化 WebSocket 客户端
|
||||||
|
setupWebSocket() {
|
||||||
const endpoint = import.meta.env.VITE_APP_WS_ENDPOINT;
|
const endpoint = import.meta.env.VITE_APP_WS_ENDPOINT;
|
||||||
|
|
||||||
if (this.client) {
|
if (this.client && this.client.connected) {
|
||||||
|
console.log("客户端已存在并且连接正常");
|
||||||
return this.client;
|
return this.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,10 +22,10 @@ class WebSocketManager {
|
|||||||
connectHeaders: {
|
connectHeaders: {
|
||||||
Authorization: getToken(),
|
Authorization: getToken(),
|
||||||
},
|
},
|
||||||
heartbeatIncoming: HEARTBEAT_INTERVAL_MS,
|
heartbeatIncoming: 30000,
|
||||||
heartbeatOutgoing: HEARTBEAT_INTERVAL_MS,
|
heartbeatOutgoing: 30000,
|
||||||
onConnect: () => {
|
onConnect: () => {
|
||||||
console.log(`已连接到 WebSocket 服务器: ${endpoint}`);
|
console.log(`连接到 WebSocket 服务器: ${endpoint}`);
|
||||||
this.messageHandlers.forEach((handlers, topic) => {
|
this.messageHandlers.forEach((handlers, topic) => {
|
||||||
handlers.forEach((handler) => {
|
handlers.forEach((handler) => {
|
||||||
this.subscribeToTopic(topic, handler);
|
this.subscribeToTopic(topic, handler);
|
||||||
@@ -36,33 +33,22 @@ class WebSocketManager {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onStompError: (frame) => {
|
onStompError: (frame) => {
|
||||||
console.error(
|
console.error(`连接错误: ${frame.headers["message"]}`);
|
||||||
`连接错误: ${endpoint}, 错误消息: ${frame.headers["message"]}`
|
|
||||||
);
|
|
||||||
console.error(`错误详情: ${frame.body}`);
|
console.error(`错误详情: ${frame.body}`);
|
||||||
if (onError) {
|
|
||||||
onError(frame);
|
|
||||||
}
|
|
||||||
this.handleReconnect();
|
|
||||||
},
|
},
|
||||||
onDisconnect: () => {
|
onDisconnect: () => {
|
||||||
console.log(`已断开连接: ${endpoint}`);
|
console.log(`WebSocket 连接已断开: ${endpoint}`);
|
||||||
this.handleReconnect();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.client.activate();
|
this.client.activate();
|
||||||
return this.client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscribeToTopic(
|
// 订阅主题
|
||||||
topic: string,
|
public subscribeToTopic(topic: string, onMessage: (message: string) => void) {
|
||||||
onMessage: (message: string) => void,
|
console.log(`正在订阅主题: ${topic}`);
|
||||||
onError?: (error: any) => void
|
|
||||||
) {
|
|
||||||
if (!this.client || !this.client.connected) {
|
if (!this.client || !this.client.connected) {
|
||||||
console.log("WebSocket 尚未连接,正在连接...");
|
this.setupWebSocket();
|
||||||
this.getOrCreateClient(onError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.messageHandlers.has(topic)) {
|
if (this.messageHandlers.has(topic)) {
|
||||||
@@ -72,7 +58,6 @@ class WebSocketManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.client?.connected) {
|
if (this.client?.connected) {
|
||||||
console.log(`正在订阅主题: ${topic}`);
|
|
||||||
this.client.subscribe(topic, (message) => {
|
this.client.subscribe(topic, (message) => {
|
||||||
const handlers = this.messageHandlers.get(topic);
|
const handlers = this.messageHandlers.get(topic);
|
||||||
handlers?.forEach((handler) => handler(message.body));
|
handlers?.forEach((handler) => handler(message.body));
|
||||||
@@ -80,23 +65,7 @@ class WebSocketManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleReconnect() {
|
// 断开 WebSocket 连接
|
||||||
if (this.reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
||||||
this.reconnectAttempts++;
|
|
||||||
console.log(
|
|
||||||
`重连尝试 (${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.client?.deactivate();
|
|
||||||
this.client = null;
|
|
||||||
this.getOrCreateClient();
|
|
||||||
}, RECONNECT_DELAY_MS);
|
|
||||||
} else {
|
|
||||||
console.error("达到最大重连次数,停止重连");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public disconnect() {
|
public disconnect() {
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
console.log("断开 WebSocket 连接");
|
console.log("断开 WebSocket 连接");
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
<el-icon class="ml-1"><Notification /></el-icon>
|
<el-icon class="ml-1"><Notification /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<el-link type="primary">
|
<el-link type="primary">
|
||||||
<span class="text-xs">查看更多</span>
|
<span class="text-xs" @click="viewMoreNotice">查看更多</span>
|
||||||
<el-icon class="text-xs"><ArrowRight /></el-icon>
|
<el-icon class="text-xs"><ArrowRight /></el-icon>
|
||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,16 +167,14 @@
|
|||||||
:key="index"
|
:key="index"
|
||||||
class="flex-y-center py-3"
|
class="flex-y-center py-3"
|
||||||
>
|
>
|
||||||
<el-tag :type="getNoticeLevelTag(item.level)" size="small">
|
<DictLabel code="notice_type" v-model="item.type" size="small" />
|
||||||
{{ getNoticeLabel(item.type) }}
|
|
||||||
</el-tag>
|
|
||||||
<el-text
|
<el-text
|
||||||
truncated
|
truncated
|
||||||
class="!mx-2 flex-1 !text-xs !text-[var(--el-text-color-secondary)]"
|
class="!mx-2 flex-1 !text-xs !text-[var(--el-text-color-secondary)]"
|
||||||
>
|
>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</el-text>
|
</el-text>
|
||||||
<el-link>
|
<el-link @click="viewNoticeDetail(item.id)">
|
||||||
<el-icon class="text-sm"><View /></el-icon>
|
<el-icon class="text-sm"><View /></el-icon>
|
||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,20 +182,27 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<NoticeDetail ref="noticeDetailRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import WebSocketManager from "@/utils/socket";
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "Dashboard",
|
name: "Dashboard",
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
import WebSocketManager from "@/utils/websocket";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { NoticeTypeEnum, getNoticeLabel } from "@/enums/NoticeTypeEnum";
|
import { NoticeTypeEnum, getNoticeLabel } from "@/enums/NoticeTypeEnum";
|
||||||
import StatsAPI, { VisitStatsVO } from "@/api/log";
|
import StatsAPI, { VisitStatsVO } from "@/api/log";
|
||||||
|
import NoticeAPI, { NoticePageVO } from "@/api/notice";
|
||||||
|
|
||||||
|
const noticeDetailRef = ref();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const date: Date = new Date();
|
const date: Date = new Date();
|
||||||
const greetings = computed(() => {
|
const greetings = computed(() => {
|
||||||
@@ -247,13 +252,13 @@ interface VisitStats {
|
|||||||
icon: string;
|
icon: string;
|
||||||
tagType: "primary" | "success" | "warning";
|
tagType: "primary" | "success" | "warning";
|
||||||
growthRate: number;
|
growthRate: number;
|
||||||
/** 粒度 */
|
// 粒度
|
||||||
granularity: string;
|
granularity: string;
|
||||||
/** 今日数量输出文档 */
|
// 今日数量
|
||||||
todayCount: number;
|
todayCount: number;
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
}
|
}
|
||||||
/** 加载访问统计数据 */
|
// 加载访问统计数据
|
||||||
const loadVisitStatsData = async () => {
|
const loadVisitStatsData = async () => {
|
||||||
const list: VisitStatsVO[] = await StatsAPI.getVisitStats();
|
const list: VisitStatsVO[] = await StatsAPI.getVisitStats();
|
||||||
|
|
||||||
@@ -314,88 +319,30 @@ const getVisitStatsIcon = (type: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const notices = ref([
|
const notices = ref<NoticePageVO[]>([]);
|
||||||
{
|
|
||||||
level: 2,
|
|
||||||
type: NoticeTypeEnum.SYSTEM_UPGRADE,
|
|
||||||
title: "v2.12.0 新增系统日志,访问趋势统计功能。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
level: 0,
|
|
||||||
type: NoticeTypeEnum.COMPANY_NEWS,
|
|
||||||
title: "公司将在 7 月 1 日举办年中总结大会,请各部门做好准备。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
level: 3,
|
|
||||||
type: NoticeTypeEnum.HOLIDAY_NOTICE,
|
|
||||||
title: "端午节假期从 6 月 12 日至 6 月 14 日放假,共 3 天。",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
// 查看更多
|
||||||
level: 2,
|
function viewMoreNotice() {
|
||||||
type: NoticeTypeEnum.SECURITY_ALERT,
|
router.push({ path: "/myNotice" });
|
||||||
title: "最近发现一些钓鱼邮件,请大家提高警惕,不要点击陌生链接。",
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
level: 2,
|
|
||||||
type: NoticeTypeEnum.SYSTEM_MAINTENANCE,
|
|
||||||
title: "系统将于本周六凌晨 2 点进行维护,预计维护时间为 2 小时。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
level: 0,
|
|
||||||
type: NoticeTypeEnum.OTHER,
|
|
||||||
title: "公司新规章制度发布,请大家及时查阅。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
level: 3,
|
|
||||||
type: NoticeTypeEnum.HOLIDAY_NOTICE,
|
|
||||||
title: "中秋节假期从 9 月 22 日至 9 月 24 日放假,共 3 天。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
level: 1,
|
|
||||||
type: NoticeTypeEnum.COMPANY_NEWS,
|
|
||||||
title: "公司将在 10 月 15 日举办新产品发布会,敬请期待。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
level: 2,
|
|
||||||
type: NoticeTypeEnum.SECURITY_ALERT,
|
|
||||||
title:
|
|
||||||
"请注意,近期有恶意软件通过即时通讯工具传播,请勿下载不明来源的文件。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
level: 2,
|
|
||||||
type: NoticeTypeEnum.SYSTEM_MAINTENANCE,
|
|
||||||
title: "系统将于下周日凌晨 3 点进行升级,预计维护时间为 1 小时。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
level: 3,
|
|
||||||
type: NoticeTypeEnum.OTHER,
|
|
||||||
title: "公司年度体检通知已发布,请各位员工按时参加。",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const getNoticeLevelTag = (type: number) => {
|
// 阅读通知公告
|
||||||
switch (type) {
|
function viewNoticeDetail(id: string) {
|
||||||
case 0:
|
noticeDetailRef.value.openNotice(id);
|
||||||
return "danger";
|
|
||||||
case 1:
|
|
||||||
return "warning";
|
|
||||||
case 2:
|
|
||||||
return "primary";
|
|
||||||
default:
|
|
||||||
return "success";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function connectWebSocket() {
|
|
||||||
WebSocketManager.getOrCreateClient("/topic/onlineUserCount", (message) => {
|
|
||||||
onlineUserCount.value = JSON.parse(message);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadVisitStatsData();
|
loadVisitStatsData();
|
||||||
connectWebSocket();
|
|
||||||
|
// 获取我的通知公告
|
||||||
|
NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 10 }).then((data) => {
|
||||||
|
notices.value = data.list;
|
||||||
|
});
|
||||||
|
|
||||||
|
WebSocketManager.subscribeToTopic("/topic/onlineUserCount", (data) => {
|
||||||
|
console.log("收到在线用户数量:", data);
|
||||||
|
onlineUserCount.value = JSON.parse(data);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const contentConfig: IContentConfig = {
|
|||||||
id: 1,
|
id: 1,
|
||||||
username: "tom",
|
username: "tom",
|
||||||
avatar:
|
avatar:
|
||||||
"https://oss.youlai.tech/youlai-boot/2023/05/16/811270ef31f548af9cffc026dfc3777b.gif",
|
"https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif",
|
||||||
percent: 99,
|
percent: 99,
|
||||||
price: 10,
|
price: 10,
|
||||||
url: "https://www.baidu.com",
|
url: "https://www.baidu.com",
|
||||||
@@ -31,7 +31,7 @@ const contentConfig: IContentConfig = {
|
|||||||
id: 2,
|
id: 2,
|
||||||
username: "jerry",
|
username: "jerry",
|
||||||
avatar:
|
avatar:
|
||||||
"https://oss.youlai.tech/youlai-boot/2023/05/16/811270ef31f548af9cffc026dfc3777b.gif",
|
"https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif",
|
||||||
percent: 88,
|
percent: 88,
|
||||||
price: 999,
|
price: 999,
|
||||||
url: "https://www.google.com",
|
url: "https://www.google.com",
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ function connectWebSocket() {
|
|||||||
connectHeaders: {
|
connectHeaders: {
|
||||||
Authorization: getToken(),
|
Authorization: getToken(),
|
||||||
},
|
},
|
||||||
debug: (str) => {
|
debug: (str: any) => {
|
||||||
console.log(str);
|
console.log(str);
|
||||||
},
|
},
|
||||||
onConnect: () => {
|
onConnect: () => {
|
||||||
@@ -155,14 +155,14 @@ function connectWebSocket() {
|
|||||||
type: "tip",
|
type: "tip",
|
||||||
});
|
});
|
||||||
|
|
||||||
stompClient.subscribe("/topic/notice", (res) => {
|
stompClient.subscribe("/topic/notice", (res: any) => {
|
||||||
messages.value.push({
|
messages.value.push({
|
||||||
sender: "Server",
|
sender: "Server",
|
||||||
content: res.body,
|
content: res.body,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
stompClient.subscribe("/user/queue/greeting", (res) => {
|
stompClient.subscribe("/user/queue/greeting", (res: any) => {
|
||||||
const messageData = JSON.parse(res.body) as MessageType;
|
const messageData = JSON.parse(res.body) as MessageType;
|
||||||
messages.value.push({
|
messages.value.push({
|
||||||
sender: messageData.sender,
|
sender: messageData.sender,
|
||||||
@@ -170,7 +170,7 @@ function connectWebSocket() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onStompError: (frame) => {
|
onStompError: (frame: any) => {
|
||||||
console.error("Broker reported error: " + frame.headers["message"]);
|
console.error("Broker reported error: " + frame.headers["message"]);
|
||||||
console.error("Additional details: " + frame.body);
|
console.error("Additional details: " + frame.body);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -174,8 +174,8 @@ import DictDataAPI, {
|
|||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const dictCode = route.query.dictCode as string;
|
const dictCode = ref(route.query.dictCode as string);
|
||||||
const dictName = route.query.dictName as string;
|
const dictName = ref(route.query.dictName as string);
|
||||||
|
|
||||||
const queryFormRef = ref(ElForm);
|
const queryFormRef = ref(ElForm);
|
||||||
const dataFormRef = ref(ElForm);
|
const dataFormRef = ref(ElForm);
|
||||||
@@ -201,15 +201,15 @@ const formData = reactive<DictDataForm>({});
|
|||||||
|
|
||||||
// 监听路由参数变化,更新字典数据
|
// 监听路由参数变化,更新字典数据
|
||||||
watch(
|
watch(
|
||||||
() => route.query.dictCode,
|
() => [route.query.dictCode, route.query.dictName],
|
||||||
(newDictCode) => {
|
([newDictCode, newDictName]) => {
|
||||||
if (newDictCode !== queryParams.dictCode) {
|
queryParams.dictCode = newDictCode as string;
|
||||||
queryParams.dictCode = newDictCode as string;
|
dictCode.value = newDictCode as string;
|
||||||
handleQuery();
|
dictName.value = newDictName as string;
|
||||||
}
|
|
||||||
|
handleQuery();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const computedRules = computed(() => {
|
const computedRules = computed(() => {
|
||||||
const rules: Partial<Record<string, any>> = {
|
const rules: Partial<Record<string, any>> = {
|
||||||
value: [{ required: true, message: "请输入字典值", trigger: "blur" }],
|
value: [{ required: true, message: "请输入字典值", trigger: "blur" }],
|
||||||
@@ -291,6 +291,8 @@ function handleCloseDialog() {
|
|||||||
dataFormRef.value.clearValidate();
|
dataFormRef.value.clearValidate();
|
||||||
|
|
||||||
formData.id = undefined;
|
formData.id = undefined;
|
||||||
|
formData.sort = 1;
|
||||||
|
formData.status = 1;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 删除字典
|
* 删除字典
|
||||||
|
|||||||
@@ -43,13 +43,8 @@
|
|||||||
<el-descriptions-item label="发布时间:">
|
<el-descriptions-item label="发布时间:">
|
||||||
{{ notice.publishTime }}
|
{{ notice.publishTime }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="内容:">
|
<el-descriptions-item label="公告内容:">
|
||||||
<el-input
|
<div v-html="notice.content"></div>
|
||||||
v-model="notice.content"
|
|
||||||
type="textarea"
|
|
||||||
style="max-height: 400px"
|
|
||||||
:readonly="true"
|
|
||||||
/>
|
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|||||||
@@ -70,27 +70,21 @@
|
|||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column type="index" label="序号" width="60" />
|
<el-table-column type="index" label="序号" width="60" />
|
||||||
<el-table-column
|
<el-table-column label="通知标题" prop="title" min-width="200" />
|
||||||
align="center"
|
<el-table-column align="center" label="通知类型" width="150">
|
||||||
key="title"
|
|
||||||
label="通知标题"
|
|
||||||
prop="title"
|
|
||||||
min-width="150"
|
|
||||||
/>
|
|
||||||
<el-table-column align="center" label="通知类型" min-width="150">
|
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<DictLabel :dictCode="'notice_type'" :value="scope.row.type" />
|
<DictLabel :code="'notice_type'" v-model="scope.row.type" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
align="center"
|
align="center"
|
||||||
label="发布人"
|
label="发布人"
|
||||||
prop="publisherName"
|
prop="publisherName"
|
||||||
min-width="100"
|
width="150"
|
||||||
/>
|
/>
|
||||||
<el-table-column align="center" label="通知等级" min-width="100">
|
<el-table-column align="center" label="通知等级" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<DictLabel :dictCode="'notice_level'" :value="scope.row.level" />
|
<DictLabel code="notice_level" v-model="scope.row.level" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -121,7 +115,7 @@
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column align="center" label="操作时间" min-width="220">
|
<el-table-column label="操作时间" width="250">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="flex-x-start">
|
<div class="flex-x-start">
|
||||||
<span>创建时间:</span>
|
<span>创建时间:</span>
|
||||||
@@ -141,7 +135,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column align="center" fixed="right" label="操作" width="220">
|
<el-table-column align="center" fixed="right" label="操作" width="150">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -230,16 +224,16 @@
|
|||||||
<el-form-item label="通知类型" prop="type">
|
<el-form-item label="通知类型" prop="type">
|
||||||
<dictionary
|
<dictionary
|
||||||
type="button"
|
type="button"
|
||||||
v-model="formData.type"
|
|
||||||
code="notice_type"
|
code="notice_type"
|
||||||
|
v-model="formData.type"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="优先级" prop="level">
|
<el-form-item label="通知等级" prop="level">
|
||||||
<el-radio-group v-model="formData.level">
|
<dictionary
|
||||||
<el-radio value="L">低</el-radio>
|
type="button"
|
||||||
<el-radio value="M">中</el-radio>
|
code="notice_level"
|
||||||
<el-radio value="H">高</el-radio>
|
v-model="formData.level"
|
||||||
</el-radio-group>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="目标类型" prop="targetType">
|
<el-form-item label="目标类型" prop="targetType">
|
||||||
<el-radio-group v-model="formData.targetType">
|
<el-radio-group v-model="formData.targetType">
|
||||||
|
|||||||
@@ -31,27 +31,21 @@
|
|||||||
highlight-current-row
|
highlight-current-row
|
||||||
>
|
>
|
||||||
<el-table-column type="index" label="序号" width="60" />
|
<el-table-column type="index" label="序号" width="60" />
|
||||||
<el-table-column
|
<el-table-column label="通知标题" prop="title" min-width="200" />
|
||||||
align="center"
|
<el-table-column align="center" label="通知类型" width="150">
|
||||||
key="title"
|
|
||||||
label="通知标题"
|
|
||||||
prop="title"
|
|
||||||
min-width="150"
|
|
||||||
/>
|
|
||||||
<el-table-column align="center" label="通知类型" min-width="150">
|
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<DictLabel :dictCode="'notice_type'" :value="scope.row.type" />
|
<DictLabel code="notice_type" v-model="scope.row.type" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
align="center"
|
align="center"
|
||||||
label="发布人"
|
label="发布人"
|
||||||
prop="publisherName"
|
prop="publisherName"
|
||||||
min-width="100"
|
width="100"
|
||||||
/>
|
/>
|
||||||
<el-table-column align="center" label="通知等级" min-width="100">
|
<el-table-column align="center" label="通知等级" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<DictLabel :dictCode="'notice_level'" :value="scope.row.type" />
|
<DictLabel code="notice_level" v-model="scope.row.level" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -59,19 +53,19 @@
|
|||||||
key="releaseTime"
|
key="releaseTime"
|
||||||
label="发布时间"
|
label="发布时间"
|
||||||
prop="publishTime"
|
prop="publishTime"
|
||||||
min-width="100"
|
width="150"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<el-table-column
|
<el-table-column
|
||||||
align="center"
|
align="center"
|
||||||
label="发布人"
|
label="发布人"
|
||||||
prop="publisherName"
|
prop="publisherName"
|
||||||
min-width="100"
|
width="150"
|
||||||
/>
|
/>
|
||||||
<el-table-column align="center" label="状态" min-width="100">
|
<el-table-column align="center" label="状态" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.isRead == 1" type="success">已读</el-tag>
|
<el-tag v-if="scope.row.isRead == 1" type="success">已读</el-tag>
|
||||||
<el-tag v-if="scope.row.isRead == 0" type="warning">未读</el-tag>
|
<el-tag v-else type="info">未读</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column align="center" fixed="right" label="操作" width="80">
|
<el-table-column align="center" fixed="right" label="操作" width="80">
|
||||||
|
|||||||
@@ -168,7 +168,7 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="permKeywords"
|
v-model="permKeywords"
|
||||||
clearable
|
clearable
|
||||||
class="w-[200px]"
|
class="w-[150px]"
|
||||||
placeholder="菜单权限名称"
|
placeholder="菜单权限名称"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
|
||||||
<div class="flex-center">
|
<div class="flex-center ml-5">
|
||||||
<el-button type="primary" size="small" plain @click="togglePermTree">
|
<el-button type="primary" size="small" plain @click="togglePermTree">
|
||||||
<i-ep-switch />
|
<i-ep-switch />
|
||||||
{{ isExpanded ? "收缩" : "展开" }}
|
{{ isExpanded ? "收缩" : "展开" }}
|
||||||
|
|||||||
Reference in New Issue
Block a user