refactor: ♻️ 通知公告、字典重构问题修复和优化

This commit is contained in:
ray
2024-10-08 01:02:48 +08:00
parent 42f7782d56
commit ff53ed6060
18 changed files with 341 additions and 269 deletions

View File

@@ -113,7 +113,7 @@ class NoticeAPI {
/** 获取我的通知分页列表 */
static getMyNoticePage(queryParams?: NoticePageQuery) {
return request<any, PageResult<NoticePageVO[]>>({
url: `${NOTICE_BASE_URL}/my/page`,
url: `${NOTICE_BASE_URL}/my-page`,
method: "get",
params: queryParams,
});

View File

@@ -1,6 +1,6 @@
<template>
<template v-if="tag">
<el-tag :type="tag">{{ label }}</el-tag>
<template v-if="tagType">
<el-tag :type="tagType" :size="tagSize">{{ label }}</el-tag>
</template>
<template v-else>
<span>{{ label }}</span>
@@ -10,26 +10,55 @@
<script setup lang="ts">
import DictDataAPI from "@/api/dict-data";
import Cache from "@/utils/cache";
import requestCache from "@/utils/requestCache"; // 导入共享的缓存
const props = defineProps({
dictCode: String,
value: [String, Number],
code: String,
modelValue: [String, Number],
size: {
type: String,
default: "default",
},
});
const label = ref("");
const tag = ref<
const tagType = ref<
"success" | "warning" | "info" | "primary" | "danger" | undefined
>();
const tagSize = ref(props.size as "default" | "large" | "small");
const dictCache = new Cache("dict_");
const getLabelAndTagByValue = async (dictCode: string, value: any) => {
// 先从本地缓存中获取字典数据
let dictData = dictCache.getCache(dictCode);
// 如果本地缓存没有数据,则检查是否已经发起请求
if (!dictData) {
dictData = await DictDataAPI.getOptions(dictCode);
dictCache.setCache(dictCode, dictData, 3 * 60 * 1000); // 缓存 3 分钟
if (!requestCache.has(dictCode)) {
// 发起请求并存入请求缓存,确保后续请求能复用此 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);
return {
label: dictEntry ? dictEntry.label : "",
@@ -40,13 +69,16 @@ const getLabelAndTagByValue = async (dictCode: string, value: any) => {
// 监听 props 的变化,获取并更新 label 和 tag
const fetchLabelAndTag = async () => {
const result = await getLabelAndTagByValue(
props.dictCode as string,
props.value
props.code as string,
props.modelValue
);
label.value = result.label;
tag.value = result.tag;
tagType.value = result.tag;
};
// 首次挂载时获取字典数据
onMounted(fetchLabelAndTag);
watch(() => props.value, fetchLabelAndTag);
// 当 modelValue 发生变化时重新获取
watch(() => props.modelValue, fetchLabelAndTag);
</script>

View File

@@ -25,7 +25,7 @@ const props = defineProps({
},
modelValue: {
type: [String, Number],
required: true,
required: false,
},
placeholder: {
type: String,

View File

@@ -1,58 +1,176 @@
<template>
<div>
<el-dropdown class="flex-center h-full align-middle">
<el-badge v-if="messages.length > 0" :value="messages.length" :max="99">
<div><i-ep-bell /></div>
</el-badge>
<el-badge v-else>
<i-ep-bell />
</el-badge>
<el-dropdown class="flex-center wh-full align-middle">
<div class="wh-full">
<el-badge
:offset="[-10, 15]"
v-if="notices.length > 0"
:value="notices.length"
: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>
<div class="p-5">
<template v-if="messages.length > 0">
<div
class="w400px flex-x-between py-2"
v-for="(item, index) in messages"
:key="index"
>
<div>
<el-tag type="success" size="small">系统通知</el-tag>
<el-link
type="primary"
@click="readNotice(item.id)"
class="ml-1"
<div class="p-2">
<el-tabs v-model="activeTab">
<el-tab-pane label="通知" name="notice">
<template v-if="notices.length > 0">
<div
class="w400px flex-x-between p-1"
v-for="(item, index) in notices"
:key="index"
>
{{ 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>
</div>
<div>
{{ item.publishTime }}
</el-tab-pane>
<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>
</template>
<template v-else>
<div class="flex-center h150px w350px">
<el-empty :image-size="30" 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>
</el-tab-pane>
<el-tab-pane label="待办" name="task">
<template v-if="tasks.length > 0">
<div
class="w400px flex-x-between p-1"
v-for="(item, index) in tasks"
: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="tasks.length > 0"
type="primary"
:underline="false"
@click="markAllAsRead"
>
<span class="text-xs">全部已读</span>
</el-link>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
</el-dropdown>
@@ -62,18 +180,21 @@
</template>
<script setup lang="ts">
import NoticeAPI, { NoticePageQuery, NoticePageVO } from "@/api/notice";
import WebSocketManager from "@/utils/socket";
import NoticeAPI, { NoticePageVO } from "@/api/notice";
import WebSocketManager from "@/utils/websocket";
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();
// 获取未读消息列表并连接 WebSocket
onMounted(() => {
NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 5, isRead: 0 }).then(
(data) => {
messages.value = data.list;
notices.value = data.list;
}
);
@@ -81,10 +202,12 @@ onMounted(() => {
console.log("收到消息:", message);
const data = JSON.parse(message);
const id = data.id;
if (!messages.value.some((msg) => msg.id === id)) {
messages.value.unshift({
if (!notices.value.some((notice) => notice.id == id)) {
notices.value.unshift({
id,
title: data.title,
type: data.type,
publishTime: data.publishTime,
});
ElNotification({
@@ -100,9 +223,9 @@ onMounted(() => {
// 阅读通知公告
function readNotice(id: string) {
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) {
messages.value.splice(index, 1); // 从消息列表中移除已读消息
notices.value.splice(index, 1); // 从消息列表中移除已读消息
}
}
@@ -114,9 +237,14 @@ function viewMore() {
// 全部已读
function markAllAsRead() {
NoticeAPI.readAll().then(() => {
messages.value = [];
notices.value = [];
});
}
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.layout-top .notification-icon,
.layout-mix .notification-icon {
color: #fff;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<section class="app-main" :style="{ minHeight: minHeight }">
<section class="app-main" :style="{ height: height }">
<router-view>
<template #default="{ Component, route }">
<transition
@@ -19,8 +19,9 @@
import { useSettingsStore, useTagsViewStore } from "@/store";
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) {
return `calc(100vh - ${variables["navbar-height"]} - ${variables["tags-view-height"]})`;
} else {
@@ -32,6 +33,7 @@ const minHeight = computed(() => {
<style lang="scss" scoped>
.app-main {
position: relative;
overflow-y: auto;
background-color: var(--el-bg-color-page);
}
</style>

View File

@@ -10,10 +10,8 @@ import "element-plus/theme-chalk/dark/css-vars.css";
import "@/styles/index.scss";
import "uno.css";
import "animate.css";
import { InstallCodeMirror } from "codemirror-editor-vue3";
const app = createApp(App);
// 注册插件
app.use(setupPlugins);
app.use(InstallCodeMirror);
app.mount("#app");

View File

@@ -1,10 +1,13 @@
import type { App } from "vue";
import { setupDirective } from "@/directive";
import { setupI18n } from "@/lang";
import { setupRouter } from "@/router";
import { setupStore } from "@/store";
import type { App } from "vue";
import { setupElIcons } from "./icons";
import { setupPermission } from "./permission";
import webSocketManager from "@/utils/websocket";
import { InstallCodeMirror } from "codemirror-editor-vue3";
export default {
install(app: App<Element>) {
@@ -20,5 +23,9 @@ export default {
setupElIcons(app);
// 路由守卫
setupPermission();
// 初始化 WebSocket
webSocketManager.setupWebSocket();
// 注册 CodeMirror
app.use(InstallCodeMirror);
},
};

View File

@@ -0,0 +1,4 @@
// 创建一个共享的 requestCache
const requestCache = new Map<string, Promise<any>>();
export default requestCache;

View File

@@ -1,22 +1,19 @@
import { Client } from "@stomp/stompjs";
import { getToken } from "@/utils/auth";
const MAX_RECONNECT_ATTEMPTS = 3;
const RECONNECT_DELAY_MS = 5000;
const HEARTBEAT_INTERVAL_MS = 30000;
class WebSocketManager {
private client: Client | null = null;
private reconnectAttempts: number = 0;
private messageHandlers: Map<string, ((message: string) => void)[]> =
new Map();
constructor() {}
private getOrCreateClient(onError?: (error: any) => void): Client {
// 初始化 WebSocket 客户端
setupWebSocket() {
const endpoint = import.meta.env.VITE_APP_WS_ENDPOINT;
if (this.client) {
if (this.client && this.client.connected) {
console.log("客户端已存在并且连接正常");
return this.client;
}
@@ -25,10 +22,10 @@ class WebSocketManager {
connectHeaders: {
Authorization: getToken(),
},
heartbeatIncoming: HEARTBEAT_INTERVAL_MS,
heartbeatOutgoing: HEARTBEAT_INTERVAL_MS,
heartbeatIncoming: 30000,
heartbeatOutgoing: 30000,
onConnect: () => {
console.log(`连接到 WebSocket 服务器: ${endpoint}`);
console.log(`连接到 WebSocket 服务器: ${endpoint}`);
this.messageHandlers.forEach((handlers, topic) => {
handlers.forEach((handler) => {
this.subscribeToTopic(topic, handler);
@@ -36,33 +33,22 @@ class WebSocketManager {
});
},
onStompError: (frame) => {
console.error(
`连接错误: ${endpoint}, 错误消息: ${frame.headers["message"]}`
);
console.error(`连接错误: ${frame.headers["message"]}`);
console.error(`错误详情: ${frame.body}`);
if (onError) {
onError(frame);
}
this.handleReconnect();
},
onDisconnect: () => {
console.log(`已断开连接: ${endpoint}`);
this.handleReconnect();
console.log(`WebSocket 连接已断开: ${endpoint}`);
},
});
this.client.activate();
return this.client;
}
public subscribeToTopic(
topic: string,
onMessage: (message: string) => void,
onError?: (error: any) => void
) {
// 订阅主题
public subscribeToTopic(topic: string, onMessage: (message: string) => void) {
console.log(`正在订阅主题: ${topic}`);
if (!this.client || !this.client.connected) {
console.log("WebSocket 尚未连接,正在连接...");
this.getOrCreateClient(onError);
this.setupWebSocket();
}
if (this.messageHandlers.has(topic)) {
@@ -72,7 +58,6 @@ class WebSocketManager {
}
if (this.client?.connected) {
console.log(`正在订阅主题: ${topic}`);
this.client.subscribe(topic, (message) => {
const handlers = this.messageHandlers.get(topic);
handlers?.forEach((handler) => handler(message.body));
@@ -80,23 +65,7 @@ class WebSocketManager {
}
}
private handleReconnect() {
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("达到最大重连次数,停止重连");
}
}
// 断开 WebSocket 连接
public disconnect() {
if (this.client) {
console.log("断开 WebSocket 连接");

View File

@@ -155,7 +155,7 @@
<el-icon class="ml-1"><Notification /></el-icon>
</div>
<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-link>
</div>
@@ -167,16 +167,14 @@
:key="index"
class="flex-y-center py-3"
>
<el-tag :type="getNoticeLevelTag(item.level)" size="small">
{{ getNoticeLabel(item.type) }}
</el-tag>
<DictLabel code="notice_type" v-model="item.type" size="small" />
<el-text
truncated
class="!mx-2 flex-1 !text-xs !text-[var(--el-text-color-secondary)]"
>
{{ item.title }}
</el-text>
<el-link>
<el-link @click="viewNoticeDetail(item.id)">
<el-icon class="text-sm"><View /></el-icon>
</el-link>
</div>
@@ -184,20 +182,27 @@
</el-card>
</el-col>
</el-row>
<NoticeDetail ref="noticeDetailRef" />
</div>
</template>
<script setup lang="ts">
import WebSocketManager from "@/utils/socket";
defineOptions({
name: "Dashboard",
inheritAttrs: false,
});
import WebSocketManager from "@/utils/websocket";
import router from "@/router";
import { useUserStore } from "@/store/modules/user";
import { NoticeTypeEnum, getNoticeLabel } from "@/enums/NoticeTypeEnum";
import StatsAPI, { VisitStatsVO } from "@/api/log";
import NoticeAPI, { NoticePageVO } from "@/api/notice";
const noticeDetailRef = ref();
const userStore = useUserStore();
const date: Date = new Date();
const greetings = computed(() => {
@@ -247,13 +252,13 @@ interface VisitStats {
icon: string;
tagType: "primary" | "success" | "warning";
growthRate: number;
/** 粒度 */
// 粒度
granularity: string;
/** 今日数量输出文档 */
// 今日数量
todayCount: number;
totalCount: number;
}
/** 加载访问统计数据 */
// 加载访问统计数据
const loadVisitStatsData = async () => {
const list: VisitStatsVO[] = await StatsAPI.getVisitStats();
@@ -314,88 +319,30 @@ const getVisitStatsIcon = (type: string) => {
}
};
const notices = ref([
{
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 天。",
},
const notices = ref<NoticePageVO[]>([]);
{
level: 2,
type: NoticeTypeEnum.SECURITY_ALERT,
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: "公司年度体检通知已发布,请各位员工按时参加。",
},
]);
// 查看更多
function viewMoreNotice() {
router.push({ path: "/myNotice" });
}
const getNoticeLevelTag = (type: number) => {
switch (type) {
case 0:
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);
});
// 阅读通知公告
function viewNoticeDetail(id: string) {
noticeDetailRef.value.openNotice(id);
}
onMounted(() => {
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>

View File

@@ -16,7 +16,7 @@ const contentConfig: IContentConfig = {
id: 1,
username: "tom",
avatar:
"https://oss.youlai.tech/youlai-boot/2023/05/16/811270ef31f548af9cffc026dfc3777b.gif",
"https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif",
percent: 99,
price: 10,
url: "https://www.baidu.com",
@@ -31,7 +31,7 @@ const contentConfig: IContentConfig = {
id: 2,
username: "jerry",
avatar:
"https://oss.youlai.tech/youlai-boot/2023/05/16/811270ef31f548af9cffc026dfc3777b.gif",
"https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif",
percent: 88,
price: 999,
url: "https://www.google.com",

View File

@@ -143,7 +143,7 @@ function connectWebSocket() {
connectHeaders: {
Authorization: getToken(),
},
debug: (str) => {
debug: (str: any) => {
console.log(str);
},
onConnect: () => {
@@ -155,14 +155,14 @@ function connectWebSocket() {
type: "tip",
});
stompClient.subscribe("/topic/notice", (res) => {
stompClient.subscribe("/topic/notice", (res: any) => {
messages.value.push({
sender: "Server",
content: res.body,
});
});
stompClient.subscribe("/user/queue/greeting", (res) => {
stompClient.subscribe("/user/queue/greeting", (res: any) => {
const messageData = JSON.parse(res.body) as MessageType;
messages.value.push({
sender: messageData.sender,
@@ -170,7 +170,7 @@ function connectWebSocket() {
});
});
},
onStompError: (frame) => {
onStompError: (frame: any) => {
console.error("Broker reported error: " + frame.headers["message"]);
console.error("Additional details: " + frame.body);
},

View File

@@ -174,8 +174,8 @@ import DictDataAPI, {
const route = useRoute();
const dictCode = route.query.dictCode as string;
const dictName = route.query.dictName as string;
const dictCode = ref(route.query.dictCode as string);
const dictName = ref(route.query.dictName as string);
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
@@ -201,15 +201,15 @@ const formData = reactive<DictDataForm>({});
// 监听路由参数变化,更新字典数据
watch(
() => route.query.dictCode,
(newDictCode) => {
if (newDictCode !== queryParams.dictCode) {
queryParams.dictCode = newDictCode as string;
handleQuery();
}
() => [route.query.dictCode, route.query.dictName],
([newDictCode, newDictName]) => {
queryParams.dictCode = newDictCode as string;
dictCode.value = newDictCode as string;
dictName.value = newDictName as string;
handleQuery();
}
);
const computedRules = computed(() => {
const rules: Partial<Record<string, any>> = {
value: [{ required: true, message: "请输入字典值", trigger: "blur" }],
@@ -291,6 +291,8 @@ function handleCloseDialog() {
dataFormRef.value.clearValidate();
formData.id = undefined;
formData.sort = 1;
formData.status = 1;
}
/**
* 删除字典

View File

@@ -43,13 +43,8 @@
<el-descriptions-item label="发布时间:">
{{ notice.publishTime }}
</el-descriptions-item>
<el-descriptions-item label="内容:">
<el-input
v-model="notice.content"
type="textarea"
style="max-height: 400px"
:readonly="true"
/>
<el-descriptions-item label="公告内容:">
<div v-html="notice.content"></div>
</el-descriptions-item>
</el-descriptions>
</el-dialog>

View File

@@ -70,27 +70,21 @@
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" />
<el-table-column
align="center"
key="title"
label="通知标题"
prop="title"
min-width="150"
/>
<el-table-column align="center" label="通知类型" min-width="150">
<el-table-column label="通知标题" prop="title" min-width="200" />
<el-table-column align="center" label="通知类型" width="150">
<template #default="scope">
<DictLabel :dictCode="'notice_type'" :value="scope.row.type" />
<DictLabel :code="'notice_type'" v-model="scope.row.type" />
</template>
</el-table-column>
<el-table-column
align="center"
label="发布人"
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">
<DictLabel :dictCode="'notice_level'" :value="scope.row.level" />
<DictLabel code="notice_level" v-model="scope.row.level" />
</template>
</el-table-column>
<el-table-column
@@ -121,7 +115,7 @@
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="操作时间" min-width="220">
<el-table-column label="操作时间" width="250">
<template #default="scope">
<div class="flex-x-start">
<span>创建时间</span>
@@ -141,7 +135,7 @@
</div>
</template>
</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">
<el-button
type="primary"
@@ -230,16 +224,16 @@
<el-form-item label="通知类型" prop="type">
<dictionary
type="button"
v-model="formData.type"
code="notice_type"
v-model="formData.type"
/>
</el-form-item>
<el-form-item label="优先级" prop="level">
<el-radio-group v-model="formData.level">
<el-radio value="L"></el-radio>
<el-radio value="M"></el-radio>
<el-radio value="H"></el-radio>
</el-radio-group>
<el-form-item label="通知等级" prop="level">
<dictionary
type="button"
code="notice_level"
v-model="formData.level"
/>
</el-form-item>
<el-form-item label="目标类型" prop="targetType">
<el-radio-group v-model="formData.targetType">

View File

@@ -31,27 +31,21 @@
highlight-current-row
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column
align="center"
key="title"
label="通知标题"
prop="title"
min-width="150"
/>
<el-table-column align="center" label="通知类型" min-width="150">
<el-table-column label="通知标题" prop="title" min-width="200" />
<el-table-column align="center" label="通知类型" width="150">
<template #default="scope">
<DictLabel :dictCode="'notice_type'" :value="scope.row.type" />
<DictLabel code="notice_type" v-model="scope.row.type" />
</template>
</el-table-column>
<el-table-column
align="center"
label="发布人"
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">
<DictLabel :dictCode="'notice_level'" :value="scope.row.type" />
<DictLabel code="notice_level" v-model="scope.row.level" />
</template>
</el-table-column>
<el-table-column
@@ -59,19 +53,19 @@
key="releaseTime"
label="发布时间"
prop="publishTime"
min-width="100"
width="150"
/>
<el-table-column
align="center"
label="发布人"
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">
<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>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="80">

View File

@@ -168,7 +168,7 @@
<el-input
v-model="permKeywords"
clearable
class="w-[200px]"
class="w-[150px]"
placeholder="菜单权限名称"
>
<template #prefix>
@@ -176,7 +176,7 @@
</template>
</el-input>
<div class="flex-center">
<div class="flex-center ml-5">
<el-button type="primary" size="small" plain @click="togglePermTree">
<i-ep-switch />
{{ isExpanded ? "收缩" : "展开" }}