feat: 项目结构重构优化
This commit is contained in:
82
src/components/NoticeDropdown/index.vue
Normal file
82
src/components/NoticeDropdown/index.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<el-dropdown trigger="click">
|
||||
<el-badge v-if="list.length > 0" :value="list.length" :max="99">
|
||||
<div class="i-svg:bell" />
|
||||
</el-badge>
|
||||
|
||||
<div v-else class="i-svg:bell" />
|
||||
|
||||
<template #dropdown>
|
||||
<div class="p-5">
|
||||
<template v-if="list.length > 0">
|
||||
<div v-for="item in list" :key="item.id" class="w-500px py-3">
|
||||
<div class="flex-y-center">
|
||||
<DictTag v-model="item.type" code="notice_type" size="small" />
|
||||
<el-text
|
||||
size="small"
|
||||
class="w-200px cursor-pointer !ml-2 !flex-1"
|
||||
truncated
|
||||
@click="read(item.id)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</el-text>
|
||||
|
||||
<div class="text-xs text-gray">
|
||||
{{ item.publishTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="flex-x-between">
|
||||
<el-link type="primary" underline="never" @click="goMore">
|
||||
<span class="text-xs">查看更多</span>
|
||||
<el-icon class="text-xs">
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
</el-link>
|
||||
<el-link v-if="list.length > 0" type="primary" underline="never" @click="readAll">
|
||||
<span class="text-xs">全部已读</span>
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex-center h-150px w-350px">
|
||||
<el-empty :image-size="50" description="暂无消息" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="detail?.title ?? '通知详情'"
|
||||
width="800px"
|
||||
custom-class="notification-detail"
|
||||
>
|
||||
<div v-if="detail" class="p-x-20px">
|
||||
<div class="flex-y-center mb-16px text-13px text-color-secondary">
|
||||
<span class="flex-y-center">
|
||||
<el-icon><User /></el-icon>
|
||||
{{ detail.publisherName }}
|
||||
</span>
|
||||
<span class="ml-2 flex-y-center">
|
||||
<el-icon><Timer /></el-icon>
|
||||
{{ detail.publishTime }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="max-h-60vh pt-16px mb-24px overflow-y-auto border-t border-solid border-color">
|
||||
<div v-html="detail.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNotice } from "./useNotice";
|
||||
|
||||
const { list, detail, dialogVisible, read, readAll, goMore } = useNotice();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
114
src/components/NoticeDropdown/useNotice.ts
Normal file
114
src/components/NoticeDropdown/useNotice.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 通知中心逻辑
|
||||
*/
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import type { NoticePageVo, NoticeDetailVo, NoticePageQuery } from "@/types/api";
|
||||
import NoticeAPI from "@/api/system/notice";
|
||||
import { useStomp } from "@/composables";
|
||||
import router from "@/router";
|
||||
|
||||
const PAGE_SIZE = 5;
|
||||
|
||||
export function useNotice() {
|
||||
const { subscribe, unsubscribe, isConnected } = useStomp();
|
||||
|
||||
// 状态
|
||||
const list = ref<NoticePageVo[]>([]);
|
||||
const detail = ref<NoticeDetailVo | null>(null);
|
||||
const dialogVisible = ref(false);
|
||||
|
||||
let subscribed = false;
|
||||
|
||||
// ============================================
|
||||
// 数据获取
|
||||
// ============================================
|
||||
|
||||
async function fetchList(params?: Partial<NoticePageQuery>) {
|
||||
const query: NoticePageQuery = {
|
||||
pageNum: 1,
|
||||
pageSize: PAGE_SIZE,
|
||||
isRead: 0,
|
||||
...params,
|
||||
} as NoticePageQuery;
|
||||
const page = await NoticeAPI.getMyNoticePage(query);
|
||||
list.value = page.list || [];
|
||||
}
|
||||
|
||||
async function read(id: string) {
|
||||
detail.value = await NoticeAPI.getDetail(id);
|
||||
dialogVisible.value = true;
|
||||
|
||||
// 从列表中移除已读项
|
||||
const idx = list.value.findIndex((item: NoticePageVo) => item.id === id);
|
||||
if (idx >= 0) list.value.splice(idx, 1);
|
||||
}
|
||||
|
||||
async function readAll() {
|
||||
await NoticeAPI.readAll();
|
||||
list.value = [];
|
||||
}
|
||||
|
||||
function goMore() {
|
||||
router.push({ name: "MyNotice" });
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// WebSocket 订阅
|
||||
// ============================================
|
||||
|
||||
function setupSubscription() {
|
||||
if (subscribed || !isConnected.value) return;
|
||||
|
||||
subscribe("/user/queue/message", (message: any) => {
|
||||
try {
|
||||
const data = JSON.parse(message.body || "{}");
|
||||
if (!data.id) return;
|
||||
|
||||
// 避免重复
|
||||
if (list.value.some((item: NoticePageVo) => item.id === data.id)) return;
|
||||
|
||||
list.value.unshift({
|
||||
id: data.id,
|
||||
title: data.title,
|
||||
type: data.type,
|
||||
publishTime: data.publishTime,
|
||||
} as NoticePageVo);
|
||||
|
||||
ElNotification({
|
||||
title: "您收到一条新的通知消息!",
|
||||
message: data.title,
|
||||
type: "success",
|
||||
position: "bottom-right",
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("解析通知消息失败", e);
|
||||
}
|
||||
});
|
||||
|
||||
subscribed = true;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 生命周期
|
||||
// ============================================
|
||||
|
||||
onMounted(() => {
|
||||
fetchList();
|
||||
setupSubscription();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
unsubscribe("/user/queue/message");
|
||||
subscribed = false;
|
||||
});
|
||||
|
||||
return {
|
||||
list,
|
||||
detail,
|
||||
dialogVisible,
|
||||
fetchList,
|
||||
read,
|
||||
readAll,
|
||||
goMore,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user