快捷菜单
常用功能一站直达
更多功能请点顶栏「快捷菜单」

请帮我生成一个 时间轴 前端页面(主要用于记录自己一些关键日期发生的事件)。
另外,请帮我生成 一个 vue3版本的组件,不要使用Tailwind CSS,样式直接使用普通css写在组件内。
我将为您创建一个 Vue3 版本的时间轴组件,用于记录关键日期发生的事件。这个组件将包含事件展示、添加新事件和删除事件的功能,所有样式将使用普通 CSS 编写在组件内部。
Vue3时间轴组件
V1 Timeline.vue
1<template>
2 <div class="timeline-container">
3 <h1 class="timeline-title">我的时间轴</h1>
4
5 <!-- 添加新事件的表单 -->
6 <div class="add-event-form">
7 <h2>添加新事件</h2>
8 <div class="form-group">
9 <label for="event-date">日期:</label>
10 <input type="date" id="event-date" v-model="newEvent.date">
11 </div>
12 <div class="form-group">
13 <label for="event-title">标题:</label>
14 <input type="text" id="event-title" v-model="newEvent.title" placeholder="输入事件标题">
15 </div>
16 <div class="form-group">
17 <label for="event-description">描述:</label>
18 <textarea id="event-description" v-model="newEvent.description" placeholder="输入事件描述"></textarea>
19 </div>
20 <button @click="addEvent" class="add-button">添加事件</button>
21 </div>
22
23 <!-- 时间轴事件列表 -->
24 <div class="timeline-events">
25 <div v-if="sortedEvents.length === 0" class="no-events">
26 暂无事件,请添加第一个事件吧!
27 </div>
28
29 <div
30 v-for="(event, index) in sortedEvents"
31 :key="index"
32 class="timeline-item"
33 :class="{ 'timeline-item-left': index % 2 === 0, 'timeline-item-right': index % 2 !== 0 }"
34 >
35 <div class="timeline-dot"></div>
36 <div class="timeline-content">
37 <div class="event-date">{{ formatDate(event.date) }}</div>
38 <h3 class="event-title">{{ event.title }}</h3>
39 <p class="event-description">{{ event.description }}</p>
40 <button @click="deleteEvent(index)" class="delete-button">删除</button>
41 </div>
42 </div>
43 </div>
44 </div>
45</template>
46
47<script setup>
48import { ref, computed } from 'vue';
49
50// 初始化事件列表,从本地存储加载(如果有)
51const events = ref(JSON.parse(localStorage.getItem('timelineEvents')) || [
52 {
53 date: '2023-01-15',
54 title: '开始新项目',
55 description: '今天开始了我的第一个大型项目,感到既兴奋又紧张。'
56 },
57 {
58 date: '2023-03-22',
59 title: '完成重要里程碑',
60 description: '成功完成了项目的第一个重要里程碑,团队一起庆祝了一下。'
61 },
62 {
63 date: '2023-06-10',
64 title: '参加技术研讨会',
65 description: '今天参加了一个很有意义的技术研讨会,学到了很多新知识。'
66 }
67]);
68
69// 新事件的数据
70const newEvent = ref({
71 date: '',
72 title: '',
73 description: ''
74});
75
76// 按日期排序事件(从早到晚)
77const sortedEvents = computed(() => {
78 return [...events.value].sort((a, b) => new Date(a.date) - new Date(b.date));
79});
80
81// 添加新事件
82const addEvent = () => {
83 if (!newEvent.value.date || !newEvent.value.title) {
84 alert('请填写日期和标题');
85 return;
86 }
87
88 events.value.push({...newEvent.value});
89
90 // 保存到本地存储,防止页面刷新后数据丢失
91 localStorage.setItem('timelineEvents', JSON.stringify(events.value));
92
93 // 重置表单
94 newEvent.value = {
95 date: '',
96 title: '',
97 description: ''
98 };
99};
100
101// 删除事件
102const deleteEvent = (index) => {
103 if (confirm('确定要删除这个事件吗?')) {
104 events.value.splice(index, 1);
105 localStorage.setItem('timelineEvents', JSON.stringify(events.value));
106 }
107};
108
109// 格式化日期显示
110const formatDate = (dateString) => {
111 const date = new Date(dateString);
112 return date.toLocaleDateString('zh-CN', {
113 year: 'numeric',
114 month: 'long',
115 day: 'numeric'
116 });
117};
118</script>
119
120<style scoped>
121.timeline-container {
122 max-width: 1200px;
123 margin: 0 auto;
124 padding: 20px;
125 font-family: 'Arial', sans-serif;
126 color: #333;
127}
128
129.timeline-title {
130 text-align: center;
131 color: #2c3e50;
132 margin-bottom: 40px;
133 font-size: 2.5rem;
134}
135
136.add-event-form {
137 background-color: #f8f9fa;
138 padding: 25px;
139 border-radius: 8px;
140 margin-bottom: 50px;
141 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
142}
143
144.add-event-form h2 {
145 margin-top: 0;
146 color: #34495e;
147 margin-bottom: 20px;
148}
149
150.form-group {
151 margin-bottom: 15px;
152}
153
154.form-group label {
155 display: block;
156 margin-bottom: 5px;
157 font-weight: bold;
158 color: #2c3e50;
159}
160
161.form-group input,
162.form-group textarea {
163 width: 100%;
164 padding: 10px;
165 border: 1px solid #ddd;
166 border-radius: 4px;
167 font-size: 1rem;
168}
169
170.form-group textarea {
171 min-height: 100px;
172 resize: vertical;
173}
174
175.add-button {
176 background-color: #3498db;
177 color: white;
178 border: none;
179 padding: 10px 20px;
180 border-radius: 4px;
181 cursor: pointer;
182 font-size: 1rem;
183 transition: background-color 0.3s;
184}
185
186.add-button:hover {
187 background-color: #2980b9;
188}
189
190.timeline-events {
191 position: relative;
192 padding: 20px 0;
193}
194
195/* 时间轴中心线 */
196.timeline-events::before {
197 content: '';
198 position: absolute;
199 top: 0;
200 bottom: 0;
201 left: 50%;
202 width: 4px;
203 background-color: #e0e0e0;
204 transform: translateX(-50%);
205}
206
207.timeline-item {
208 margin-bottom: 40px;
209 position: relative;
210 width: 50%;
211}
212
213.timeline-item-left {
214 left: 0;
215 padding-right: 40px;
216}
217
218.timeline-item-right {
219 left: 50%;
220 padding-left: 40px;
221}
222
223/* 时间点 */
224.timeline-dot {
225 position: absolute;
226 width: 20px;
227 height: 20px;
228 background-color: #3498db;
229 border-radius: 50%;
230 top: 10px;
231 z-index: 1;
232}
233
234.timeline-item-left .timeline-dot {
235 right: -10px;
236}
237
238.timeline-item-right .timeline-dot {
239 left: -10px;
240}
241
242.timeline-content {
243 background-color: white;
244 padding: 20px;
245 border-radius: 8px;
246 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
247}
248
249.event-date {
250 color: #7f8c8d;
251 font-size: 0.9rem;
252 margin-bottom: 5px;
253}
254
255.event-title {
256 margin: 0 0 10px 0;
257 color: #2c3e50;
258}
259
260.event-description {
261 margin: 0;
262 color: #34495e;
263 line-height: 1.6;
264}
265
266.delete-button {
267 background-color: #e74c3c;
268 color: white;
269 border: none;
270 padding: 5px 10px;
271 border-radius: 4px;
272 cursor: pointer;
273 font-size: 0.9rem;
274 margin-top: 10px;
275 transition: background-color 0.3s;
276}
277
278.delete-button:hover {
279 background-color: #c0392b;
280}
281
282.no-events {
283 text-align: center;
284 color: #7f8c8d;
285 padding: 40px;
286 font-size: 1.2rem;
287}
288
289/* 响应式设计 - 适配移动设备 */
290@media (max-width: 768px) {
291 .timeline-events::before {
292 left: 30px;
293 }
294
295 .timeline-item {
296 width: 100%;
297 padding-left: 70px;
298 padding-right: 25px;
299 }
300
301 .timeline-item-left,
302 .timeline-item-right {
303 left: 0;
304 }
305
306 .timeline-item-left .timeline-dot,
307 .timeline-item-right .timeline-dot {
308 left: 20px;
309 }
310}
311</style>创建时间:06:22
这个时间轴组件具有以下特点:
您可以直接使用这个组件,也可以根据需要调整样式或添加更多功能,如编辑事件、搜索事件等。


问题1:这个 添加新事件 在最前面,显得不太好看

问题2:每个事件都有一个 红色的删除按钮,也显得很不好看

1存在如下2个问题:
2问题1:这个 添加新事件 在最前面,显得不太好看
3问题2:每个事件都有一个 红色的删除按钮,也显得很不好看
4
5请帮我按上面提出的2个问题 继续 优化下这个代码我根据您提出的两个问题进行了优化:
优化后的Vue3时间轴组件
V2
1<template>
2 <div class="timeline-container">
3 <h1 class="timeline-title">我的时间轴</h1>
4
5 <!-- 时间轴事件列表 -->
6 <div class="timeline-events">
7 <div v-if="sortedEvents.length === 0" class="no-events">
8 暂无事件,请添加第一个事件吧!
9 </div>
10
11 <div
12 v-for="(event, index) in sortedEvents"
13 :key="index"
14 class="timeline-item"
15 :class="{ 'timeline-item-left': index % 2 === 0, 'timeline-item-right': index % 2 !== 0 }"
16 >
17 <div class="timeline-dot"></div>
18 <div class="timeline-content">
19 <div class="event-date">{{ formatDate(event.date) }}</div>
20 <h3 class="event-title">{{ event.title }}</h3>
21 <p class="event-description">{{ event.description }}</p>
22 <button @click="deleteEvent(index)" class="delete-button">删除</button>
23 </div>
24 </div>
25 </div>
26
27 <!-- 添加新事件的表单(移到了页面底部) -->
28 <div class="add-event-form">
29 <h2>添加新事件</h2>
30 <div class="form-group">
31 <label for="event-date">日期:</label>
32 <input type="date" id="event-date" v-model="newEvent.date">
33 </div>
34 <div class="form-group">
35 <label for="event-title">标题:</label>
36 <input type="text" id="event-title" v-model="newEvent.title" placeholder="输入事件标题">
37 </div>
38 <div class="form-group">
39 <label for="event-description">描述:</label>
40 <textarea id="event-description" v-model="newEvent.description" placeholder="输入事件描述"></textarea>
41 </div>
42 <button @click="addEvent" class="add-button">添加事件</button>
43 </div>
44 </div>
45</template>
46
47<script setup>
48import { ref, computed } from 'vue';
49
50// 初始化事件列表,从本地存储加载(如果有)
51const events = ref(JSON.parse(localStorage.getItem('timelineEvents')) || [
52 {
53 date: '2023-01-15',
54 title: '开始新项目',
55 description: '今天开始了我的第一个大型项目,感到既兴奋又紧张。'
56 },
57 {
58 date: '2023-03-22',
59 title: '完成重要里程碑',
60 description: '成功完成了项目的第一个重要里程碑,团队一起庆祝了一下。'
61 },
62 {
63 date: '2023-06-10',
64 title: '参加技术研讨会',
65 description: '今天参加了一个很有意义的技术研讨会,学到了很多新知识。'
66 }
67]);
68
69// 新事件的数据
70const newEvent = ref({
71 date: '',
72 title: '',
73 description: ''
74});
75
76// 按日期排序事件(从早到晚)
77const sortedEvents = computed(() => {
78 return [...events.value].sort((a, b) => new Date(a.date) - new Date(b.date));
79});
80
81// 添加新事件
82const addEvent = () => {
83 if (!newEvent.value.date || !newEvent.value.title) {
84 alert('请填写日期和标题');
85 return;
86 }
87
88 events.value.push({...newEvent.value});
89
90 // 保存到本地存储,防止页面刷新后数据丢失
91 localStorage.setItem('timelineEvents', JSON.stringify(events.value));
92
93 // 重置表单
94 newEvent.value = {
95 date: '',
96 title: '',
97 description: ''
98 };
99};
100
101// 删除事件
102const deleteEvent = (index) => {
103 if (confirm('确定要删除这个事件吗?')) {
104 events.value.splice(index, 1);
105 localStorage.setItem('timelineEvents', JSON.stringify(events.value));
106 }
107};
108
109// 格式化日期显示
110const formatDate = (dateString) => {
111 const date = new Date(dateString);
112 return date.toLocaleDateString('zh-CN', {
113 year: 'numeric',
114 month: 'long',
115 day: 'numeric'
116 });
117};
118</script>
119
120<style scoped>
121.timeline-container {
122 max-width: 1200px;
123 margin: 0 auto;
124 padding: 20px;
125 font-family: 'Arial', sans-serif;
126 color: #333;
127}
128
129.timeline-title {
130 text-align: center;
131 color: #2c3e50;
132 margin-bottom: 40px;
133 font-size: 2.5rem;
134}
135
136.add-event-form {
137 background-color: #f8f9fa;
138 padding: 25px;
139 border-radius: 8px;
140 margin-top: 50px; /* 调整为顶部margin,因为移到了底部 */
141 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
142}
143
144.add-event-form h2 {
145 margin-top: 0;
146 color: #34495e;
147 margin-bottom: 20px;
148}
149
150.form-group {
151 margin-bottom: 15px;
152}
153
154.form-group label {
155 display: block;
156 margin-bottom: 5px;
157 font-weight: bold;
158 color: #2c3e50;
159}
160
161.form-group input,
162.form-group textarea {
163 width: 100%;
164 padding: 10px;
165 border: 1px solid #ddd;
166 border-radius: 4px;
167 font-size: 1rem;
168}
169
170.form-group textarea {
171 min-height: 100px;
172 resize: vertical;
173}
174
175.add-button {
176 background-color: #3498db;
177 color: white;
178 border: none;
179 padding: 10px 20px;
180 border-radius: 4px;
181 cursor: pointer;
182 font-size: 1rem;
183 transition: background-color 0.3s;
184}
185
186.add-button:hover {
187 background-color: #2980b9;
188}
189
190.timeline-events {
191 position: relative;
192 padding: 20px 0;
193}
194
195/* 时间轴中心线 */
196.timeline-events::before {
197 content: '';
198 position: absolute;
199 top: 0;
200 bottom: 0;
201 left: 50%;
202 width: 4px;
203 background-color: #e0e0e0;
204 transform: translateX(-50%);
205}
206
207.timeline-item {
208 margin-bottom: 40px;
209 position: relative;
210 width: 50%;
211}
212
213.timeline-item-left {
214 left: 0;
215 padding-right: 40px;
216}
217
218.timeline-item-right {
219 left: 50%;
220 padding-left: 40px;
221}
222
223/* 时间点 */
224.timeline-dot {
225 position: absolute;
226 width: 20px;
227 height: 20px;
228 background-color: #3498db;
229 border-radius: 50%;
230 top: 10px;
231 z-index: 1;
232}
233
234.timeline-item-left .timeline-dot {
235 right: -10px;
236}
237
238.timeline-item-right .timeline-dot {
239 left: -10px;
240}
241
242.timeline-content {
243 background-color: white;
244 padding: 20px;
245 border-radius: 8px;
246 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
247 transition: transform 0.3s;
248}
249
250/* 鼠标悬停时轻微上浮效果 */
251.timeline-item:hover .timeline-content {
252 transform: translateY(-5px);
253}
254
255.event-date {
256 color: #7f8c8d;
257 font-size: 0.9rem;
258 margin-bottom: 5px;
259}
260
261.event-title {
262 margin: 0 0 10px 0;
263 color: #2c3e50;
264}
265
266.event-description {
267 margin: 0;
268 color: #34495e;
269 line-height: 1.6;
270 margin-bottom: 10px;
271}
272
273/* 改进的删除按钮样式 */
274.delete-button {
275 background-color: #ff6b6b;
276 color: white;
277 border: none;
278 padding: 5px 10px;
279 border-radius: 4px;
280 cursor: pointer;
281 font-size: 0.9rem;
282 transition: all 0.3s;
283 opacity: 0; /* 默认隐藏 */
284 transform: translateY(5px);
285}
286
287/* 鼠标悬停在事件上时显示删除按钮 */
288.timeline-item:hover .delete-button {
289 opacity: 1;
290 transform: translateY(0);
291}
292
293.delete-button:hover {
294 background-color: #ff5252;
295}
296
297.no-events {
298 text-align: center;
299 color: #7f8c8d;
300 padding: 40px;
301 font-size: 1.2rem;
302}
303
304/* 响应式设计 - 适配移动设备 */
305@media (max-width: 768px) {
306 .timeline-events::before {
307 left: 30px;
308 }
309
310 .timeline-item {
311 width: 100%;
312 padding-left: 70px;
313 padding-right: 25px;
314 }
315
316 .timeline-item-left,
317 .timeline-item-right {
318 left: 0;
319 }
320
321 .timeline-item-left .timeline-dot,
322 .timeline-item-right .timeline-dot {
323 left: 20px;
324 }
325}
326</style>创建时间:06:32
除了您提出的两个主要优化点外,我还增加了一个小细节:当鼠标悬停在事件卡片上时,卡片会有一个轻微上浮的效果,增强了交互感。
这些改进使得整个时间轴更加整洁,删除操作不再干扰正常浏览,同时添加新事件的功能也不会喧宾夺主,而是作为一个辅助功能放在页面底部。
问题1:这个部署到自己博客后,其它用户是不是也能直接删除这个事件呢?或者 也能新增新事件呢? 正常情况,我不需要其他用户具有这个修改的权限,他们只需要查看的权限。 修改的权限只能是我自己。请帮我继续优化下,输出完整的代码。
这个部署到自己博客后,其它用户是不是也能直接删除这个事件呢?或者 也能新增新事件呢? 正常情况,我不需要其他用户具有这个修改的权限,他们只需要查看的权限。 修改的权限只能是我自己。请帮我继续优化下,输出完整的代码。
为了解决权限控制的问题,我可以添加一个简单的密码验证机制,只有输入正确密码的用户(也就是您自己)才能看到并使用添加和删除功能,其他用户只能查看时间轴内容。
带权限控制的Vue3时间轴组件
V3
1<template>
2 <div class="timeline-container">
3 <h1 class="timeline-title">我的时间轴</h1>
4
5 <!-- 密码验证区域 -->
6 <div v-if="!isAuthenticated && showAuthForm" class="auth-form">
7 <h2>管理员验证</h2>
8 <input
9 type="password"
10 v-model="adminPassword"
11 placeholder="输入管理员密码"
12 @keyup.enter="authenticate"
13 class="password-input"
14 >
15 <button @click="authenticate" class="auth-button">验证</button>
16 <p v-if="authError" class="auth-error">密码错误,请重试</p>
17 </div>
18
19 <!-- 显示管理按钮(仅管理员可见) -->
20 <div v-if="sortedEvents.length > 0 && !showAuthForm && !isAuthenticated" class="manage-controls">
21 <button @click="showAuthForm = true" class="manage-button">管理时间轴</button>
22 </div>
23
24 <!-- 时间轴事件列表 -->
25 <div class="timeline-events">
26 <div v-if="sortedEvents.length === 0" class="no-events">
27 暂无事件,请添加第一个事件吧!
28 </div>
29
30 <div
31 v-for="(event, index) in sortedEvents"
32 :key="index"
33 class="timeline-item"
34 :class="{ 'timeline-item-left': index % 2 === 0, 'timeline-item-right': index % 2 !== 0 }"
35 >
36 <div class="timeline-dot"></div>
37 <div class="timeline-content">
38 <div class="event-date">{{ formatDate(event.date) }}</div>
39 <h3 class="event-title">{{ event.title }}</h3>
40 <p class="event-description">{{ event.description }}</p>
41 <!-- 仅管理员可见的删除按钮 -->
42 <button
43 @click="deleteEvent(index)"
44 class="delete-button"
45 v-if="isAuthenticated"
46 >
47 删除
48 </button>
49 </div>
50 </div>
51 </div>
52
53 <!-- 添加新事件的表单(仅管理员可见) -->
54 <div class="add-event-form" v-if="isAuthenticated">
55 <h2>添加新事件</h2>
56 <div class="form-group">
57 <label for="event-date">日期:</label>
58 <input type="date" id="event-date" v-model="newEvent.date">
59 </div>
60 <div class="form-group">
61 <label for="event-title">标题:</label>
62 <input type="text" id="event-title" v-model="newEvent.title" placeholder="输入事件标题">
63 </div>
64 <div class="form-group">
65 <label for="event-description">描述:</label>
66 <textarea id="event-description" v-model="newEvent.description" placeholder="输入事件描述"></textarea>
67 </div>
68 <button @click="addEvent" class="add-button">添加事件</button>
69 <button @click="logout" class="logout-button">退出管理模式</button>
70 </div>
71 </div>
72</template>
73
74<script setup>
75import { ref, computed } from 'vue';
76
77// 管理员密码(请在部署前修改为您自己的密码)
78const ADMIN_PASSWORD = '1234'; // 这里请修改为您自己的密码
79
80// 初始化事件列表,从本地存储加载(如果有)
81const events = ref(JSON.parse(localStorage.getItem('timelineEvents')) || [
82 {
83 date: '2023-01-15',
84 title: '开始新项目',
85 description: '今天开始了我的第一个大型项目,感到既兴奋又紧张。'
86 },
87 {
88 date: '2023-03-22',
89 title: '完成重要里程碑',
90 description: '成功完成了项目的第一个重要里程碑,团队一起庆祝了一下。'
91 },
92 {
93 date: '2023-06-10',
94 title: '参加技术研讨会',
95 description: '今天参加了一个很有意义的技术研讨会,学到了很多新知识。'
96 }
97]);
98
99// 权限控制相关
100const isAuthenticated = ref(false);
101const showAuthForm = ref(false);
102const adminPassword = ref('');
103const authError = ref(false);
104
105// 新事件的数据
106const newEvent = ref({
107 date: '',
108 title: '',
109 description: ''
110});
111
112// 按日期排序事件(从早到晚)
113const sortedEvents = computed(() => {
114 return [...events.value].sort((a, b) => new Date(a.date) - new Date(b.date));
115});
116
117// 验证管理员身份
118const authenticate = () => {
119 if (adminPassword.value === ADMIN_PASSWORD) {
120 isAuthenticated.value = true;
121 showAuthForm.value = false;
122 authError.value = false;
123 adminPassword.value = '';
124 } else {
125 authError.value = true;
126 }
127};
128
129// 退出管理模式
130const logout = () => {
131 isAuthenticated.value = false;
132};
133
134// 添加新事件(仅管理员可执行)
135const addEvent = () => {
136 if (!newEvent.value.date || !newEvent.value.title) {
137 alert('请填写日期和标题');
138 return;
139 }
140
141 events.value.push({...newEvent.value});
142
143 // 保存到本地存储
144 localStorage.setItem('timelineEvents', JSON.stringify(events.value));
145
146 // 重置表单
147 newEvent.value = {
148 date: '',
149 title: '',
150 description: ''
151 };
152};
153
154// 删除事件(仅管理员可执行)
155const deleteEvent = (index) => {
156 if (confirm('确定要删除这个事件吗?')) {
157 events.value.splice(index, 1);
158 localStorage.setItem('timelineEvents', JSON.stringify(events.value));
159 }
160};
161
162// 格式化日期显示
163const formatDate = (dateString) => {
164 const date = new Date(dateString);
165 return date.toLocaleDateString('zh-CN', {
166 year: 'numeric',
167 month: 'long',
168 day: 'numeric'
169 });
170};
171</script>
172
173<style scoped>
174.timeline-container {
175 max-width: 1200px;
176 margin: 0 auto;
177 padding: 20px;
178 font-family: 'Arial', sans-serif;
179 color: #333;
180}
181
182.timeline-title {
183 text-align: center;
184 color: #2c3e50;
185 margin-bottom: 40px;
186 font-size: 2.5rem;
187}
188
189/* 权限验证样式 */
190.auth-form {
191 max-width: 400px;
192 margin: 0 auto 40px;
193 text-align: center;
194 padding: 20px;
195 background-color: #f8f9fa;
196 border-radius: 8px;
197 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
198}
199
200.password-input {
201 width: 80%;
202 padding: 10px;
203 margin-bottom: 15px;
204 border: 1px solid #ddd;
205 border-radius: 4px;
206 font-size: 1rem;
207}
208
209.auth-button {
210 background-color: #3498db;
211 color: white;
212 border: none;
213 padding: 10px 20px;
214 border-radius: 4px;
215 cursor: pointer;
216 font-size: 1rem;
217 transition: background-color 0.3s;
218}
219
220.auth-button:hover {
221 background-color: #2980b9;
222}
223
224.auth-error {
225 color: #e74c3c;
226 margin-top: 10px;
227 font-size: 0.9rem;
228}
229
230.manage-controls {
231 text-align: center;
232 margin-bottom: 30px;
233}
234
235.manage-button {
236 background-color: #f1c40f;
237 color: #333;
238 border: none;
239 padding: 8px 16px;
240 border-radius: 4px;
241 cursor: pointer;
242 font-size: 0.9rem;
243 transition: background-color 0.3s;
244}
245
246.manage-button:hover {
247 background-color: #d4ac0d;
248}
249
250.add-event-form {
251 background-color: #f8f9fa;
252 padding: 25px;
253 border-radius: 8px;
254 margin-top: 50px;
255 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
256}
257
258.add-event-form h2 {
259 margin-top: 0;
260 color: #34495e;
261 margin-bottom: 20px;
262}
263
264.form-group {
265 margin-bottom: 15px;
266}
267
268.form-group label {
269 display: block;
270 margin-bottom: 5px;
271 font-weight: bold;
272 color: #2c3e50;
273}
274
275.form-group input,
276.form-group textarea {
277 width: 100%;
278 padding: 10px;
279 border: 1px solid #ddd;
280 border-radius: 4px;
281 font-size: 1rem;
282}
283
284.form-group textarea {
285 min-height: 100px;
286 resize: vertical;
287}
288
289.add-button {
290 background-color: #3498db;
291 color: white;
292 border: none;
293 padding: 10px 20px;
294 border-radius: 4px;
295 cursor: pointer;
296 font-size: 1rem;
297 transition: background-color 0.3s;
298 margin-right: 10px;
299}
300
301.add-button:hover {
302 background-color: #2980b9;
303}
304
305.logout-button {
306 background-color: #95a5a6;
307 color: white;
308 border: none;
309 padding: 10px 20px;
310 border-radius: 4px;
311 cursor: pointer;
312 font-size: 1rem;
313 transition: background-color 0.3s;
314}
315
316.logout-button:hover {
317 background-color: #7f8c8d;
318}
319
320.timeline-events {
321 position: relative;
322 padding: 20px 0;
323}
324
325/* 时间轴中心线 */
326.timeline-events::before {
327 content: '';
328 position: absolute;
329 top: 0;
330 bottom: 0;
331 left: 50%;
332 width: 4px;
333 background-color: #e0e0e0;
334 transform: translateX(-50%);
335}
336
337.timeline-item {
338 margin-bottom: 40px;
339 position: relative;
340 width: 50%;
341}
342
343.timeline-item-left {
344 left: 0;
345 padding-right: 40px;
346}
347
348.timeline-item-right {
349 left: 50%;
350 padding-left: 40px;
351}
352
353/* 时间点 */
354.timeline-dot {
355 position: absolute;
356 width: 20px;
357 height: 20px;
358 background-color: #3498db;
359 border-radius: 50%;
360 top: 10px;
361 z-index: 1;
362}
363
364.timeline-item-left .timeline-dot {
365 right: -10px;
366}
367
368.timeline-item-right .timeline-dot {
369 left: -10px;
370}
371
372.timeline-content {
373 background-color: white;
374 padding: 20px;
375 border-radius: 8px;
376 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
377 transition: transform 0.3s;
378}
379
380/* 鼠标悬停时轻微上浮效果 */
381.timeline-item:hover .timeline-content {
382 transform: translateY(-5px);
383}
384
385.event-date {
386 color: #7f8c8d;
387 font-size: 0.9rem;
388 margin-bottom: 5px;
389}
390
391.event-title {
392 margin: 0 0 10px 0;
393 color: #2c3e50;
394}
395
396.event-description {
397 margin: 0;
398 color: #34495e;
399 line-height: 1.6;
400 margin-bottom: 10px;
401}
402
403/* 删除按钮样式 */
404.delete-button {
405 background-color: #ff6b6b;
406 color: white;
407 border: none;
408 padding: 5px 10px;
409 border-radius: 4px;
410 cursor: pointer;
411 font-size: 0.9rem;
412 transition: all 0.3s;
413 opacity: 0;
414 transform: translateY(5px);
415}
416
417.timeline-item:hover .delete-button {
418 opacity: 1;
419 transform: translateY(0);
420}
421
422.delete-button:hover {
423 background-color: #ff5252;
424}
425
426.no-events {
427 text-align: center;
428 color: #7f8c8d;
429 padding: 40px;
430 font-size: 1.2rem;
431}
432
433/* 响应式设计 - 适配移动设备 */
434@media (max-width: 768px) {
435 .timeline-events::before {
436 left: 30px;
437 }
438
439 .timeline-item {
440 width: 100%;
441 padding-left: 70px;
442 padding-right: 25px;
443 }
444
445 .timeline-item-left,
446 .timeline-item-right {
447 left: 0;
448 }
449
450 .timeline-item-left .timeline-dot,
451 .timeline-item-right .timeline-dot {
452 left: 20px;
453 }
454
455 .auth-form {
456 width: 90%;
457 }
458
459 .password-input {
460 width: 90%;
461 }
462}
463</style>创建时间:06:38
这个优化版本增加了以下功能:
your_secure_password 替换为您自己的安全密码这种方式提供了基本的权限控制,适合个人博客使用。如果需要更高级的安全控制,可以考虑集成后端用户认证系统。
分享
(1)新建docs\.vitepress\theme\components\Timeline.vue文件
1<template>
2 <div class="timeline-container">
3 <h1 class="timeline-title">我的时间轴</h1>
4
5 <!-- 密码验证区域 -->
6 <div v-if="!isAuthenticated && showAuthForm" class="auth-form">
7 <h2>管理员验证</h2>
8 <input
9 type="password"
10 v-model="adminPassword"
11 placeholder="输入管理员密码"
12 @keyup.enter="authenticate"
13 class="password-input"
14 >
15 <button @click="authenticate" class="auth-button">验证</button>
16 <p v-if="authError" class="auth-error">密码错误,请重试</p>
17 </div>
18
19 <!-- 显示管理按钮(仅管理员可见) -->
20 <div v-if="sortedEvents.length > 0 && !showAuthForm && !isAuthenticated" class="manage-controls">
21 <button @click="showAuthForm = true" class="manage-button">管理时间轴</button>
22 </div>
23
24 <!-- 时间轴事件列表 -->
25 <div class="timeline-events">
26 <div v-if="sortedEvents.length === 0" class="no-events">
27 暂无事件,请添加第一个事件吧!
28 </div>
29
30 <div
31 v-for="(event, index) in sortedEvents"
32 :key="index"
33 class="timeline-item"
34 :class="{ 'timeline-item-left': index % 2 === 0, 'timeline-item-right': index % 2 !== 0 }"
35 >
36 <div class="timeline-dot"></div>
37 <div class="timeline-content">
38 <div class="event-date">{{ formatDate(event.date) }}</div>
39 <h3 class="event-title">{{ event.title }}</h3>
40 <p class="event-description">{{ event.description }}</p>
41 <!-- 仅管理员可见的删除按钮 -->
42 <button
43 @click="deleteEvent(index)"
44 class="delete-button"
45 v-if="isAuthenticated"
46 >
47 删除
48 </button>
49 </div>
50 </div>
51 </div>
52
53 <!-- 添加新事件的表单(仅管理员可见) -->
54 <div class="add-event-form" v-if="isAuthenticated">
55 <h2>添加新事件</h2>
56 <div class="form-group">
57 <label for="event-date">日期:</label>
58 <input type="date" id="event-date" v-model="newEvent.date">
59 </div>
60 <div class="form-group">
61 <label for="event-title">标题:</label>
62 <input type="text" id="event-title" v-model="newEvent.title" placeholder="输入事件标题">
63 </div>
64 <div class="form-group">
65 <label for="event-description">描述:</label>
66 <textarea id="event-description" v-model="newEvent.description" placeholder="输入事件描述"></textarea>
67 </div>
68 <button @click="addEvent" class="add-button">添加事件</button>
69 <button @click="logout" class="logout-button">退出管理模式</button>
70 </div>
71 </div>
72</template>
73
74<script setup>
75import { ref, computed } from 'vue';
76
77// 管理员密码(请在部署前修改为您自己的密码)
78const ADMIN_PASSWORD = '1234'; // 这里请修改为您自己的密码
79
80// 初始化事件列表,从本地存储加载(如果有)
81const events = ref(JSON.parse(localStorage.getItem('timelineEvents')) || [
82 {
83 date: '2023-01-15',
84 title: '开始新项目',
85 description: '今天开始了我的第一个大型项目,感到既兴奋又紧张。'
86 },
87 {
88 date: '2023-03-22',
89 title: '完成重要里程碑',
90 description: '成功完成了项目的第一个重要里程碑,团队一起庆祝了一下。'
91 },
92 {
93 date: '2023-06-10',
94 title: '参加技术研讨会',
95 description: '今天参加了一个很有意义的技术研讨会,学到了很多新知识。'
96 }
97]);
98
99// 权限控制相关
100const isAuthenticated = ref(false);
101const showAuthForm = ref(false);
102const adminPassword = ref('');
103const authError = ref(false);
104
105// 新事件的数据
106const newEvent = ref({
107 date: '',
108 title: '',
109 description: ''
110});
111
112// 按日期排序事件(从早到晚)
113const sortedEvents = computed(() => {
114 return [...events.value].sort((a, b) => new Date(a.date) - new Date(b.date));
115});
116
117// 验证管理员身份
118const authenticate = () => {
119 if (adminPassword.value === ADMIN_PASSWORD) {
120 isAuthenticated.value = true;
121 showAuthForm.value = false;
122 authError.value = false;
123 adminPassword.value = '';
124 } else {
125 authError.value = true;
126 }
127};
128
129// 退出管理模式
130const logout = () => {
131 isAuthenticated.value = false;
132};
133
134// 添加新事件(仅管理员可执行)
135const addEvent = () => {
136 if (!newEvent.value.date || !newEvent.value.title) {
137 alert('请填写日期和标题');
138 return;
139 }
140
141 events.value.push({...newEvent.value});
142
143 // 保存到本地存储
144 localStorage.setItem('timelineEvents', JSON.stringify(events.value));
145
146 // 重置表单
147 newEvent.value = {
148 date: '',
149 title: '',
150 description: ''
151 };
152};
153
154// 删除事件(仅管理员可执行)
155const deleteEvent = (index) => {
156 if (confirm('确定要删除这个事件吗?')) {
157 events.value.splice(index, 1);
158 localStorage.setItem('timelineEvents', JSON.stringify(events.value));
159 }
160};
161
162// 格式化日期显示
163const formatDate = (dateString) => {
164 const date = new Date(dateString);
165 return date.toLocaleDateString('zh-CN', {
166 year: 'numeric',
167 month: 'long',
168 day: 'numeric'
169 });
170};
171</script>
172
173<style scoped>
174.timeline-container {
175 max-width: 1200px;
176 margin: 0 auto;
177 padding: 20px;
178 font-family: 'Arial', sans-serif;
179 color: #333;
180}
181
182.timeline-title {
183 text-align: center;
184 color: #2c3e50;
185 margin-bottom: 40px;
186 font-size: 2.5rem;
187}
188
189/* 权限验证样式 */
190.auth-form {
191 max-width: 400px;
192 margin: 0 auto 40px;
193 text-align: center;
194 padding: 20px;
195 background-color: #f8f9fa;
196 border-radius: 8px;
197 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
198}
199
200.password-input {
201 width: 80%;
202 padding: 10px;
203 margin-bottom: 15px;
204 border: 1px solid #ddd;
205 border-radius: 4px;
206 font-size: 1rem;
207}
208
209.auth-button {
210 background-color: #3498db;
211 color: white;
212 border: none;
213 padding: 10px 20px;
214 border-radius: 4px;
215 cursor: pointer;
216 font-size: 1rem;
217 transition: background-color 0.3s;
218}
219
220.auth-button:hover {
221 background-color: #2980b9;
222}
223
224.auth-error {
225 color: #e74c3c;
226 margin-top: 10px;
227 font-size: 0.9rem;
228}
229
230.manage-controls {
231 text-align: center;
232 margin-bottom: 30px;
233}
234
235.manage-button {
236 background-color: #f1c40f;
237 color: #333;
238 border: none;
239 padding: 8px 16px;
240 border-radius: 4px;
241 cursor: pointer;
242 font-size: 0.9rem;
243 transition: background-color 0.3s;
244}
245
246.manage-button:hover {
247 background-color: #d4ac0d;
248}
249
250.add-event-form {
251 background-color: #f8f9fa;
252 padding: 25px;
253 border-radius: 8px;
254 margin-top: 50px;
255 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
256}
257
258.add-event-form h2 {
259 margin-top: 0;
260 color: #34495e;
261 margin-bottom: 20px;
262}
263
264.form-group {
265 margin-bottom: 15px;
266}
267
268.form-group label {
269 display: block;
270 margin-bottom: 5px;
271 font-weight: bold;
272 color: #2c3e50;
273}
274
275.form-group input,
276.form-group textarea {
277 width: 100%;
278 padding: 10px;
279 border: 1px solid #ddd;
280 border-radius: 4px;
281 font-size: 1rem;
282}
283
284.form-group textarea {
285 min-height: 100px;
286 resize: vertical;
287}
288
289.add-button {
290 background-color: #3498db;
291 color: white;
292 border: none;
293 padding: 10px 20px;
294 border-radius: 4px;
295 cursor: pointer;
296 font-size: 1rem;
297 transition: background-color 0.3s;
298 margin-right: 10px;
299}
300
301.add-button:hover {
302 background-color: #2980b9;
303}
304
305.logout-button {
306 background-color: #95a5a6;
307 color: white;
308 border: none;
309 padding: 10px 20px;
310 border-radius: 4px;
311 cursor: pointer;
312 font-size: 1rem;
313 transition: background-color 0.3s;
314}
315
316.logout-button:hover {
317 background-color: #7f8c8d;
318}
319
320.timeline-events {
321 position: relative;
322 padding: 20px 0;
323}
324
325/* 时间轴中心线 */
326.timeline-events::before {
327 content: '';
328 position: absolute;
329 top: 0;
330 bottom: 0;
331 left: 50%;
332 width: 4px;
333 background-color: #e0e0e0;
334 transform: translateX(-50%);
335}
336
337.timeline-item {
338 margin-bottom: 40px;
339 position: relative;
340 width: 50%;
341}
342
343.timeline-item-left {
344 left: 0;
345 padding-right: 40px;
346}
347
348.timeline-item-right {
349 left: 50%;
350 padding-left: 40px;
351}
352
353/* 时间点 */
354.timeline-dot {
355 position: absolute;
356 width: 20px;
357 height: 20px;
358 background-color: #3498db;
359 border-radius: 50%;
360 top: 10px;
361 z-index: 1;
362}
363
364.timeline-item-left .timeline-dot {
365 right: -10px;
366}
367
368.timeline-item-right .timeline-dot {
369 left: -10px;
370}
371
372.timeline-content {
373 background-color: white;
374 padding: 20px;
375 border-radius: 8px;
376 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
377 transition: transform 0.3s;
378}
379
380/* 鼠标悬停时轻微上浮效果 */
381.timeline-item:hover .timeline-content {
382 transform: translateY(-5px);
383}
384
385.event-date {
386 color: #7f8c8d;
387 font-size: 0.9rem;
388 margin-bottom: 5px;
389}
390
391.event-title {
392 margin: 0 0 10px 0;
393 color: #2c3e50;
394}
395
396.event-description {
397 margin: 0;
398 color: #34495e;
399 line-height: 1.6;
400 margin-bottom: 10px;
401}
402
403/* 删除按钮样式 */
404.delete-button {
405 background-color: #ff6b6b;
406 color: white;
407 border: none;
408 padding: 5px 10px;
409 border-radius: 4px;
410 cursor: pointer;
411 font-size: 0.9rem;
412 transition: all 0.3s;
413 opacity: 0;
414 transform: translateY(5px);
415}
416
417.timeline-item:hover .delete-button {
418 opacity: 1;
419 transform: translateY(0);
420}
421
422.delete-button:hover {
423 background-color: #ff5252;
424}
425
426.no-events {
427 text-align: center;
428 color: #7f8c8d;
429 padding: 40px;
430 font-size: 1.2rem;
431}
432
433/* 响应式设计 - 适配移动设备 */
434@media (max-width: 768px) {
435 .timeline-events::before {
436 left: 30px;
437 }
438
439 .timeline-item {
440 width: 100%;
441 padding-left: 70px;
442 padding-right: 25px;
443 }
444
445 .timeline-item-left,
446 .timeline-item-right {
447 left: 0;
448 }
449
450 .timeline-item-left .timeline-dot,
451 .timeline-item-right .timeline-dot {
452 left: 20px;
453 }
454
455 .auth-form {
456 width: 90%;
457 }
458
459 .password-input {
460 width: 90%;
461 }
462}
463</style>(2)新建``文件
(3)测试
见下方。
结束。
日历 · 精选 · 友链 · 更多
如果内容对你有帮助,欢迎请我喝杯咖啡 ☕



One的公众号
爱折腾博客的小白