feat(system): 添加公告通知已读记录功能
- 新增 SysNoticeRead 实体类用于存储公告/通知已读记录 - 实现 SysNoticeReadMapper 数据访问层接口及 XML 映射文件 - 创建 ISysNoticeReadService 服务接口及实现类 - 添加数据库表 sys_notice_read 存储用户阅读状态 - 添加发布状态字段到公告表支持公告发布控制 - 实现前端 NoticePanel 组件支持未读标记和阅读状态显示 - 提供标记已读、批量标记、未读数量统计等功能 - 优化公告列表按已读状态和时间排序显示
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
package com.core.system.domain;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.BaseEntity;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公告/通知已读记录 sys_notice_read
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
*/
|
||||||
|
public class SysNoticeRead extends BaseEntity {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 阅读ID */
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long readId;
|
||||||
|
|
||||||
|
/** 公告/通知ID */
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long noticeId;
|
||||||
|
|
||||||
|
/** 用户ID */
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/** 阅读时间 */
|
||||||
|
private String readTime;
|
||||||
|
|
||||||
|
public Long getReadId() {
|
||||||
|
return readId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadId(Long readId) {
|
||||||
|
this.readId = readId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getNoticeId() {
|
||||||
|
return noticeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNoticeId(Long noticeId) {
|
||||||
|
this.noticeId = noticeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(Long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReadTime() {
|
||||||
|
return readTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadTime(String readTime) {
|
||||||
|
this.readTime = readTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.core.system.mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.core.system.domain.SysNoticeRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公告/通知已读记录 Mapper接口
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
*/
|
||||||
|
public interface SysNoticeReadMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询公告/通知已读记录
|
||||||
|
*
|
||||||
|
* @param readId 阅读ID
|
||||||
|
* @return 公告/通知已读记录
|
||||||
|
*/
|
||||||
|
public SysNoticeRead selectNoticeReadById(Long readId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户的已读公告/通知ID列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 已读公告/通知ID列表
|
||||||
|
*/
|
||||||
|
public List<Long> selectReadNoticeIdsByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询公告/通知的已读用户数量
|
||||||
|
*
|
||||||
|
* @param noticeId 公告/通知ID
|
||||||
|
* @return 已读用户数量
|
||||||
|
*/
|
||||||
|
public int countReadByNoticeId(Long noticeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增公告/通知已读记录
|
||||||
|
*
|
||||||
|
* @param noticeRead 公告/通知已读记录
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int insertNoticeRead(SysNoticeRead noticeRead);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除公告/通知已读记录
|
||||||
|
*
|
||||||
|
* @param readId 阅读ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int deleteNoticeReadById(Long readId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除公告/通知已读记录
|
||||||
|
*
|
||||||
|
* @param readIds 需要删除的阅读ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int deleteNoticeReadByIds(Long[] readIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查用户是否已阅读公告/通知
|
||||||
|
*
|
||||||
|
* @param noticeId 公告/通知ID
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 是否已阅读
|
||||||
|
*/
|
||||||
|
public boolean checkNoticeRead(Long noticeId, Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.core.system.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.AjaxResult;
|
||||||
|
import com.core.system.domain.SysNotice;
|
||||||
|
import com.core.system.domain.SysNoticeRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公告/通知已读记录 服务层
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
*/
|
||||||
|
public interface ISysNoticeReadService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户的未读公告/通知数量
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 未读数量
|
||||||
|
*/
|
||||||
|
public int getUnreadCount(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记公告/通知为已读
|
||||||
|
*
|
||||||
|
* @param noticeId 公告/通知ID
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public AjaxResult markAsRead(Long noticeId, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量标记公告/通知为已读
|
||||||
|
*
|
||||||
|
* @param noticeIds 公告/通知ID列表
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public AjaxResult markAllAsRead(Long[] noticeIds, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户的已读公告/通知ID列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 已读公告/通知ID列表
|
||||||
|
*/
|
||||||
|
public List<Long> selectReadNoticeIdsByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询带已读状态的公告列表
|
||||||
|
*
|
||||||
|
* @param notice 公告信息
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 公告集合
|
||||||
|
*/
|
||||||
|
public List<SysNotice> selectNoticeListWithReadStatus(SysNotice notice, Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.core.system.service.impl;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.AjaxResult;
|
||||||
|
import com.core.system.domain.SysNotice;
|
||||||
|
import com.core.system.domain.SysNoticeRead;
|
||||||
|
import com.core.system.mapper.SysNoticeMapper;
|
||||||
|
import com.core.system.mapper.SysNoticeReadMapper;
|
||||||
|
import com.core.system.service.ISysNoticeReadService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公告/通知已读记录 服务层实现
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class SysNoticeReadServiceImpl implements ISysNoticeReadService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysNoticeReadMapper noticeReadMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysNoticeMapper noticeMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户的未读公告/通知数量
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 未读数量
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getUnreadCount(Long userId) {
|
||||||
|
// 查询所有状态为正常(0)的公告/通知
|
||||||
|
SysNotice notice = new SysNotice();
|
||||||
|
notice.setStatus("0");
|
||||||
|
List<SysNotice> allNotices = noticeMapper.selectNoticeList(notice);
|
||||||
|
|
||||||
|
// 查询用户已读的公告/通知ID
|
||||||
|
List<Long> readNoticeIds = noticeReadMapper.selectReadNoticeIdsByUserId(userId);
|
||||||
|
|
||||||
|
// 计算未读数量
|
||||||
|
int unreadCount = 0;
|
||||||
|
for (SysNotice n : allNotices) {
|
||||||
|
if (!readNoticeIds.contains(n.getNoticeId())) {
|
||||||
|
unreadCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unreadCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记公告/通知为已读
|
||||||
|
*
|
||||||
|
* @param noticeId 公告/通知ID
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AjaxResult markAsRead(Long noticeId, Long userId) {
|
||||||
|
// 检查是否已读
|
||||||
|
boolean isRead = noticeReadMapper.checkNoticeRead(noticeId, userId);
|
||||||
|
if (isRead) {
|
||||||
|
return AjaxResult.success("已标记为已读");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入已读记录
|
||||||
|
SysNoticeRead noticeRead = new SysNoticeRead();
|
||||||
|
noticeRead.setNoticeId(noticeId);
|
||||||
|
noticeRead.setUserId(userId);
|
||||||
|
|
||||||
|
int result = noticeReadMapper.insertNoticeRead(noticeRead);
|
||||||
|
if (result > 0) {
|
||||||
|
return AjaxResult.success("标记成功");
|
||||||
|
}
|
||||||
|
return AjaxResult.error("标记失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量标记公告/通知为已读
|
||||||
|
*
|
||||||
|
* @param noticeIds 公告/通知ID列表
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AjaxResult markAllAsRead(Long[] noticeIds, Long userId) {
|
||||||
|
int successCount = 0;
|
||||||
|
for (Long noticeId : noticeIds) {
|
||||||
|
boolean isRead = noticeReadMapper.checkNoticeRead(noticeId, userId);
|
||||||
|
if (!isRead) {
|
||||||
|
SysNoticeRead noticeRead = new SysNoticeRead();
|
||||||
|
noticeRead.setNoticeId(noticeId);
|
||||||
|
noticeRead.setUserId(userId);
|
||||||
|
noticeReadMapper.insertNoticeRead(noticeRead);
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AjaxResult.success("成功标记" + successCount + "条记录为已读");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户的已读公告/通知ID列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 已读公告/通知ID列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Long> selectReadNoticeIdsByUserId(Long userId) {
|
||||||
|
return noticeReadMapper.selectReadNoticeIdsByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询带已读状态的公告列表
|
||||||
|
*
|
||||||
|
* @param notice 公告信息
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 公告集合
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<SysNotice> selectNoticeListWithReadStatus(SysNotice notice, Long userId) {
|
||||||
|
// 这里可以扩展为在查询结果中添加已读状态标记
|
||||||
|
// 暂时返回普通列表
|
||||||
|
return noticeMapper.selectNoticeList(notice);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.core.system.mapper.SysNoticeReadMapper">
|
||||||
|
|
||||||
|
<resultMap type="SysNoticeRead" id="SysNoticeReadResult">
|
||||||
|
<result property="readId" column="read_id"/>
|
||||||
|
<result property="noticeId" column="notice_id"/>
|
||||||
|
<result property="userId" column="user_id"/>
|
||||||
|
<result property="readTime" column="read_time"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="selectNoticeReadById" parameterType="Long" resultMap="SysNoticeReadResult">
|
||||||
|
select read_id, notice_id, user_id, read_time
|
||||||
|
from sys_notice_read
|
||||||
|
where read_id = #{readId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectReadNoticeIdsByUserId" parameterType="Long" resultType="Long">
|
||||||
|
select notice_id
|
||||||
|
from sys_notice_read
|
||||||
|
where user_id = #{userId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countReadByNoticeId" parameterType="Long" resultType="int">
|
||||||
|
select count(1)
|
||||||
|
from sys_notice_read
|
||||||
|
where notice_id = #{noticeId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="checkNoticeRead" resultType="boolean">
|
||||||
|
select count(1) > 0
|
||||||
|
from sys_notice_read
|
||||||
|
where notice_id = #{noticeId} and user_id = #{userId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertNoticeRead" parameterType="SysNoticeRead">
|
||||||
|
insert into sys_notice_read (
|
||||||
|
read_id,
|
||||||
|
notice_id,
|
||||||
|
user_id,
|
||||||
|
read_time
|
||||||
|
) values (
|
||||||
|
(SELECT COALESCE(MAX(read_id), 0) + 1 FROM sys_notice_read),
|
||||||
|
#{noticeId},
|
||||||
|
#{userId},
|
||||||
|
now()
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<delete id="deleteNoticeReadById" parameterType="Long">
|
||||||
|
delete from sys_notice_read where read_id = #{readId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<delete id="deleteNoticeReadByIds" parameterType="Long">
|
||||||
|
delete from sys_notice_read where read_id in
|
||||||
|
<foreach item="readId" collection="array" open="(" separator="," close=")">
|
||||||
|
#{readId}
|
||||||
|
</foreach>
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
249
openhis-ui-vue3/src/components/NoticePanel.vue
Normal file
249
openhis-ui-vue3/src/components/NoticePanel.vue
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer v-model="noticeVisible" title="公告" direction="rtl" size="400px" destroy-on-close>
|
||||||
|
<el-tabs v-model="activeTab">
|
||||||
|
<el-tab-pane label="公告" name="notice">
|
||||||
|
<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) }" @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">
|
||||||
|
<dict-tag :options="sys_notice_type" :value="item.noticeType" />
|
||||||
|
</span>
|
||||||
|
<span class="notice-time">{{ parseTime(item.createTime, '{y}-{m}-{d}') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="通知" name="notification">
|
||||||
|
<el-empty v-if="notificationList.length === 0" description="暂无通知" />
|
||||||
|
<div v-else class="notice-list">
|
||||||
|
<div v-for="item in notificationList" :key="item.noticeId" class="notice-item" :class="{ 'is-read': 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">
|
||||||
|
<dict-tag :options="sys_notice_type" :value="item.noticeType" />
|
||||||
|
</span>
|
||||||
|
<span class="notice-time">{{ parseTime(item.createTime, '{y}-{m}-{d}') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
<!-- 公告/通知详情对话框 -->
|
||||||
|
<el-dialog v-model="detailVisible" :title="currentNotice.noticeTitle" width="800px" append-to-body>
|
||||||
|
<div class="notice-detail">
|
||||||
|
<div class="detail-header">
|
||||||
|
<span class="detail-type">
|
||||||
|
<dict-tag :options="sys_notice_type" :value="currentNotice.noticeType" />
|
||||||
|
</span>
|
||||||
|
<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 { getPublicNoticeList, getUserNotices, markAsRead, getReadNoticeIds } from '@/api/system/notice'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance()
|
||||||
|
const { sys_notice_type } = proxy.useDict('sys_notice_type')
|
||||||
|
const emit = defineEmits(['updateUnreadCount'])
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const noticeVisible = ref(false)
|
||||||
|
const detailVisible = ref(false)
|
||||||
|
const activeTab = ref('notice')
|
||||||
|
const noticeList = ref([])
|
||||||
|
const notificationList = 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同类型按创建时间倒序(最新的在前)
|
||||||
|
return new Date(b.createTime) - new Date(a.createTime)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载公告和通知
|
||||||
|
function loadNotices() {
|
||||||
|
// 加载公告列表
|
||||||
|
getPublicNoticeList({ pageNum: 1, pageSize: 10 }).then(response => {
|
||||||
|
let list = response.rows || response.data || []
|
||||||
|
noticeList.value = sortNoticeList(list)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载通知列表
|
||||||
|
getUserNotices().then(response => {
|
||||||
|
let list = response.data || []
|
||||||
|
notificationList.value = sortNoticeList(list)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid #EBEEF5;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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>
|
||||||
BIN
发版记录/2025-12-24/~$ Microsoft Word 文档.docx
Normal file
BIN
发版记录/2025-12-24/~$ Microsoft Word 文档.docx
Normal file
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
-- 添加公告/通知发布状态字段
|
||||||
|
ALTER TABLE sys_notice ADD COLUMN publish_status VARCHAR(1) DEFAULT '0';
|
||||||
|
|
||||||
|
-- 添加字段注释
|
||||||
|
COMMENT ON COLUMN sys_notice.publish_status IS '发布状态(0未发布 1已发布)';
|
||||||
|
|
||||||
|
-- 更新现有数据为已发布状态
|
||||||
|
UPDATE sys_notice SET publish_status = '1' WHERE publish_status IS NULL;
|
||||||
26
迁移记录-DB变更记录/2025-12-30 add_table_sys_notice_read.sql
Normal file
26
迁移记录-DB变更记录/2025-12-30 add_table_sys_notice_read.sql
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
-- 公告/通知已读记录表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_notice_read (
|
||||||
|
read_id BIGINT PRIMARY KEY,
|
||||||
|
notice_id BIGINT NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
read_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT uk_notice_user UNIQUE (notice_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE sys_notice_read IS '公告/通知已读记录表';
|
||||||
|
COMMENT ON COLUMN sys_notice_read.read_id IS '阅读ID';
|
||||||
|
COMMENT ON COLUMN sys_notice_read.notice_id IS '公告/通知ID';
|
||||||
|
COMMENT ON COLUMN sys_notice_read.user_id IS '用户ID';
|
||||||
|
COMMENT ON COLUMN sys_notice_read.read_time IS '阅读时间';
|
||||||
|
|
||||||
|
-- 创建序列
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS sys_notice_read_read_id_seq
|
||||||
|
INCREMENT 1
|
||||||
|
MINVALUE 1
|
||||||
|
MAXVALUE 99999999
|
||||||
|
START 200
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
-- 索引
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_notice_read_notice_id ON sys_notice_read(notice_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_notice_read_user_id ON sys_notice_read(user_id);
|
||||||
Reference in New Issue
Block a user