时间轴-v2
时间轴-v2
问题
请帮我生成一个 时间轴 前端页面(主要用于记录自己一些关键日期发生的事件)。
另外,请帮我生成 一个 vue3版本的组件,不要使用Tailwind CSS,样式直接使用普通css写在组件内。
v1-2025.9.13-豆包生成
豆包输出
我将为您创建一个 Vue3 版本的时间轴组件,用于记录关键日期发生的事件。这个组件将包含事件展示、添加新事件和删除事件的功能,所有样式将使用普通 CSS 编写在组件内部。
Vue3时间轴组件
V1 Timeline.vue
<template>
<div class="timeline-container">
<h1 class="timeline-title">我的时间轴</h1>
<!-- 添加新事件的表单 -->
<div class="add-event-form">
<h2>添加新事件</h2>
<div class="form-group">
<label for="event-date">日期:</label>
<input type="date" id="event-date" v-model="newEvent.date">
</div>
<div class="form-group">
<label for="event-title">标题:</label>
<input type="text" id="event-title" v-model="newEvent.title" placeholder="输入事件标题">
</div>
<div class="form-group">
<label for="event-description">描述:</label>
<textarea id="event-description" v-model="newEvent.description" placeholder="输入事件描述"></textarea>
</div>
<button @click="addEvent" class="add-button">添加事件</button>
</div>
<!-- 时间轴事件列表 -->
<div class="timeline-events">
<div v-if="sortedEvents.length === 0" class="no-events">
暂无事件,请添加第一个事件吧!
</div>
<div
v-for="(event, index) in sortedEvents"
:key="index"
class="timeline-item"
:class="{ 'timeline-item-left': index % 2 === 0, 'timeline-item-right': index % 2 !== 0 }"
>
<div class="timeline-dot"></div>
<div class="timeline-content">
<div class="event-date">{{ formatDate(event.date) }}</div>
<h3 class="event-title">{{ event.title }}</h3>
<p class="event-description">{{ event.description }}</p>
<button @click="deleteEvent(index)" class="delete-button">删除</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
// 初始化事件列表,从本地存储加载(如果有)
const events = ref(JSON.parse(localStorage.getItem('timelineEvents')) || [
{
date: '2023-01-15',
title: '开始新项目',
description: '今天开始了我的第一个大型项目,感到既兴奋又紧张。'
},
{
date: '2023-03-22',
title: '完成重要里程碑',
description: '成功完成了项目的第一个重要里程碑,团队一起庆祝了一下。'
},
{
date: '2023-06-10',
title: '参加技术研讨会',
description: '今天参加了一个很有意义的技术研讨会,学到了很多新知识。'
}
]);
// 新事件的数据
const newEvent = ref({
date: '',
title: '',
description: ''
});
// 按日期排序事件(从早到晚)
const sortedEvents = computed(() => {
return [...events.value].sort((a, b) => new Date(a.date) - new Date(b.date));
});
// 添加新事件
const addEvent = () => {
if (!newEvent.value.date || !newEvent.value.title) {
alert('请填写日期和标题');
return;
}
events.value.push({...newEvent.value});
// 保存到本地存储,防止页面刷新后数据丢失
localStorage.setItem('timelineEvents', JSON.stringify(events.value));
// 重置表单
newEvent.value = {
date: '',
title: '',
description: ''
};
};
// 删除事件
const deleteEvent = (index) => {
if (confirm('确定要删除这个事件吗?')) {
events.value.splice(index, 1);
localStorage.setItem('timelineEvents', JSON.stringify(events.value));
}
};
// 格式化日期显示
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
</script>
<style scoped>
.timeline-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: 'Arial', sans-serif;
color: #333;
}
.timeline-title {
text-align: center;
color: #2c3e50;
margin-bottom: 40px;
font-size: 2.5rem;
}
.add-event-form {
background-color: #f8f9fa;
padding: 25px;
border-radius: 8px;
margin-bottom: 50px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.add-event-form h2 {
margin-top: 0;
color: #34495e;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #2c3e50;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.add-button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
}
.add-button:hover {
background-color: #2980b9;
}
.timeline-events {
position: relative;
padding: 20px 0;
}
/* 时间轴中心线 */
.timeline-events::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 4px;
background-color: #e0e0e0;
transform: translateX(-50%);
}
.timeline-item {
margin-bottom: 40px;
position: relative;
width: 50%;
}
.timeline-item-left {
left: 0;
padding-right: 40px;
}
.timeline-item-right {
left: 50%;
padding-left: 40px;
}
/* 时间点 */
.timeline-dot {
position: absolute;
width: 20px;
height: 20px;
background-color: #3498db;
border-radius: 50%;
top: 10px;
z-index: 1;
}
.timeline-item-left .timeline-dot {
right: -10px;
}
.timeline-item-right .timeline-dot {
left: -10px;
}
.timeline-content {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.event-date {
color: #7f8c8d;
font-size: 0.9rem;
margin-bottom: 5px;
}
.event-title {
margin: 0 0 10px 0;
color: #2c3e50;
}
.event-description {
margin: 0;
color: #34495e;
line-height: 1.6;
}
.delete-button {
background-color: #e74c3c;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
margin-top: 10px;
transition: background-color 0.3s;
}
.delete-button:hover {
background-color: #c0392b;
}
.no-events {
text-align: center;
color: #7f8c8d;
padding: 40px;
font-size: 1.2rem;
}
/* 响应式设计 - 适配移动设备 */
@media (max-width: 768px) {
.timeline-events::before {
left: 30px;
}
.timeline-item {
width: 100%;
padding-left: 70px;
padding-right: 25px;
}
.timeline-item-left,
.timeline-item-right {
left: 0;
}
.timeline-item-left .timeline-dot,
.timeline-item-right .timeline-dot {
left: 20px;
}
}
</style>
创建时间:06:22
这个时间轴组件具有以下特点:
- 完整的功能:
- 展示按时间顺序排列的事件
- 支持添加新事件(包含日期、标题和描述)
- 可以删除已有事件
- 使用 localStorage 保存数据,页面刷新后不会丢失
- 视觉设计:
- 采用左右交替的布局,使时间轴更加美观
- 中间有一条垂直线作为时间轴的主线
- 每个事件都有一个圆点标记在时间轴上
- 包含适当的阴影和圆角,增强视觉层次感
- 响应式布局:
- 在大屏幕上使用左右交替布局
- 在移动设备上自动调整为单列布局
- 适配不同尺寸的屏幕
- 用户体验:
- 表单验证确保日期和标题不为空
- 删除前有确认提示
- 按钮有悬停效果
- 日期显示格式友好(例如:2023 年 1 月 15 日)
您可以直接使用这个组件,也可以根据需要调整样式或添加更多功能,如编辑事件、搜索事件等。
效果测试
存在问题
问题1:这个 添加新事件 在最前面,显得不太好看
问题2:每个事件都有一个 红色的删除按钮,也显得很不好看
v2-2025.9.13-豆包生成-解决2个存在的问题
提出问题
存在如下2个问题:
问题1:这个 添加新事件 在最前面,显得不太好看
问题2:每个事件都有一个 红色的删除按钮,也显得很不好看
请帮我按上面提出的2个问题 继续 优化下这个代码
豆包生成
我根据您提出的两个问题进行了优化:
- 将 "添加新事件" 表单移动到了页面底部,使时间轴内容更加突出
- 改进了删除按钮的样式,使其平时隐藏,鼠标悬停在事件上时才显示,并且采用了更柔和的红色
优化后的Vue3时间轴组件
V2
<template>
<div class="timeline-container">
<h1 class="timeline-title">我的时间轴</h1>
<!-- 时间轴事件列表 -->
<div class="timeline-events">
<div v-if="sortedEvents.length === 0" class="no-events">
暂无事件,请添加第一个事件吧!
</div>
<div
v-for="(event, index) in sortedEvents"
:key="index"
class="timeline-item"
:class="{ 'timeline-item-left': index % 2 === 0, 'timeline-item-right': index % 2 !== 0 }"
>
<div class="timeline-dot"></div>
<div class="timeline-content">
<div class="event-date">{{ formatDate(event.date) }}</div>
<h3 class="event-title">{{ event.title }}</h3>
<p class="event-description">{{ event.description }}</p>
<button @click="deleteEvent(index)" class="delete-button">删除</button>
</div>
</div>
</div>
<!-- 添加新事件的表单(移到了页面底部) -->
<div class="add-event-form">
<h2>添加新事件</h2>
<div class="form-group">
<label for="event-date">日期:</label>
<input type="date" id="event-date" v-model="newEvent.date">
</div>
<div class="form-group">
<label for="event-title">标题:</label>
<input type="text" id="event-title" v-model="newEvent.title" placeholder="输入事件标题">
</div>
<div class="form-group">
<label for="event-description">描述:</label>
<textarea id="event-description" v-model="newEvent.description" placeholder="输入事件描述"></textarea>
</div>
<button @click="addEvent" class="add-button">添加事件</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
// 初始化事件列表,从本地存储加载(如果有)
const events = ref(JSON.parse(localStorage.getItem('timelineEvents')) || [
{
date: '2023-01-15',
title: '开始新项目',
description: '今天开始了我的第一个大型项目,感到既兴奋又紧张。'
},
{
date: '2023-03-22',
title: '完成重要里程碑',
description: '成功完成了项目的第一个重要里程碑,团队一起庆祝了一下。'
},
{
date: '2023-06-10',
title: '参加技术研讨会',
description: '今天参加了一个很有意义的技术研讨会,学到了很多新知识。'
}
]);
// 新事件的数据
const newEvent = ref({
date: '',
title: '',
description: ''
});
// 按日期排序事件(从早到晚)
const sortedEvents = computed(() => {
return [...events.value].sort((a, b) => new Date(a.date) - new Date(b.date));
});
// 添加新事件
const addEvent = () => {
if (!newEvent.value.date || !newEvent.value.title) {
alert('请填写日期和标题');
return;
}
events.value.push({...newEvent.value});
// 保存到本地存储,防止页面刷新后数据丢失
localStorage.setItem('timelineEvents', JSON.stringify(events.value));
// 重置表单
newEvent.value = {
date: '',
title: '',
description: ''
};
};
// 删除事件
const deleteEvent = (index) => {
if (confirm('确定要删除这个事件吗?')) {
events.value.splice(index, 1);
localStorage.setItem('timelineEvents', JSON.stringify(events.value));
}
};
// 格式化日期显示
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
</script>
<style scoped>
.timeline-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: 'Arial', sans-serif;
color: #333;
}
.timeline-title {
text-align: center;
color: #2c3e50;
margin-bottom: 40px;
font-size: 2.5rem;
}
.add-event-form {
background-color: #f8f9fa;
padding: 25px;
border-radius: 8px;
margin-top: 50px; /* 调整为顶部margin,因为移到了底部 */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.add-event-form h2 {
margin-top: 0;
color: #34495e;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #2c3e50;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.add-button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
}
.add-button:hover {
background-color: #2980b9;
}
.timeline-events {
position: relative;
padding: 20px 0;
}
/* 时间轴中心线 */
.timeline-events::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 4px;
background-color: #e0e0e0;
transform: translateX(-50%);
}
.timeline-item {
margin-bottom: 40px;
position: relative;
width: 50%;
}
.timeline-item-left {
left: 0;
padding-right: 40px;
}
.timeline-item-right {
left: 50%;
padding-left: 40px;
}
/* 时间点 */
.timeline-dot {
position: absolute;
width: 20px;
height: 20px;
background-color: #3498db;
border-radius: 50%;
top: 10px;
z-index: 1;
}
.timeline-item-left .timeline-dot {
right: -10px;
}
.timeline-item-right .timeline-dot {
left: -10px;
}
.timeline-content {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.3s;
}
/* 鼠标悬停时轻微上浮效果 */
.timeline-item:hover .timeline-content {
transform: translateY(-5px);
}
.event-date {
color: #7f8c8d;
font-size: 0.9rem;
margin-bottom: 5px;
}
.event-title {
margin: 0 0 10px 0;
color: #2c3e50;
}
.event-description {
margin: 0;
color: #34495e;
line-height: 1.6;
margin-bottom: 10px;
}
/* 改进的删除按钮样式 */
.delete-button {
background-color: #ff6b6b;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s;
opacity: 0; /* 默认隐藏 */
transform: translateY(5px);
}
/* 鼠标悬停在事件上时显示删除按钮 */
.timeline-item:hover .delete-button {
opacity: 1;
transform: translateY(0);
}
.delete-button:hover {
background-color: #ff5252;
}
.no-events {
text-align: center;
color: #7f8c8d;
padding: 40px;
font-size: 1.2rem;
}
/* 响应式设计 - 适配移动设备 */
@media (max-width: 768px) {
.timeline-events::before {
left: 30px;
}
.timeline-item {
width: 100%;
padding-left: 70px;
padding-right: 25px;
}
.timeline-item-left,
.timeline-item-right {
left: 0;
}
.timeline-item-left .timeline-dot,
.timeline-item-right .timeline-dot {
left: 20px;
}
}
</style>
创建时间:06:32
除了您提出的两个主要优化点外,我还增加了一个小细节:当鼠标悬停在事件卡片上时,卡片会有一个轻微上浮的效果,增强了交互感。
这些改进使得整个时间轴更加整洁,删除操作不再干扰正常浏览,同时添加新事件的功能也不会喧宾夺主,而是作为一个辅助功能放在页面底部。
效果测试
存在问题
问题1:这个部署到自己博客后,其它用户是不是也能直接删除这个事件呢?或者 也能新增新事件呢? 正常情况,我不需要其他用户具有这个修改的权限,他们只需要查看的权限。 修改的权限只能是我自己。请帮我继续优化下,输出完整的代码。
v3-2025.9.13-豆包生成-带权限控制的Vue3时间轴组件(正在使用💖)
提出问题
这个部署到自己博客后,其它用户是不是也能直接删除这个事件呢?或者 也能新增新事件呢? 正常情况,我不需要其他用户具有这个修改的权限,他们只需要查看的权限。 修改的权限只能是我自己。请帮我继续优化下,输出完整的代码。
豆包生成
为了解决权限控制的问题,我可以添加一个简单的密码验证机制,只有输入正确密码的用户(也就是您自己)才能看到并使用添加和删除功能,其他用户只能查看时间轴内容。
带权限控制的Vue3时间轴组件
V3
<template>
<div class="timeline-container">
<h1 class="timeline-title">我的时间轴</h1>
<!-- 密码验证区域 -->
<div v-if="!isAuthenticated && showAuthForm" class="auth-form">
<h2>管理员验证</h2>
<input
type="password"
v-model="adminPassword"
placeholder="输入管理员密码"
@keyup.enter="authenticate"
class="password-input"
>
<button @click="authenticate" class="auth-button">验证</button>
<p v-if="authError" class="auth-error">密码错误,请重试</p>
</div>
<!-- 显示管理按钮(仅管理员可见) -->
<div v-if="sortedEvents.length > 0 && !showAuthForm && !isAuthenticated" class="manage-controls">
<button @click="showAuthForm = true" class="manage-button">管理时间轴</button>
</div>
<!-- 时间轴事件列表 -->
<div class="timeline-events">
<div v-if="sortedEvents.length === 0" class="no-events">
暂无事件,请添加第一个事件吧!
</div>
<div
v-for="(event, index) in sortedEvents"
:key="index"
class="timeline-item"
:class="{ 'timeline-item-left': index % 2 === 0, 'timeline-item-right': index % 2 !== 0 }"
>
<div class="timeline-dot"></div>
<div class="timeline-content">
<div class="event-date">{{ formatDate(event.date) }}</div>
<h3 class="event-title">{{ event.title }}</h3>
<p class="event-description">{{ event.description }}</p>
<!-- 仅管理员可见的删除按钮 -->
<button
@click="deleteEvent(index)"
class="delete-button"
v-if="isAuthenticated"
>
删除
</button>
</div>
</div>
</div>
<!-- 添加新事件的表单(仅管理员可见) -->
<div class="add-event-form" v-if="isAuthenticated">
<h2>添加新事件</h2>
<div class="form-group">
<label for="event-date">日期:</label>
<input type="date" id="event-date" v-model="newEvent.date">
</div>
<div class="form-group">
<label for="event-title">标题:</label>
<input type="text" id="event-title" v-model="newEvent.title" placeholder="输入事件标题">
</div>
<div class="form-group">
<label for="event-description">描述:</label>
<textarea id="event-description" v-model="newEvent.description" placeholder="输入事件描述"></textarea>
</div>
<button @click="addEvent" class="add-button">添加事件</button>
<button @click="logout" class="logout-button">退出管理模式</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
// 管理员密码(请在部署前修改为您自己的密码)
const ADMIN_PASSWORD = '1234'; // 这里请修改为您自己的密码
// 初始化事件列表,从本地存储加载(如果有)
const events = ref(JSON.parse(localStorage.getItem('timelineEvents')) || [
{
date: '2023-01-15',
title: '开始新项目',
description: '今天开始了我的第一个大型项目,感到既兴奋又紧张。'
},
{
date: '2023-03-22',
title: '完成重要里程碑',
description: '成功完成了项目的第一个重要里程碑,团队一起庆祝了一下。'
},
{
date: '2023-06-10',
title: '参加技术研讨会',
description: '今天参加了一个很有意义的技术研讨会,学到了很多新知识。'
}
]);
// 权限控制相关
const isAuthenticated = ref(false);
const showAuthForm = ref(false);
const adminPassword = ref('');
const authError = ref(false);
// 新事件的数据
const newEvent = ref({
date: '',
title: '',
description: ''
});
// 按日期排序事件(从早到晚)
const sortedEvents = computed(() => {
return [...events.value].sort((a, b) => new Date(a.date) - new Date(b.date));
});
// 验证管理员身份
const authenticate = () => {
if (adminPassword.value === ADMIN_PASSWORD) {
isAuthenticated.value = true;
showAuthForm.value = false;
authError.value = false;
adminPassword.value = '';
} else {
authError.value = true;
}
};
// 退出管理模式
const logout = () => {
isAuthenticated.value = false;
};
// 添加新事件(仅管理员可执行)
const addEvent = () => {
if (!newEvent.value.date || !newEvent.value.title) {
alert('请填写日期和标题');
return;
}
events.value.push({...newEvent.value});
// 保存到本地存储
localStorage.setItem('timelineEvents', JSON.stringify(events.value));
// 重置表单
newEvent.value = {
date: '',
title: '',
description: ''
};
};
// 删除事件(仅管理员可执行)
const deleteEvent = (index) => {
if (confirm('确定要删除这个事件吗?')) {
events.value.splice(index, 1);
localStorage.setItem('timelineEvents', JSON.stringify(events.value));
}
};
// 格式化日期显示
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
</script>
<style scoped>
.timeline-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: 'Arial', sans-serif;
color: #333;
}
.timeline-title {
text-align: center;
color: #2c3e50;
margin-bottom: 40px;
font-size: 2.5rem;
}
/* 权限验证样式 */
.auth-form {
max-width: 400px;
margin: 0 auto 40px;
text-align: center;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.password-input {
width: 80%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.auth-button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
}
.auth-button:hover {
background-color: #2980b9;
}
.auth-error {
color: #e74c3c;
margin-top: 10px;
font-size: 0.9rem;
}
.manage-controls {
text-align: center;
margin-bottom: 30px;
}
.manage-button {
background-color: #f1c40f;
color: #333;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: background-color 0.3s;
}
.manage-button:hover {
background-color: #d4ac0d;
}
.add-event-form {
background-color: #f8f9fa;
padding: 25px;
border-radius: 8px;
margin-top: 50px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.add-event-form h2 {
margin-top: 0;
color: #34495e;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #2c3e50;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.add-button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
margin-right: 10px;
}
.add-button:hover {
background-color: #2980b9;
}
.logout-button {
background-color: #95a5a6;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
}
.logout-button:hover {
background-color: #7f8c8d;
}
.timeline-events {
position: relative;
padding: 20px 0;
}
/* 时间轴中心线 */
.timeline-events::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 4px;
background-color: #e0e0e0;
transform: translateX(-50%);
}
.timeline-item {
margin-bottom: 40px;
position: relative;
width: 50%;
}
.timeline-item-left {
left: 0;
padding-right: 40px;
}
.timeline-item-right {
left: 50%;
padding-left: 40px;
}
/* 时间点 */
.timeline-dot {
position: absolute;
width: 20px;
height: 20px;
background-color: #3498db;
border-radius: 50%;
top: 10px;
z-index: 1;
}
.timeline-item-left .timeline-dot {
right: -10px;
}
.timeline-item-right .timeline-dot {
left: -10px;
}
.timeline-content {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.3s;
}
/* 鼠标悬停时轻微上浮效果 */
.timeline-item:hover .timeline-content {
transform: translateY(-5px);
}
.event-date {
color: #7f8c8d;
font-size: 0.9rem;
margin-bottom: 5px;
}
.event-title {
margin: 0 0 10px 0;
color: #2c3e50;
}
.event-description {
margin: 0;
color: #34495e;
line-height: 1.6;
margin-bottom: 10px;
}
/* 删除按钮样式 */
.delete-button {
background-color: #ff6b6b;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s;
opacity: 0;
transform: translateY(5px);
}
.timeline-item:hover .delete-button {
opacity: 1;
transform: translateY(0);
}
.delete-button:hover {
background-color: #ff5252;
}
.no-events {
text-align: center;
color: #7f8c8d;
padding: 40px;
font-size: 1.2rem;
}
/* 响应式设计 - 适配移动设备 */
@media (max-width: 768px) {
.timeline-events::before {
left: 30px;
}
.timeline-item {
width: 100%;
padding-left: 70px;
padding-right: 25px;
}
.timeline-item-left,
.timeline-item-right {
left: 0;
}
.timeline-item-left .timeline-dot,
.timeline-item-right .timeline-dot {
left: 20px;
}
.auth-form {
width: 90%;
}
.password-input {
width: 90%;
}
}
</style>
创建时间:06:38
这个优化版本增加了以下功能:
- 权限控制系统:
- 普通访客只能查看时间轴事件,看不到添加和删除按钮
- 只有输入正确密码的用户(管理员)才能看到并使用添加和删除功能
- 管理员可以退出管理模式,回到普通查看模式
- 使用说明:
- 请务必将代码中的
your_secure_password
替换为您自己的安全密码 - 部署后,其他用户只能浏览时间轴内容,无法修改
- 您自己可以通过点击 "管理时间轴" 按钮,输入密码后进入管理模式
- 请务必将代码中的
- 用户体验优化:
- 密码输入区域有错误提示
- 支持按 Enter 键提交密码
- 管理模式下有退出按钮
这种方式提供了基本的权限控制,适合个人博客使用。如果需要更高级的安全控制,可以考虑集成后端用户认证系统。
分享
Tee接入
(1)新建docs\.vitepress\theme\components\Timeline.vue
文件
<template>
<div class="timeline-container">
<h1 class="timeline-title">我的时间轴</h1>
<!-- 密码验证区域 -->
<div v-if="!isAuthenticated && showAuthForm" class="auth-form">
<h2>管理员验证</h2>
<input
type="password"
v-model="adminPassword"
placeholder="输入管理员密码"
@keyup.enter="authenticate"
class="password-input"
>
<button @click="authenticate" class="auth-button">验证</button>
<p v-if="authError" class="auth-error">密码错误,请重试</p>
</div>
<!-- 显示管理按钮(仅管理员可见) -->
<div v-if="sortedEvents.length > 0 && !showAuthForm && !isAuthenticated" class="manage-controls">
<button @click="showAuthForm = true" class="manage-button">管理时间轴</button>
</div>
<!-- 时间轴事件列表 -->
<div class="timeline-events">
<div v-if="sortedEvents.length === 0" class="no-events">
暂无事件,请添加第一个事件吧!
</div>
<div
v-for="(event, index) in sortedEvents"
:key="index"
class="timeline-item"
:class="{ 'timeline-item-left': index % 2 === 0, 'timeline-item-right': index % 2 !== 0 }"
>
<div class="timeline-dot"></div>
<div class="timeline-content">
<div class="event-date">{{ formatDate(event.date) }}</div>
<h3 class="event-title">{{ event.title }}</h3>
<p class="event-description">{{ event.description }}</p>
<!-- 仅管理员可见的删除按钮 -->
<button
@click="deleteEvent(index)"
class="delete-button"
v-if="isAuthenticated"
>
删除
</button>
</div>
</div>
</div>
<!-- 添加新事件的表单(仅管理员可见) -->
<div class="add-event-form" v-if="isAuthenticated">
<h2>添加新事件</h2>
<div class="form-group">
<label for="event-date">日期:</label>
<input type="date" id="event-date" v-model="newEvent.date">
</div>
<div class="form-group">
<label for="event-title">标题:</label>
<input type="text" id="event-title" v-model="newEvent.title" placeholder="输入事件标题">
</div>
<div class="form-group">
<label for="event-description">描述:</label>
<textarea id="event-description" v-model="newEvent.description" placeholder="输入事件描述"></textarea>
</div>
<button @click="addEvent" class="add-button">添加事件</button>
<button @click="logout" class="logout-button">退出管理模式</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
// 管理员密码(请在部署前修改为您自己的密码)
const ADMIN_PASSWORD = '1234'; // 这里请修改为您自己的密码
// 初始化事件列表,从本地存储加载(如果有)
const events = ref(JSON.parse(localStorage.getItem('timelineEvents')) || [
{
date: '2023-01-15',
title: '开始新项目',
description: '今天开始了我的第一个大型项目,感到既兴奋又紧张。'
},
{
date: '2023-03-22',
title: '完成重要里程碑',
description: '成功完成了项目的第一个重要里程碑,团队一起庆祝了一下。'
},
{
date: '2023-06-10',
title: '参加技术研讨会',
description: '今天参加了一个很有意义的技术研讨会,学到了很多新知识。'
}
]);
// 权限控制相关
const isAuthenticated = ref(false);
const showAuthForm = ref(false);
const adminPassword = ref('');
const authError = ref(false);
// 新事件的数据
const newEvent = ref({
date: '',
title: '',
description: ''
});
// 按日期排序事件(从早到晚)
const sortedEvents = computed(() => {
return [...events.value].sort((a, b) => new Date(a.date) - new Date(b.date));
});
// 验证管理员身份
const authenticate = () => {
if (adminPassword.value === ADMIN_PASSWORD) {
isAuthenticated.value = true;
showAuthForm.value = false;
authError.value = false;
adminPassword.value = '';
} else {
authError.value = true;
}
};
// 退出管理模式
const logout = () => {
isAuthenticated.value = false;
};
// 添加新事件(仅管理员可执行)
const addEvent = () => {
if (!newEvent.value.date || !newEvent.value.title) {
alert('请填写日期和标题');
return;
}
events.value.push({...newEvent.value});
// 保存到本地存储
localStorage.setItem('timelineEvents', JSON.stringify(events.value));
// 重置表单
newEvent.value = {
date: '',
title: '',
description: ''
};
};
// 删除事件(仅管理员可执行)
const deleteEvent = (index) => {
if (confirm('确定要删除这个事件吗?')) {
events.value.splice(index, 1);
localStorage.setItem('timelineEvents', JSON.stringify(events.value));
}
};
// 格式化日期显示
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
</script>
<style scoped>
.timeline-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: 'Arial', sans-serif;
color: #333;
}
.timeline-title {
text-align: center;
color: #2c3e50;
margin-bottom: 40px;
font-size: 2.5rem;
}
/* 权限验证样式 */
.auth-form {
max-width: 400px;
margin: 0 auto 40px;
text-align: center;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.password-input {
width: 80%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.auth-button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
}
.auth-button:hover {
background-color: #2980b9;
}
.auth-error {
color: #e74c3c;
margin-top: 10px;
font-size: 0.9rem;
}
.manage-controls {
text-align: center;
margin-bottom: 30px;
}
.manage-button {
background-color: #f1c40f;
color: #333;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: background-color 0.3s;
}
.manage-button:hover {
background-color: #d4ac0d;
}
.add-event-form {
background-color: #f8f9fa;
padding: 25px;
border-radius: 8px;
margin-top: 50px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.add-event-form h2 {
margin-top: 0;
color: #34495e;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #2c3e50;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.add-button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
margin-right: 10px;
}
.add-button:hover {
background-color: #2980b9;
}
.logout-button {
background-color: #95a5a6;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
}
.logout-button:hover {
background-color: #7f8c8d;
}
.timeline-events {
position: relative;
padding: 20px 0;
}
/* 时间轴中心线 */
.timeline-events::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 4px;
background-color: #e0e0e0;
transform: translateX(-50%);
}
.timeline-item {
margin-bottom: 40px;
position: relative;
width: 50%;
}
.timeline-item-left {
left: 0;
padding-right: 40px;
}
.timeline-item-right {
left: 50%;
padding-left: 40px;
}
/* 时间点 */
.timeline-dot {
position: absolute;
width: 20px;
height: 20px;
background-color: #3498db;
border-radius: 50%;
top: 10px;
z-index: 1;
}
.timeline-item-left .timeline-dot {
right: -10px;
}
.timeline-item-right .timeline-dot {
left: -10px;
}
.timeline-content {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.3s;
}
/* 鼠标悬停时轻微上浮效果 */
.timeline-item:hover .timeline-content {
transform: translateY(-5px);
}
.event-date {
color: #7f8c8d;
font-size: 0.9rem;
margin-bottom: 5px;
}
.event-title {
margin: 0 0 10px 0;
color: #2c3e50;
}
.event-description {
margin: 0;
color: #34495e;
line-height: 1.6;
margin-bottom: 10px;
}
/* 删除按钮样式 */
.delete-button {
background-color: #ff6b6b;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s;
opacity: 0;
transform: translateY(5px);
}
.timeline-item:hover .delete-button {
opacity: 1;
transform: translateY(0);
}
.delete-button:hover {
background-color: #ff5252;
}
.no-events {
text-align: center;
color: #7f8c8d;
padding: 40px;
font-size: 1.2rem;
}
/* 响应式设计 - 适配移动设备 */
@media (max-width: 768px) {
.timeline-events::before {
left: 30px;
}
.timeline-item {
width: 100%;
padding-left: 70px;
padding-right: 25px;
}
.timeline-item-left,
.timeline-item-right {
left: 0;
}
.timeline-item-left .timeline-dot,
.timeline-item-right .timeline-dot {
left: 20px;
}
.auth-form {
width: 90%;
}
.password-input {
width: 90%;
}
}
</style>
(2)新建``文件
(3)测试
见下方。
效果测试
结束。