SpringBoot学习笔记-创建个人中心页面(下)

  1. 1. 实现个人中心页面
  2. 2. POJO时区修改
  3. 3. 集成代码编辑器

本节内容为实现个人中心的前端页面,用户能够查看自己的 Bot 信息,并能创建、修改或删除 Bot,此外还集成了 Vue Ace Editor 代码编辑器,方便用户输入 Bot 的代码。

1. 实现个人中心页面

我们要实现用户的个人中心页面,能够展示用户的头像以及自己所有 Bot 的信息,例如 Bot 名字、创建时间等,还需要具有创建/修改/删除 Bot 等功能,在很早之前创建的 MyBotsIndexView 组件中实现:

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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
<template>
<div class="container">
<div class="card text-bg-secondary" style="margin-top: 20px;">
<div class="card-header">
<h3>My Bots</h3>
</div>
<div class="card-body" style="background-color: rgba(255, 255, 255, 0.5);">
<div class="row">
<div class="col-md-3">
<div class="card">
<!-- 用户头像与用户名 -->
<div class="card-body text-center">
<img class="img-fluid" :src="$store.state.user.photo" style="width: 50%; border-radius: 50%;">
<div style="font-size: 20px; margin-top: 10px; font-weight: bold;">
{{ $store.state.user.username }}
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div class="card">
<div class="card-header">
<span style="font-size: 25px;">Bot 管理</span>
<!-- 点击创建按钮能够打开模态框 -->
<button class="btn btn-outline-success float-end" type="button" data-bs-toggle="modal" data-bs-target="#add_bot_modal">
创建 Bot
</button>
<!-- 创建 Bot 的模态框 -->
<div class="modal fade" id="add_bot_modal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header" style="background-color: #198754;">
<h1 class="modal-title fs-5" style="color: white;">创建 Bot</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="title" class="form-label">名称</label>
<input v-model="new_bot.title" type="email" class="form-control" id="title" placeholder="请输入 Bot 名称">
</div>
<div class="mb-3">
<label for="description" class="form-label">简介</label>
<textarea v-model="new_bot.description" class="form-control" id="description" rows="3" placeholder="介绍一下你的 Bot 吧~(可以暂时不填)"></textarea>
</div>
<div class="mb-3">
<label for="content" class="form-label">代码</label>
<textarea v-model="new_bot.content" class="form-control" id="content" rows="14" placeholder="在这里编写你的 Bot 代码~"></textarea>
</div>
</div>
<div class="modal-footer">
<div class="error_message" style="color: red;">{{ new_bot.error_message }}</div>
<button type="button" class="btn btn-success" @click="add_bot">创建</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
</div>
</div>
</div>
</div>
</div>
<!-- 用户的 Bot 列表 -->
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>名称</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="bot in bots" :key="bot.id">
<td style="line-height: 32px;">{{ bot.title }}</td>
<td style="line-height: 32px;">{{ bot.createtime }}</td>
<td>
<button class="btn btn-outline-primary" type="button" data-bs-toggle="modal" :data-bs-target="'#update_bot_modal_' + bot.id" style="margin-right: 10px; padding: 3px 10px;">
修改
</button>
<button class="btn btn-outline-danger" type="button" data-bs-toggle="modal" :data-bs-target="'#remove_bot_modal_' + bot.id" style="padding: 3px 10px;">
删除
</button>
<!-- 修改 Bot 的模态框 -->
<div class="modal fade" :id="'update_bot_modal_' + bot.id" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header" style="background-color: #0D6EFD;">
<h1 class="modal-title fs-5" style="color: white;">修改 Bot</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="title" class="form-label">名称</label>
<input v-model="bot.title" type="email" class="form-control" id="title" placeholder="请输入 Bot 名称">
</div>
<div class="mb-3">
<label for="description" class="form-label">简介</label>
<textarea v-model="bot.description" class="form-control" id="description" rows="3" placeholder="介绍一下你的 Bot 吧~(可以暂时不填)"></textarea>
</div>
<div class="mb-3">
<label for="content" class="form-label">代码</label>
<textarea v-model="bot.content" class="form-control" id="content" rows="14" placeholder="在这里编写你的 Bot 代码~"></textarea>
</div>
</div>
<div class="modal-footer">
<div class="error_message" style="color: red;">{{ new_bot.error_message }}</div>
<button type="button" class="btn btn-primary" @click="update_bot(bot)">保存修改</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
</div>
</div>
</div>
</div>
<!-- 删除 Bot 的确认模态框 -->
<div class="modal fade" :id="'remove_bot_modal_' + bot.id" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header" style="background-color: #DC3545;">
<h1 class="modal-title fs-5" style="color: white;">警告</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" style="font-size: 20px;">
确认删除?该操作无法撤销哦~
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" @click="remove_bot(bot)">删除</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
</div>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import { ref, reactive } from "vue"; // reactive用来让组件绑定对象
import { useStore } from "vuex";
import { Modal } from "bootstrap/dist/js/bootstrap";
import $ from "jquery";

export default {
setup() {
const store = useStore();
let bots = ref([]); // bot列表

const new_bot = reactive({ // 要新创建的bot对象
title: "",
description: "",
content: "",
error_message: "",
});

const get_bots = () => {
$.ajax({
url: "http://localhost:3000/user/bot/getlist/",
type: "GET",
headers: {
Authorization: "Bearer " + store.state.user.jwt_token,
},
success(resp) {
bots.value = resp; // 后端返回的就是一个列表
},
});
};

get_bots(); // 定义完后直接执行一遍

const add_bot = () => {
new_bot.error_message = "";
$.ajax({
url: "http://localhost:3000/user/bot/add/",
type: "POST",
data: {
title: new_bot.title,
description: new_bot.description,
content: new_bot.content,
},
headers: {
Authorization: "Bearer " + store.state.user.jwt_token,
},
success(resp) {
if (resp.result === "success") {
// 需要清空信息防止下次打开创建模态框时还留有上次的信息
new_bot.title = "";
new_bot.description = "";
new_bot.content = "";
Modal.getInstance("#add_bot_modal").hide(); // 关闭模态框
get_bots(); // 刷新一下Bot列表
} else {
new_bot.error_message = resp.result;
}
},
});
};

const remove_bot = (bot) => {
$.ajax({
url: "http://localhost:3000/user/bot/remove/",
type: "POST",
data: {
bot_id: bot.id,
},
headers: {
Authorization: "Bearer " + store.state.user.jwt_token,
},
success(resp) {
if (resp.result === "success") {
Modal.getInstance("#remove_bot_modal_" + bot.id).hide();
get_bots();
}
},
});
};

const update_bot = (bot) => {
new_bot.error_message = "";
$.ajax({
url: "http://localhost:3000/user/bot/update/",
type: "POST",
data: {
bot_id: bot.id,
title: bot.title,
description: bot.description,
content: bot.content,
},
headers: {
Authorization: "Bearer " + store.state.user.jwt_token,
},
success(resp) {
if (resp.result === "success") {
Modal.getInstance("#update_bot_modal_" + bot.id).hide();
get_bots();
} else {
new_bot.error_message = resp.result;
}
},
});
};

return {
bots,
new_bot,
add_bot,
remove_bot,
update_bot,
};
},
};
</script>

<style scoped></style>

注意,我们实现了当点击创建 Bot 按钮时弹出一个模态框(Bootstrap 中的 Modal)的效果,然后用户可以在里面编辑 Bot 的信息。但对于每个 Bot 的修改和删除应该都会分别对应一个模态框,而不像创建那样只有一个模态框,因此每个模态框还需要加一个 ID 来对应。

2. POJO时区修改

现在我们会发现前端页面中显示的 Bot 创建时间和后端数据库中的不一致,需要在 pojo 中的 Bot 类中修改:

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
package com.kob.backend.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Bot {
@TableId(value = "id", type = IdType.AUTO) // 声明id为自增类型
private Integer id;
private Integer userId; // 注意驼峰命名,userId之后会被解析为user_id,别写成userID,因为这样会解析成user_i_d
private String title;
private String description;
private String content;
private Integer rating;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") // 注意日期格式和时区的设置
private Date createtime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date modifytime;
}

3. 集成代码编辑器

由于 Bot 的内容有代码部分,之前我们用的是 <textarea> 让用户编写代码,这样编写体验不好,因此我们需要集成一个代码编辑器,我们使用的是 Vue Ace Editor(Ace GitHub 官网:Ace (Ajax.org Cloud9 Editor),Vue 3 版 Ace GitHub 官网:vue3-ace-editor)。

先在 Vue 的控制台中安装以下依赖:

  • vue3-ace-editor
  • ace-builds

然后就可以用 <VAceEditor> 替代之前的 <textarea>

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
<template>
<div class="container">
<div class="card text-bg-secondary" style="margin-top: 20px;">
...
<div class="card-body" style="background-color: rgba(255, 255, 255, 0.5);">
<div class="row">
...
<div class="col-md-9">
<div class="card">
<div class="card-header">
...
<!-- 创建 Bot 的模态框 -->
<div class="modal fade" id="add_bot_modal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
...
<div class="modal-body">
...
<div class="mb-3">
<label for="content" class="form-label">代码</label>
<VAceEditor
v-model:value="new_bot.content"
@init="editorInit"
lang="c_cpp"
theme="textmate"
style="height: 400px"
:options="{
enableBasicAutocompletion: true, //启用基本自动完成
enableSnippets: true, // 启用代码段
enableLiveAutocompletion: true, // 启用实时自动完成
fontSize: 16, //设置字号
tabSize: 4, // 标签大小
showPrintMargin: false, //去除编辑器里的竖线
highlightActiveLine: true, // 选中行高亮显示
}"
/>
</div>
</div>
...
</div>
</div>
</div>
</div>
<!-- 用户的 Bot 列表 -->
<div class="card-body">
<table class="table table-striped table-hover">
...
<tbody>
<tr v-for="bot in bots" :key="bot.id">
<td style="line-height: 32px;">{{ bot.title }}</td>
<td style="line-height: 32px;">{{ bot.createtime }}</td>
<td>
...
<!-- 修改 Bot 的模态框 -->
<div class="modal fade" :id="'update_bot_modal_' + bot.id" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
...
<div class="modal-body">
...
<div class="mb-3">
<label for="content" class="form-label">代码</label>
<VAceEditor
v-model:value="bot.content"
@init="editorInit"
lang="c_cpp"
theme="textmate"
style="height: 400px"
:options="{
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
fontSize: 16,
tabSize: 4,
showPrintMargin: false,
highlightActiveLine: true,
}"
/>
</div>
</div>
...
</div>
</div>
</div>
<!-- 删除 Bot 的确认模态框 -->
...
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
...
import { VAceEditor } from "vue3-ace-editor";
import ace from "ace-builds";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-chrome";
import "ace-builds/src-noconflict/ext-language_tools";
import "ace-builds/src-noconflict/mode-c_cpp";

export default {
components: {
VAceEditor,
},

setup() {
ace.config.set(
"basePath",
"https://cdn.jsdelivr.net/npm/ace-builds@" +
require("ace-builds").version +
"/src-noconflict/");

...
},
};
</script>

<style scoped></style>