逻辑

前后端交互:

前端通过/notice/开头的接口与后端交互
首页会加载最新公告显示在时间轴上
管理页面可以查看完整公告列表并进行管理操作

数据结构:

每个公告包含4个基本信息:ID、标题(title)、内容(content)和发布时间(time)
核心功能:

增:管理员可以添加新公告,系统会自动记录发布时间
删:可以按ID删除公告
改:可以修改公告的标题和内容
查:

1.  查看所有公告(按时间倒序显示)
1.  按标题关键词搜索公告
1.  分页查看公告列表

业务流程:

管理员发布公告 → 存入数据库 → 首页自动显示 → 学生/教师登录后即可查看
公告修改后所有用户看到的内容会实时更新

创建数据库表

1
2
3
4
5
6
7
CREATE TABLE `notice` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`title` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '公告标题',
`content` text COLLATE utf8mb4_unicode_ci COMMENT '公告内容',
`time` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '公告时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公告信息';

image-20250421223724230

entity—Notice.java

创建实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.example.entity;

public class Notice {
private Integer id;
private String title;
private String content;
private String time;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public String getTime() {
return time;
}

public void setTime(String time) {
this.time = time;
}
}

image-20250421220954793

前端

Manager.vue

1
2
3
4
5
6
7
8
9
10
11
<!--仅管理员可以看到公告信息,教师和学生都看不到 index="2" 是菜单的索引-->
<el-sub-menu index="2">
<template #title>
<el-icon><Memo /></el-icon>
<span>信息管理</span>
</template>
<el-menu-item index="/notice" v-if ="data.user.role === 'ADMIN'">
<el-icon><Bell /></el-icon>
<span>公告信息</span>
</el-menu-item>
</el-sub-menu>

配置路由index.js

1
{ path: 'notice', component: () => import('@/views/manager/Notice.vue')},

Notice.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<template>
<div>

<div class="card" style="margin-bottom: 5px;">
<el-input v-model="data.title" style="width: 300px; margin-right: 10px" placeholder="请输入标题查询"></el-input>
<el-button type="primary" @click="load">查询</el-button>
<el-button type="info" style="margin: 0 10px" @click="reset">重置</el-button>
</div>

<div class="card" style="margin-bottom: 5px">
<div style="margin-bottom: 10px">
<el-button type="primary" @click="handleAdd">新增</el-button>
</div>
<el-table :data="data.tableData" stripe>
<el-table-column label="公告标题" prop="title"></el-table-column>
<el-table-column label="公告内容" prop="content"></el-table-column>
<el-table-column label="发布时间" prop="time"></el-table-column>
<el-table-column label="操作" align="center" width="160">
<template #default="scope">
<el-button type="primary" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>

<div class="card">
<el-pagination background layout="prev, pager, next" v-model:page-size="data.pageSize" v-model:current-page="data.pageNum" :total="data.total"/>
</div>

<el-dialog title="公告信息" width="40%" v-model="data.formVisible" :close-on-click-modal="false" destroy-on-close>
<el-form :model="data.form" label-width="100px" style="padding-right: 50px">
<el-form-item label="公告标题" prop="title">
<el-input v-model="data.form.title" autocomplete="off" />
</el-form-item>
<el-form-item label="公告内容" prop="content">
<el-input type="textarea" :rows="4" v-model="data.form.content" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false">取 消</el-button>
<el-button type="primary" @click="save">保 存</el-button>
</span>
</template>
</el-dialog>

</div>
</template>

<script setup>
import request from "@/utils/request";
import {reactive} from "vue";
import {ElMessageBox, ElMessage} from "element-plus";


const data = reactive({
pageNum: 1,
pageSize: 10,
total: 0,
formVisible: false,
form: {},
tableData: [],
title: null
})

// 分页查询
const load = () => {
request.get('/notice/selectPage', {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
title: data.title
}
}).then(res => {
data.tableData = res.data?.list
data.total = res.data?.total
})
}

// 新增
const handleAdd = () => {
data.form = {}
data.formVisible = true
}

// 编辑
const handleEdit = (row) => {
data.form = JSON.parse(JSON.stringify(row))
data.formVisible = true
}

// 新增保存
const add = () => {
request.post('/notice/add', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}

// 编辑保存
const update = () => {
request.put('/notice/update', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}

// 弹窗保存
const save = () => {
// data.form有id就是更新,没有就是新增
data.form.id ? update() : add()
}

// 删除
const handleDelete = (id) => {
ElMessageBox.confirm('删除后数据无法恢复,您确定删除吗?', '删除确认', { type: 'warning' }).then(res => {
request.delete('/notice/deleteById//' + id).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
} else {
ElMessage.error(res.msg)
}
})
}).catch(err => {})
}

// 重置
const reset = () => {
data.title = null
load()
}

load()
</script>

Controller—NoticeController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* 公告信息模块前端操作接口入口
* 拿到数据后,调用service层的方法,返回结果
* 前端调用接口时,需要传入参数,调用service层的方法,返回结果
**/
@RestController

@RequestMapping("/notice")
public class NoticeController {
@Resource
// 注入NoticeService
private NoticeService noticeService;

/**
* 新增公告信息
* */
@PostMapping("/add")
public Result add(@RequestBody Notice notice){
noticeService.add(notice);
return Result.success();
}

/**
* 更新公告信息
*/
@PutMapping("/update")//更新公告信息信息,前端调用接口时,需要传入参数,调用service层的方法,返回结果
public Result update(@RequestBody Notice notice){//接收前端传来的参数
noticeService.updateById(notice);//调用service层的方法,返回结果
return Result.success();
}

/**
* 删除公告信息
*/
@DeleteMapping("/deleteById/{id}")
public Result deleteById(@PathVariable Integer id) {
noticeService.deleteById(id);
return Result.success();
}

/**
* 分页查询
* */
@GetMapping("selectPage")
public Result selectPage(Notice notice,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "5") Integer pageSize){
// 调用service层的方法,返回结果
PageInfo<Notice> pageInfo = noticeService.selectPage(notice,pageNum,pageSize);
// 总数
return Result.success(pageInfo);
}
/**
* 查询所有公告信息
*/
@GetMapping("/selectAll")
public Result selectAll(){
List<Notice> list = noticeService.selectAll();
return Result.success(list);
}
}


Service—NoticeService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 公告模块业务逻辑接口
* 要把数据往数据库里存,调用mapper层的方法,返回结果
* 业务逻辑接口调用mapper层的方法,返回结果
*/
@Service
public class NoticeService {
// 注入NoticeMapper
@Resource
private NoticeMapper noticeMapper;
/**
* 新增公告
*/
public void add(Notice notice) {
notice.setTime(DateUtil.now());
noticeMapper.insert(notice);
}
/**
* 分页查询
*/
public PageInfo<Notice> selectPage(Notice notice,Integer pageNum, Integer pageSize) {
// ToDo 分页查询逻辑处理
// 1. 分页查询公告信息
// 2. 返回公告信息
// 3. 如果没有公告信息,返回空列表
// 4. 如果有公告信息,返回公告信息列表
List<Notice> list;//定义一个list集合
PageHelper.startPage(pageNum,pageSize);//分页查询
if(ObjectUtil.isNotEmpty(notice.getTitle())){//如果title不为空,就按照title查询
list = noticeMapper.selectByTitle(notice.getTitle());
}else{//如果title为空,就查询所有公告信息
list = noticeMapper.selectAll();
}
return PageInfo.of(list);//返回公告信息列表
}

//根据id查询公告信息,返回一个notice对象
public void updateById(Notice notice) {//传入一个notice对象
noticeMapper.updateById(notice);//调用mapper层的方法,返回结果
}


//根据id删除公告信息
public void deleteById(Integer id) {
noticeMapper.deleteById(id);
}

Mapper

NoticeMapper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.List;

@Mapper
public interface NoticeMapper {
//新增
void insert(Notice notice);
//查询所有
@Select("select * from notice")
List<Notice> selectAll();

//根据title查询
@Select("select * from notice where title like concat('%',#{title},'%')")
List<Notice> selectByTitle(String title);

//修改
void updateById(Notice notice);

//删除
@Delete("delete from notice where id = #{id}")
void deleteById(Integer id);
}

NoticeMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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.example.mapper.NoticeMapper">
<insert id="insert" parameterType="com.example.entity.Notice" useGeneratedKeys="true">
insert into notice (title, content, time)
values (#{title}, #{content}, #{time})
</insert>

<update id="updateById" parameterType="com.example.entity.Notice">
update notice
set title = #{title}, content = #{content}, time = #{time}
where id = #{id}
-- 这里的id是实体类中的属性名,不是数据库中的字段名
</update>
</mapper>

点击没有反应怎么办?!重点学习

我们要确定的问题是什么?不要慌,是问题总有解决方法不是?!

  1. F12打开,看一下网络请求,接口和参数有没有问题
  2. 如果都对,再看下后台有没有报错
  3. 如果没有,那么就要在后台打个断点,看看具体在什么环节出问题了

比如:

额。。。不仔细公告信息写成公告信息信息了

image-20250421220954793

首页渲染

Timeline: https://element-plus.org/zh-CN/component/timeline.html

image-20250421220954793

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<template>
<div>
<div class="card" style="line-height: 30px; margin-bottom: 5px">
<div>欢迎您,{{ data.user.name }} 祝您今天过得开心!</div>
</div>

<!-- 公告信息 -->
<div style="display: flex">
<div class="card" style="flex: 50%">
<div style="font-size: 16px; font-weight: bold; padding: 10px 10px 20px">系统公告</div>
<!--时间轴-->
<el-timeline>
<el-timeline-item
v-for="(item, index) in data.noticeDate"
:key="index"
:timestamp="item.time"
>
<!--时间轴的内容-->
{{item.title}}:{{ item.content }}
</el-timeline-item>
</el-timeline>
</div>
<div style="flex: 50%;margin-left: 5px">
</div>
</div>
</div>
</template>

<script setup>
import { reactive } from "vue";
import request from "@/utils/request";
import {ElMessage} from "element-plus";

const data = reactive({
// 用户信息
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
// 公告信息
noticeDate: [],
})
// 加载公告信息
const loadNotice = () => {
request.get('/notice/selectAll').then(res => {
if (res.code === '200') {
data.noticeDate = res.data
}else {
ElMessage.error(res.msg)
}
})
}
// 初始化 加载数据
loadNotice()
</script>

Controller—NoticeController

1
2
3
4
5
6
7
8
/**
* 查询所有公告信息
*/
@GetMapping("/selectAll")
public Result selectAll(){
List<Notice> list = noticeService.selectAll();
return Result.success(list);
}

Service—NoticeService.java

1
2
3
4
5
6
/**
* 查询所有 Notice 对象
*/
public List<Notice> selectAll() {
return NoticeMapper.selectAll();
}

image