7 files added
6 files modified
| | |
| | | import org.springframework.web.bind.annotation.PutMapping;
|
| | | import org.springframework.web.bind.annotation.RequestBody;
|
| | | import org.springframework.web.bind.annotation.RequestMapping;
|
| | | import org.springframework.web.bind.annotation.ResponseBody;
|
| | | import org.springframework.web.bind.annotation.RestController;
|
| | | import com.ruoyi.common.core.text.Convert;
|
| | | import com.ruoyi.common.core.web.controller.BaseController;
|
| | | import com.ruoyi.common.core.web.domain.AjaxResult;
|
| | | import com.ruoyi.common.core.web.page.TableDataInfo;
|
| | |
| | | import com.ruoyi.common.security.annotation.RequiresPermissions;
|
| | | import com.ruoyi.common.security.utils.SecurityUtils;
|
| | | import com.ruoyi.system.domain.SysNotice;
|
| | | import com.ruoyi.system.service.ISysNoticeReadService;
|
| | | import com.ruoyi.system.service.ISysNoticeService;
|
| | |
|
| | | /**
|
| | |
| | | {
|
| | | @Autowired
|
| | | private ISysNoticeService noticeService;
|
| | |
|
| | | @Autowired
|
| | | private ISysNoticeReadService noticeReadService;
|
| | |
|
| | | /**
|
| | | * 获取通知公告列表
|
| | |
| | | }
|
| | |
|
| | | /**
|
| | | * 首页顶部公告列表(返回全部正常公告,带当前用户已读标记,最多5条)
|
| | | */
|
| | | @GetMapping("/listTop")
|
| | | @ResponseBody
|
| | | public AjaxResult listTop()
|
| | | {
|
| | | Long userId = SecurityUtils.getUserId();
|
| | | List<SysNotice> list = noticeReadService.selectNoticeListWithReadStatus(userId, 5);
|
| | | long unreadCount = list.stream().filter(n -> !n.getIsRead()).count();
|
| | | AjaxResult result = AjaxResult.success(list);
|
| | | result.put("unreadCount", unreadCount);
|
| | | return result;
|
| | | }
|
| | |
|
| | | /**
|
| | | * 标记公告已读
|
| | | */
|
| | | @PostMapping("/markRead")
|
| | | @ResponseBody
|
| | | public AjaxResult markRead(Long noticeId)
|
| | | {
|
| | | Long userId = SecurityUtils.getUserId();
|
| | | noticeReadService.markRead(noticeId, userId);
|
| | | return success();
|
| | | }
|
| | |
|
| | | /**
|
| | | * 批量标记已读
|
| | | */
|
| | | @PostMapping("/markReadAll")
|
| | | @ResponseBody
|
| | | public AjaxResult markReadAll(String ids)
|
| | | {
|
| | | Long userId = SecurityUtils.getUserId();
|
| | | Long[] noticeIds = Convert.toLongArray(ids);
|
| | | noticeReadService.markReadBatch(userId, noticeIds);
|
| | | return success();
|
| | | }
|
| | |
|
| | | /**
|
| | | * 删除通知公告
|
| | | */
|
| | | @RequiresPermissions("system:notice:remove")
|
| | |
| | | @DeleteMapping("/{noticeIds}")
|
| | | public AjaxResult remove(@PathVariable Long[] noticeIds)
|
| | | {
|
| | | noticeReadService.deleteByNoticeIds(noticeIds);
|
| | | return toAjax(noticeService.deleteNoticeByIds(noticeIds));
|
| | | }
|
| | | }
|
| | |
| | | import jakarta.validation.constraints.Size;
|
| | | import org.apache.commons.lang3.builder.ToStringBuilder;
|
| | | import org.apache.commons.lang3.builder.ToStringStyle;
|
| | | import com.fasterxml.jackson.annotation.JsonProperty;
|
| | | import com.ruoyi.common.core.web.domain.BaseEntity;
|
| | | import com.ruoyi.common.core.xss.Xss;
|
| | |
|
| | |
| | |
|
| | | /** 公告状态(0正常 1关闭) */
|
| | | private String status;
|
| | |
|
| | | /** 是否已读 */
|
| | | @JsonProperty("isRead")
|
| | | private boolean isRead;
|
| | |
|
| | | public Long getNoticeId()
|
| | | {
|
| | |
| | | return status;
|
| | | }
|
| | |
|
| | | public boolean getIsRead()
|
| | | {
|
| | | return isRead;
|
| | | }
|
| | |
|
| | | public void setIsRead(boolean isRead)
|
| | | {
|
| | | this.isRead = isRead;
|
| | | }
|
| | |
|
| | | @Override
|
| | | public String toString() {
|
| | | return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
| New file |
| | |
| | | package com.ruoyi.system.domain; |
| | | |
| | | import java.util.Date; |
| | | import org.apache.commons.lang3.builder.ToStringBuilder; |
| | | import org.apache.commons.lang3.builder.ToStringStyle; |
| | | |
| | | /** |
| | | * 公告已读记录表 sys_notice_read |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | public class SysNoticeRead |
| | | { |
| | | /** 主键 */ |
| | | private Long readId; |
| | | |
| | | /** 公告ID */ |
| | | private Long noticeId; |
| | | |
| | | /** 用户ID */ |
| | | private Long userId; |
| | | |
| | | /** 阅读时间 */ |
| | | private Date 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 Date getReadTime() |
| | | { |
| | | return readTime; |
| | | } |
| | | |
| | | public void setReadTime(Date readTime) |
| | | { |
| | | this.readTime = readTime; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() |
| | | { |
| | | return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) |
| | | .append("readId", getReadId()) |
| | | .append("noticeId", getNoticeId()) |
| | | .append("userId", getUserId()) |
| | | .append("readTime", getReadTime()) |
| | | .toString(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.ruoyi.system.mapper; |
| | | |
| | | import java.util.List; |
| | | import org.apache.ibatis.annotations.Param; |
| | | import com.ruoyi.system.domain.SysNoticeRead; |
| | | import com.ruoyi.system.domain.SysNotice; |
| | | |
| | | /** |
| | | * 公告已读记录 数据层 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | public interface SysNoticeReadMapper |
| | | { |
| | | /** |
| | | * 新增已读记录(忽略重复) |
| | | * |
| | | * @param noticeRead 已读记录 |
| | | * @return 结果 |
| | | */ |
| | | public int insertNoticeRead(SysNoticeRead noticeRead); |
| | | |
| | | /** |
| | | * 查询某用户未读公告数量 |
| | | * |
| | | * @param userId 用户ID |
| | | * @return 未读数量 |
| | | */ |
| | | public int selectUnreadCount(@Param("userId") Long userId); |
| | | |
| | | /** |
| | | * 查询某用户是否已读某公告 |
| | | * |
| | | * @param noticeId 公告ID |
| | | * @param userId 用户ID |
| | | * @return 已读记录数(0未读 1已读) |
| | | */ |
| | | public int selectIsRead(@Param("noticeId") Long noticeId, @Param("userId") Long userId); |
| | | |
| | | /** |
| | | * 批量标记已读 |
| | | * |
| | | * @param userId 用户ID |
| | | * @param noticeIds 公告ID数组 |
| | | * @return 结果 |
| | | */ |
| | | public int insertNoticeReadBatch(@Param("userId") Long userId, @Param("noticeIds") Long[] noticeIds); |
| | | |
| | | /** |
| | | * 查询带已读状态的公告列表(SQL层限制条数,一次查询完成) |
| | | * |
| | | * @param userId 用户ID |
| | | * @param limit 最多返回条数 |
| | | * @return 带 isRead 标记的公告列表 |
| | | */ |
| | | public List<SysNotice> selectNoticeListWithReadStatus(@Param("userId") Long userId, @Param("limit") int limit); |
| | | |
| | | /** |
| | | * 公告删除时清理对应已读记录 |
| | | * |
| | | * @param noticeIds 公告ID数组 |
| | | * @return 结果 |
| | | */ |
| | | public int deleteByNoticeIds(@Param("noticeIds") Long[] noticeIds); |
| | | } |
| New file |
| | |
| | | package com.ruoyi.system.service; |
| | | |
| | | import java.util.List; |
| | | import com.ruoyi.system.domain.SysNotice; |
| | | |
| | | /** |
| | | * 公告已读记录 服务层 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | public interface ISysNoticeReadService |
| | | { |
| | | /** |
| | | * 标记已读(幂等,重复调用不报错) |
| | | * |
| | | * @param noticeId 公告ID |
| | | * @param userId 用户ID |
| | | */ |
| | | public void markRead(Long noticeId, Long userId); |
| | | |
| | | /** |
| | | * 查询某用户未读公告数量 |
| | | * |
| | | * @param userId 用户ID |
| | | * @return 未读数量 |
| | | */ |
| | | public int selectUnreadCount(Long userId); |
| | | |
| | | /** |
| | | * 查询公告列表并标记当前用户已读状态(用于首页展示) |
| | | * |
| | | * @param userId 用户ID |
| | | * @param limit 最多返回条数 |
| | | * @return 带 isRead 标记的公告列表 |
| | | */ |
| | | public List<SysNotice> selectNoticeListWithReadStatus(Long userId, int limit); |
| | | |
| | | /** |
| | | * 批量标记已读 |
| | | * |
| | | * @param userId 用户ID |
| | | * @param noticeIds 公告ID数组 |
| | | */ |
| | | public void markReadBatch(Long userId, Long[] noticeIds); |
| | | |
| | | /** |
| | | * 删除公告时清理对应已读记录 |
| | | * |
| | | * @param noticeIds 公告ID数组 |
| | | */ |
| | | public void deleteByNoticeIds(Long[] noticeIds); |
| | | } |
| New file |
| | |
| | | package com.ruoyi.system.service.impl; |
| | | |
| | | import java.util.List; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import com.ruoyi.system.domain.SysNoticeRead; |
| | | import com.ruoyi.system.domain.SysNotice; |
| | | import com.ruoyi.system.mapper.SysNoticeReadMapper; |
| | | import com.ruoyi.system.service.ISysNoticeReadService; |
| | | |
| | | /** |
| | | * 公告已读记录 服务层实现 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | @Service |
| | | public class SysNoticeReadServiceImpl implements ISysNoticeReadService |
| | | { |
| | | @Autowired |
| | | private SysNoticeReadMapper noticeReadMapper; |
| | | |
| | | /** |
| | | * 标记已读 |
| | | */ |
| | | @Override |
| | | public void markRead(Long noticeId, Long userId) |
| | | { |
| | | SysNoticeRead record = new SysNoticeRead(); |
| | | record.setNoticeId(noticeId); |
| | | record.setUserId(userId); |
| | | noticeReadMapper.insertNoticeRead(record); |
| | | } |
| | | |
| | | /** |
| | | * 查询某用户未读公告数量 |
| | | */ |
| | | @Override |
| | | public int selectUnreadCount(Long userId) |
| | | { |
| | | return noticeReadMapper.selectUnreadCount(userId); |
| | | } |
| | | |
| | | /** |
| | | * 查询公告列表并标记当前用户已读状态 |
| | | */ |
| | | @Override |
| | | public List<SysNotice> selectNoticeListWithReadStatus(Long userId, int limit) |
| | | { |
| | | return noticeReadMapper.selectNoticeListWithReadStatus(userId, limit); |
| | | } |
| | | |
| | | /** |
| | | * 批量标记已读 |
| | | */ |
| | | @Override |
| | | public void markReadBatch(Long userId, Long[] noticeIds) |
| | | { |
| | | if (noticeIds == null || noticeIds.length == 0) |
| | | { |
| | | return; |
| | | } |
| | | noticeReadMapper.insertNoticeReadBatch(userId, noticeIds); |
| | | } |
| | | |
| | | /** |
| | | * 删除公告时清理对应已读记录 |
| | | */ |
| | | @Override |
| | | public void deleteByNoticeIds(Long[] noticeIds) |
| | | { |
| | | noticeReadMapper.deleteByNoticeIds(noticeIds); |
| | | } |
| | | } |
| | |
| | | AND create_by like concat('%', #{createBy}, '%')
|
| | | </if>
|
| | | </where>
|
| | | order by notice_id desc
|
| | | </select>
|
| | |
|
| | | <insert id="insertNotice" parameterType="SysNotice">
|
| New file |
| | |
| | | <?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.ruoyi.system.mapper.SysNoticeReadMapper"> |
| | | |
| | | <resultMap type="SysNoticeRead" id="SysNoticeReadResult"> |
| | | <id property="readId" column="read_id" /> |
| | | <result property="noticeId" column="notice_id" /> |
| | | <result property="userId" column="user_id" /> |
| | | <result property="readTime" column="read_time" /> |
| | | </resultMap> |
| | | |
| | | <!-- 新增已读记录 --> |
| | | <insert id="insertNoticeRead" parameterType="SysNoticeRead"> |
| | | insert ignore into sys_notice_read (notice_id, user_id, read_time) |
| | | values (#{noticeId}, #{userId}, sysdate()) |
| | | </insert> |
| | | |
| | | <!-- 查询未读数量:正常状态公告 减去 当前用户已读数 --> |
| | | <select id="selectUnreadCount" resultType="int"> |
| | | select count(*) from sys_notice n |
| | | where n.status = '0' and not exists (select 1 from sys_notice_read r where r.notice_id = n.notice_id and r.user_id = #{userId}) |
| | | </select> |
| | | |
| | | <!-- 查询是否已读 --> |
| | | <select id="selectIsRead" resultType="int"> |
| | | select count(*) from sys_notice_read where notice_id = #{noticeId} and user_id = #{userId} |
| | | </select> |
| | | |
| | | <!-- 查询带已读状态的公告列表(直接在SQL中限制条数) --> |
| | | <select id="selectNoticeListWithReadStatus" resultType="SysNotice"> |
| | | select |
| | | n.notice_id as noticeId, |
| | | n.notice_title as noticeTitle, |
| | | n.notice_type as noticeType, |
| | | n.status, |
| | | n.create_by as createBy, |
| | | n.create_time as createTime, |
| | | case when r.notice_id is not null then true else false end as isRead |
| | | from sys_notice n |
| | | left join sys_notice_read r |
| | | on r.notice_id = n.notice_id and r.user_id = #{userId} |
| | | where n.status = '0' |
| | | order by n.notice_id desc |
| | | limit #{limit} |
| | | </select> |
| | | |
| | | <!-- 批量标记已读 --> |
| | | <insert id="insertNoticeReadBatch"> |
| | | insert ignore into sys_notice_read (notice_id, user_id, read_time) |
| | | values |
| | | <foreach collection="noticeIds" item="noticeId" separator=","> |
| | | (#{noticeId}, #{userId}, sysdate()) |
| | | </foreach> |
| | | </insert> |
| | | |
| | | <!-- 删除公告时清理已读记录 --> |
| | | <delete id="deleteByNoticeIds"> |
| | | delete from sys_notice_read where notice_id in |
| | | <foreach collection="noticeIds" item="noticeId" open="(" separator="," close=")"> |
| | | #{noticeId} |
| | | </foreach> |
| | | </delete> |
| | | |
| | | </mapper> |
| | |
| | | url: '/system/notice/' + noticeId,
|
| | | method: 'delete'
|
| | | })
|
| | | } |
| | | }
|
| | |
|
| | | // 首页顶部公告列表(带已读状态)
|
| | | export function listNoticeTop() {
|
| | | return request({
|
| | | url: '/system/notice/listTop',
|
| | | method: 'get'
|
| | | })
|
| | | }
|
| | |
|
| | | // 标记公告已读
|
| | | export function markNoticeRead(noticeId) {
|
| | | return request({
|
| | | url: '/system/notice/markRead',
|
| | | method: 'post',
|
| | | params: { noticeId }
|
| | | })
|
| | | }
|
| | |
|
| | | // 批量标记已读
|
| | | export function markNoticeReadAll(ids) {
|
| | | return request({
|
| | | url: '/system/notice/markReadAll',
|
| | | method: 'post',
|
| | | params: { ids }
|
| | | })
|
| | | }
|
| New file |
| | |
| | | <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773923748724" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5930" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 212l48.8 12c101.6 24.8 176 117.6 176 220.8v254.4l18.4 18.4 24.8 25.6h-536l24.8-25.6 18.4-18.4V444.8c0-103.2 73.6-196.8 176-220.8l48.8-12M512 64c-36.8 0-64 30.4-64 68v30.4C320.8 192 223.2 307.2 223.2 444.8v228.8L136 763.2v44.8h752v-44.8l-87.2-89.6V444.8c0-137.6-97.6-252.8-224.8-283.2v-28.8c0-32-17.6-60.8-48-67.2-5.6-1.6-11.2-1.6-16-1.6z m88 808H424c0 49.6 38.4 88 88 88s88-38.4 88-88z" p-id="5931"></path></svg> |
| New file |
| | |
| | | <template> |
| | | <div> |
| | | <el-popover ref="noticePopover" placement="bottom-end" width="320" trigger="manual" :value="noticeVisible" popper-class="notice-popover"> |
| | | <div class="notice-header"> |
| | | <span class="notice-title">通知公告</span> |
| | | <span class="notice-mark-all" @click="markAllRead">全部已读</span> |
| | | </div> |
| | | <div v-if="noticeLoading" class="notice-loading"><i class="el-icon-loading"></i> 加载中...</div> |
| | | <div v-else-if="noticeList.length === 0" class="notice-empty"><i class="el-icon-inbox"></i><br>暂无公告</div> |
| | | <div v-else> |
| | | <div v-for="item in noticeList" :key="item.noticeId" class="notice-item" :class="{ 'is-read': item.isRead }" @click="previewNotice(item)"> |
| | | <el-tag size="mini" :type="item.noticeType === '1' ? 'warning' : 'success'" class="notice-tag"> |
| | | {{ item.noticeType === '1' ? '通知' : '公告' }} |
| | | </el-tag> |
| | | <span class="notice-item-title">{{ item.noticeTitle }}</span> |
| | | <span class="notice-item-date">{{ item.createTime }}</span> |
| | | </div> |
| | | </div> |
| | | </el-popover> |
| | | |
| | | <div v-popover:noticePopover class="right-menu-item hover-effect notice-trigger" @mouseenter="onNoticeEnter" @mouseleave="onNoticeLeave"> |
| | | <svg-icon icon-class="bell" /> |
| | | <span v-if="unreadCount > 0" class="notice-badge">{{ unreadCount }}</span> |
| | | </div> |
| | | |
| | | <el-dialog :title="previewTitle" :visible.sync="previewVisible" width="680px" append-to-body custom-class="notice-preview-dialog"> |
| | | <div class="notice-preview-meta"> |
| | | <el-tag size="small" :type="previewNoticeType === '1' ? 'warning' : 'success'"> |
| | | {{ previewNoticeType === '1' ? '通知' : '公告' }} |
| | | </el-tag> |
| | | <span class="notice-preview-info"><i class="el-icon-user"></i> {{ previewCreateBy }}</span> |
| | | <span class="notice-preview-info"><i class="el-icon-time"></i> {{ previewCreateTime }}</span> |
| | | </div> |
| | | <div class="notice-preview-divider"></div> |
| | | <div class="notice-preview-content" v-html="previewContent"></div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { listNoticeTop, markNoticeRead, markNoticeReadAll, getNotice } from '@/api/system/notice' |
| | | |
| | | export default { |
| | | name: 'HeaderNotice', |
| | | data() { |
| | | return { |
| | | noticeList: [], // 通知列表 |
| | | unreadCount: 0, // 未读数量 |
| | | noticeLoading: false, // 加载状态 |
| | | noticeVisible: false, // 弹出层显示状态 |
| | | noticeLeaveTimer: null, // 鼠标离开计时器 |
| | | previewVisible: false, // 预览弹窗显示状态 |
| | | previewTitle: '', // 预览弹窗标题 |
| | | previewContent: '', // 预览弹窗内容 |
| | | previewNoticeType: '', // 预览弹窗类型 |
| | | previewCreateBy: '', // 预览弹窗创建人 |
| | | previewCreateTime: '' // 预览弹窗创建时间 |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.loadNoticeTop() |
| | | }, |
| | | methods: { |
| | | // 鼠标移入铃铛区域 |
| | | onNoticeEnter() { |
| | | clearTimeout(this.noticeLeaveTimer) |
| | | this.noticeVisible = true |
| | | this.$nextTick(() => { |
| | | const popper = this.$refs.noticePopover.$refs.popper |
| | | if (popper && !popper._noticeBound) { |
| | | popper._noticeBound = true |
| | | popper.addEventListener('mouseenter', () => clearTimeout(this.noticeLeaveTimer)) |
| | | popper.addEventListener('mouseleave', () => { |
| | | this.noticeLeaveTimer = setTimeout(() => { this.noticeVisible = false }, 100) |
| | | }) |
| | | } |
| | | }) |
| | | }, |
| | | // 鼠标离开铃铛区域 |
| | | onNoticeLeave() { |
| | | this.noticeLeaveTimer = setTimeout(() => { this.noticeVisible = false }, 150) |
| | | }, |
| | | // 加载顶部公告列表 |
| | | loadNoticeTop() { |
| | | this.noticeLoading = true |
| | | listNoticeTop().then(res => { |
| | | this.noticeList = res.data || [] |
| | | this.unreadCount = res.unreadCount !== undefined ? res.unreadCount : this.noticeList.filter(n => !n.isRead).length |
| | | }).finally(() => { |
| | | this.noticeLoading = false |
| | | }) |
| | | }, |
| | | // 预览公告详情 |
| | | previewNotice(item) { |
| | | if (!item.isRead) { |
| | | markNoticeRead(item.noticeId).catch(() => {}) |
| | | item.isRead = true |
| | | const idx = this.noticeList.indexOf(item) |
| | | if (idx !== -1) this.$set(this.noticeList, idx, { ...item, isRead: true }) |
| | | this.unreadCount = Math.max(0, this.unreadCount - 1) |
| | | } |
| | | getNotice(item.noticeId).then(res => { |
| | | const notice = res.data |
| | | this.previewTitle = notice.noticeTitle |
| | | this.previewContent = notice.noticeContent |
| | | this.previewNoticeType = notice.noticeType |
| | | this.previewCreateBy = notice.createBy |
| | | this.previewCreateTime = notice.createTime |
| | | this.previewVisible = true |
| | | }) |
| | | }, |
| | | // 全部已读 |
| | | markAllRead() { |
| | | const ids = this.noticeList.map(n => n.noticeId).join(',') |
| | | if (!ids) return |
| | | markNoticeReadAll(ids).catch(() => {}) |
| | | this.noticeList = this.noticeList.map(n => ({ ...n, isRead: true })) |
| | | this.unreadCount = 0 |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .notice-trigger { |
| | | position: relative; |
| | | transform: translateX(-6px); |
| | | .svg-icon { width: 1.2em; height: 1.2em; vertical-align: -0.2em; } |
| | | .notice-badge { |
| | | position: absolute; |
| | | top: 7px; |
| | | right: -3px; |
| | | background: #f56c6c; |
| | | color: #fff; |
| | | border-radius: 10px; |
| | | font-size: 10px; |
| | | height: 16px; |
| | | line-height: 16px; |
| | | padding: 0 4px; |
| | | min-width: 16px; |
| | | text-align: center; |
| | | white-space: nowrap; |
| | | pointer-events: none; |
| | | } |
| | | } |
| | | .notice-popover { |
| | | padding: 0 !important; |
| | | } |
| | | .notice-popover .notice-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 10px 14px; |
| | | background: #f7f9fb; |
| | | border-bottom: 1px solid #eee; |
| | | font-size: 13px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .notice-popover .notice-mark-all { |
| | | font-size: 12px; |
| | | color: #409EFF; |
| | | font-weight: normal; |
| | | cursor: pointer; |
| | | } |
| | | .notice-popover .notice-mark-all:hover { color: #2b7cc1; } |
| | | .notice-popover .notice-loading, |
| | | .notice-popover .notice-empty { |
| | | padding: 24px; |
| | | text-align: center; |
| | | color: #bbb; |
| | | font-size: 12px; |
| | | line-height: 1.8; |
| | | } |
| | | .notice-popover .notice-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 10px 14px; |
| | | border-bottom: 1px solid #f5f5f5; |
| | | cursor: pointer; |
| | | transition: background 0.15s; |
| | | } |
| | | .notice-popover .notice-item:last-child { border-bottom: none; } |
| | | .notice-popover .notice-item:hover { background: #f7f9fb; } |
| | | .notice-popover .notice-item.is-read .notice-tag, |
| | | .notice-popover .notice-item.is-read .notice-item-title, |
| | | .notice-popover .notice-item.is-read .notice-item-date { opacity: 0.45; filter: grayscale(1); color: #999; } |
| | | .notice-popover .notice-tag { flex-shrink: 0; } |
| | | .notice-popover .notice-item-title { |
| | | flex: 1; |
| | | font-size: 12px; |
| | | color: #333; |
| | | overflow: hidden; |
| | | white-space: nowrap; |
| | | text-overflow: ellipsis; |
| | | } |
| | | .notice-popover .notice-item-date { |
| | | flex-shrink: 0; |
| | | font-size: 11px; |
| | | color: #bbb; |
| | | } |
| | | ::v-deep .notice-preview-dialog { |
| | | .el-dialog__body { padding: 0 20px 20px; } |
| | | .notice-preview-meta { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 14px; |
| | | padding: 12px 0; |
| | | font-size: 12px; |
| | | color: #888; |
| | | .notice-preview-info { display: flex; align-items: center; gap: 4px; } |
| | | } |
| | | .notice-preview-divider { |
| | | height: 1px; |
| | | background: linear-gradient(to right, transparent, #e2e8f0, transparent); |
| | | margin-bottom: 16px; |
| | | } |
| | | .notice-preview-content { |
| | | font-size: 14px; |
| | | line-height: 1.85; |
| | | color: #2d3748; |
| | | word-break: break-word; |
| | | img { max-width: 100%; border-radius: 4px; } |
| | | p { margin: 0 0 1em; } |
| | | a { color: #409EFF; text-decoration: underline; } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <size-select id="size-select" class="right-menu-item hover-effect" />
|
| | | </el-tooltip>
|
| | |
|
| | | <el-tooltip content="消息通知" effect="dark" placement="bottom">
|
| | | <header-notice id="header-notice" class="right-menu-item hover-effect" />
|
| | | </el-tooltip>
|
| | |
|
| | | </template>
|
| | |
|
| | | <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover">
|
| | |
| | | import Search from '@/components/HeaderSearch'
|
| | | import RuoYiGit from '@/components/RuoYi/Git'
|
| | | import RuoYiDoc from '@/components/RuoYi/Doc'
|
| | | import HeaderNotice from './HeaderNotice'
|
| | |
|
| | | export default {
|
| | | emits: ['setLayout'],
|
| | | components: {
|
| | | Breadcrumb,
|
| | | Logo,
|
| | |
| | | SizeSelect,
|
| | | Search,
|
| | | RuoYiGit,
|
| | | RuoYiDoc
|
| | | RuoYiDoc,
|
| | | HeaderNotice
|
| | | },
|
| | | computed: {
|
| | | ...mapGetters([
|
| | |
| | | -- ---------------------------- |
| | | insert into sys_notice values('1', '温馨提醒:2018-07-01 若依新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员'); |
| | | insert into sys_notice values('2', '维护通知:2018-07-01 若依系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员'); |
| | | insert into sys_notice values('3', '若依开源框架介绍', '1', '<p><span style=\"color: rgb(230, 0, 0);\">项目介绍</span></p><p><font color=\"#333333\">RuoYi开源项目是为企业用户定制的后台脚手架框架,为企业打造的一站式解决方案,降低企业开发成本,提升开发效率。主要包括用户管理、角色管理、部门管理、菜单管理、参数管理、字典管理、</font><span style=\"color: rgb(51, 51, 51);\">岗位管理</span><span style=\"color: rgb(51, 51, 51);\">、定时任务</span><span style=\"color: rgb(51, 51, 51);\">、</span><span style=\"color: rgb(51, 51, 51);\">服务监控、登录日志、操作日志、代码生成等功能。其中,还支持多数据源、数据权限、国际化、Redis缓存、Docker部署、滑动验证码、第三方认证登录、分布式事务、</span><font color=\"#333333\">分布式文件存储</font><span style=\"color: rgb(51, 51, 51);\">、分库分表处理等技术特点。</span></p><p><img src=\"https://foruda.gitee.com/images/1773931848342439032/a4d22313_1815095.png\" style=\"width: 64px;\"><br></p><p><span style=\"color: rgb(230, 0, 0);\">官网及演示</span></p><p><span style=\"color: rgb(51, 51, 51);\">若依官网地址: </span><a href=\"http://ruoyi.vip\" target=\"_blank\">http://ruoyi.vip</a><a href=\"http://ruoyi.vip\" target=\"_blank\"></a></p><p><span style=\"color: rgb(51, 51, 51);\">若依文档地址: </span><a href=\"http://doc.ruoyi.vip\" target=\"_blank\">http://doc.ruoyi.vip</a><br></p><p><span style=\"color: rgb(51, 51, 51);\">演示地址【不分离版】: </span><a href=\"http://demo.ruoyi.vip\" target=\"_blank\">http://demo.ruoyi.vip</a></p><p><span style=\"color: rgb(51, 51, 51);\">演示地址【分离版本】: </span><a href=\"http://vue.ruoyi.vip\" target=\"_blank\">http://vue.ruoyi.vip</a></p><p><span style=\"color: rgb(51, 51, 51);\">演示地址【微服务版】: </span><a href=\"http://cloud.ruoyi.vip\" target=\"_blank\">http://cloud.ruoyi.vip</a></p><p><span style=\"color: rgb(51, 51, 51);\">演示地址【移动端版】: </span><a href=\"http://h5.ruoyi.vip\" target=\"_blank\">http://h5.ruoyi.vip</a></p><p><br style=\"color: rgb(48, 49, 51); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 12px;\"></p>', '0', 'admin', sysdate(), '', null, '管理员'); |
| | | |
| | | |
| | | -- ---------------------------- |
| | | -- 18、代码生成业务表 |
| | | -- 18、公告已读记录表 |
| | | -- ---------------------------- |
| | | drop table if exists sys_notice_read; |
| | | create table sys_notice_read ( |
| | | read_id bigint(20) not null auto_increment comment '已读主键', |
| | | notice_id int(4) not null comment '公告id', |
| | | user_id bigint(20) not null comment '用户id', |
| | | read_time datetime not null comment '阅读时间', |
| | | primary key (read_id), |
| | | unique key uk_user_notice (user_id, notice_id) comment '同一用户同一公告只记录一次' |
| | | ) engine=innodb auto_increment=1 comment='公告已读记录表'; |
| | | |
| | | |
| | | -- ---------------------------- |
| | | -- 19、代码生成业务表 |
| | | -- ---------------------------- |
| | | drop table if exists gen_table; |
| | | create table gen_table ( |
| | |
| | | |
| | | |
| | | -- ---------------------------- |
| | | -- 19、代码生成业务表字段 |
| | | -- 20、代码生成业务表字段 |
| | | -- ---------------------------- |
| | | drop table if exists gen_table_column; |
| | | create table gen_table_column ( |