Files
his/openhis-ui-vue3/src/components/NoticePanel.vue
chenqi 4d4828ea71 feat(login): 添加租户名称获取功能并优化前端布局
- 在登录控制器中注入租户服务并获取租户名称信息
- 添加租户名称到登录响应结果中
- 更新样式变量定义侧边栏宽度和Logo高度
- 重构公告面板组件统一公告通知显示逻辑
- 简化公告类型图标和样式映射关系
- 更新侧边栏为垂直菜单布局并添加折叠功能
- 优化Logo组件显示租户名称和系统标题
- 调整导航栏布局结构和响应式样式
- 重构主应用容器样式和标签页显示逻辑
2025-12-31 10:28:52 +08:00

290 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-drawer v-model="noticeVisible" title="公告/通知" direction="rtl" size="400px" destroy-on-close>
<el-empty v-if="noticeList.length === 0" description="暂无公告/通知" />
<div v-else class="notice-list">
<div v-for="item in noticeList" :key="item.noticeId" class="notice-item" :class="{ 'is-read': isRead(item.noticeId), 'unread': !isRead(item.noticeId) }" @click="viewDetail(item)">
<div class="notice-title">
<span v-if="!isRead(item.noticeId)" class="unread-dot"></span>
{{ item.noticeTitle }}
</div>
<div class="notice-info">
<span class="notice-type">
<el-tag :type="getNoticeTypeTagType(item.noticeType)" size="small">
{{ getNoticeTypeText(item.noticeType) }}
</el-tag>
</span>
<span class="notice-priority" v-if="item.priority">
<el-tag :type="getPriorityTagType(item.priority)" size="small" effect="plain">
{{ getPriorityText(item.priority) }}
</el-tag>
</span>
<span class="notice-time">{{ parseTime(item.createTime, '{y}-{m}-{d}') }}</span>
</div>
</div>
</div>
<!-- 公告/通知详情对话框 -->
<el-dialog v-model="detailVisible" :title="currentNotice.noticeTitle" width="800px" append-to-body>
<div class="notice-detail">
<div class="detail-header">
<div class="detail-type">
<el-tag :type="getNoticeTypeTagType(currentNotice.noticeType)" size="small">
{{ getNoticeTypeText(currentNotice.noticeType) }}
</el-tag>
<el-tag :type="getPriorityTagType(currentNotice.priority)" size="small" effect="plain" style="margin-left: 8px;">
{{ getPriorityText(currentNotice.priority) }}
</el-tag>
</div>
<span class="detail-time">{{ parseTime(currentNotice.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</div>
<div class="detail-content" v-html="currentNotice.noticeContent"></div>
</div>
<template #footer>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</el-dialog>
</el-drawer>
</template>
<script setup>
import {ref} from 'vue'
import {getReadNoticeIds, getUserNotices, markAsRead} from '@/api/system/notice'
const emit = defineEmits(['updateUnreadCount'])
const noticeVisible = ref(false)
const detailVisible = ref(false)
const noticeList = ref([])
const currentNotice = ref({})
const readNoticeIds = ref(new Set())
// 打开公告/通知面板
function open() {
noticeVisible.value = true
loadNotices()
loadReadNoticeIds()
}
// 加载已读公告ID列表
function loadReadNoticeIds() {
getReadNoticeIds().then(response => {
const ids = response.data || []
readNoticeIds.value = new Set(ids)
// 同步到 localStorage
localStorage.setItem('readNoticeIds', JSON.stringify(ids))
}).catch(() => {
// 接口调用失败时从 localStorage 读取
const readIds = localStorage.getItem('readNoticeIds')
if (readIds) {
try {
const ids = JSON.parse(readIds)
readNoticeIds.value = new Set(ids)
} catch (e) {
console.error('解析已读ID失败', e)
}
}
})
}
// 排序:未读的排前面,已读的排后面,同状态按优先级和时间排序
function sortNoticeList(list) {
return list.sort((a, b) => {
const aRead = isRead(a.noticeId)
const bRead = isRead(b.noticeId)
// 未读排在前面
if (aRead !== bRead) {
return aRead ? 1 : -1
}
// 同状态按优先级排序1高 2中 3低
const priorityA = a.priority || '3'
const priorityB = b.priority || '3'
if (priorityA !== priorityB) {
return priorityA.localeCompare(priorityB)
}
// 同优先级按创建时间倒序(最新的在前)
return new Date(b.createTime) - new Date(a.createTime)
})
}
// 加载公告和通知(统一从一个接口获取)
function loadNotices() {
getUserNotices().then(response => {
let list = response.data || []
noticeList.value = sortNoticeList(list)
})
}
// 获取公告类型标签类型
// noticeType: 1=通知, 2=公告
function getNoticeTypeTagType(type) {
const typeMap = {
'1': 'primary', // 通知
'2': 'success' // 公告
}
return typeMap[type] || 'info'
}
// 获取公告类型文本
function getNoticeTypeText(type) {
const textMap = {
'1': '通知',
'2': '公告'
}
return textMap[type] || '公告'
}
// 获取优先级标签类型
// priority: 1=高, 2=中, 3=低
function getPriorityTagType(priority) {
const typeMap = {
'1': 'danger', // 高优先级 - 红色
'2': 'warning', // 中优先级 - 橙色
'3': 'info' // 低优先级 - 灰色
}
return typeMap[priority] || 'info'
}
// 获取优先级文本
function getPriorityText(priority) {
const textMap = {
'1': '高',
'2': '中',
'3': '低'
}
return textMap[priority] || '中'
}
// 查看详情
function viewDetail(item) {
currentNotice.value = item
detailVisible.value = true
// 标记为已读
if (!readNoticeIds.value.has(item.noticeId)) {
markAsRead(item.noticeId).then(() => {
readNoticeIds.value.add(item.noticeId)
// 保存到 localStorage
saveReadNoticeIds()
emit('updateUnreadCount')
})
}
}
// 保存已读公告ID列表
function saveReadNoticeIds() {
const ids = Array.from(readNoticeIds.value)
localStorage.setItem('readNoticeIds', JSON.stringify(ids))
}
// 检查是否已读
function isRead(noticeId) {
return readNoticeIds.value.has(noticeId)
}
// 暴露方法给父组件
defineExpose({
open,
isRead,
readNoticeIds
})
</script>
<style lang="scss" scoped>
.notice-list {
max-height: calc(100vh - 200px);
overflow-y: auto;
}
.notice-item {
padding: 12px 0;
border-bottom: 1px solid #EBEEF5;
cursor: pointer;
transition: background-color 0.3s;
&:hover {
background-color: #F5F7FA;
}
&:last-child {
border-bottom: none;
}
&.is-read {
.notice-title {
color: #909399;
}
}
&.unread {
background-color: #fffbe6;
}
}
.notice-title {
font-size: 14px;
color: #303133;
margin-bottom: 8px;
line-height: 1.4;
display: flex;
align-items: center;
.unread-dot {
display: inline-block;
width: 6px;
height: 6px;
background-color: #f56c6c;
border-radius: 50%;
margin-right: 8px;
flex-shrink: 0;
}
}
.notice-info {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #909399;
gap: 8px;
.notice-type,
.notice-priority {
flex-shrink: 0;
}
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 16px;
border-bottom: 1px solid #EBEEF5;
margin-bottom: 16px;
.detail-type {
display: flex;
align-items: center;
}
}
.detail-time {
font-size: 12px;
color: #909399;
}
.detail-content {
font-size: 14px;
line-height: 1.8;
color: #303133;
max-height: 500px;
overflow-y: auto;
}
:deep(.el-drawer__body) {
padding: 0 20px;
}
</style>