@ -0,0 +1,523 @@ | |||
:root { | |||
--primary-purple: #6a1b9a; | |||
--secondary-purple: #9c27b0; | |||
--light-purple: #d1c4e9; | |||
--dark-purple: #4a148c; | |||
--background: #f3e5f5; | |||
} | |||
* { | |||
box-sizing: border-box; | |||
margin: 0; | |||
padding: 0; | |||
font-family: 'Segoe UI', system-ui, sans-serif; | |||
} | |||
body { | |||
background: var(--background); | |||
min-height: 100vh; | |||
padding: 20px; | |||
} | |||
/* 新增内容容器边距 */ | |||
.main-container { | |||
padding: 0 2rem; | |||
/* 内容区二级边距 */ | |||
margin: 0 auto; | |||
} | |||
.card { | |||
background-color: var(--card-bg); | |||
border: 1px solid var(--border-color); | |||
transition: transform .3s, box-shadow .3s, border-color .3s; | |||
margin: 1rem 0; | |||
/* 新增卡片外边距 */ | |||
padding: 1.5rem; | |||
/* 新增卡片内边距[2](@ref) */ | |||
} | |||
.card:hover { | |||
border-color: var(--primary); | |||
transform: translateY(-5px); | |||
box-shadow: 0 10px 20px #0000001a | |||
} | |||
.btn { | |||
transition: transform .2s, background-color .2s | |||
} | |||
.btn:hover { | |||
transform: scale(1.05) | |||
} | |||
.fade-in { | |||
animation: .8s ease-in fadeIn | |||
} | |||
@keyframes fadeIn { | |||
0% { | |||
opacity: 0; | |||
transform: translateY(20px) | |||
} | |||
to { | |||
opacity: 1; | |||
transform: translateY(0) | |||
} | |||
} | |||
/* 导航栏样式 */ | |||
.nav-link { | |||
position: relative | |||
} | |||
.nav-link:after { | |||
content: ""; | |||
background-color: var(--primary); | |||
width: 0; | |||
height: 2px; | |||
transition: width .3s; | |||
position: absolute; | |||
bottom: -2px; | |||
left: 0 | |||
} | |||
.nav-link:hover:after,.nav-link.active:after { | |||
width: 100% | |||
} | |||
.navbar { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
padding: 15px 30px; | |||
background: linear-gradient(135deg, #E6E6FA, #D8BFD8); /* 浅紫色渐变背景 */ | |||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |||
border-bottom-left-radius: 30px; | |||
border-bottom-right-radius: 30px; | |||
margin-bottom: -10px; | |||
} | |||
/* Logo 样式 */ | |||
.navbar .logo { | |||
font-size: 28px; | |||
font-weight: bold; | |||
color: #4B0082; | |||
} | |||
/* 标题 样式 */ | |||
.title-highlight { | |||
font-size: 30px; | |||
font-weight: bold; | |||
color: #fff; | |||
background: #7B1FA2; | |||
padding: 10px 20px; | |||
border-radius: 20px; | |||
box-shadow: 3px 3px 12px rgba(0, 0, 0, 0.2); | |||
position: relative; | |||
display: inline-block; | |||
transition: transform 0.3s ease-in-out; | |||
} | |||
.title-highlight::after { | |||
content: ''; | |||
position: absolute; | |||
left: 50%; | |||
bottom: -6px; | |||
width: 60%; | |||
height: 5px; | |||
background: #BA68C8; | |||
border-radius: 10px; | |||
transform: translateX(-50%); | |||
transition: width 0.3s ease-in-out; | |||
} | |||
.title-highlight:hover { | |||
transform: translateY(-5px) scale(1.05); | |||
} | |||
.title-highlight:hover::after { | |||
width: 80%; | |||
} | |||
.nav-menu { | |||
display: flex; | |||
flex: 1; | |||
justify-content: space-between; | |||
max-width: 600px; | |||
} | |||
.nav-item { | |||
color: white; | |||
padding: 8px 20px; | |||
border-radius: 20px; | |||
transition: all 0.3s; | |||
text-align: center; | |||
font-size: 16px; | |||
} | |||
.nav-item:hover { | |||
background: rgba(255,255,255,0.15); | |||
} | |||
.search-box { | |||
margin-left: auto; | |||
display: flex; | |||
align-items: center; | |||
} | |||
.search-input { | |||
padding: 8px 15px; | |||
border-radius: 20px; | |||
border: 2px solid var(--light-purple); | |||
background: rgba(255,255,255,0.9); | |||
width: 240px; | |||
} | |||
/* 主内容区 */ | |||
.container { | |||
margin: 20px auto; | |||
margin-left: 50px; | |||
width: 90%; | |||
display: grid; | |||
grid-template-columns: 2fr 1fr; | |||
gap: 30px; | |||
} | |||
/* 动态编辑器 */ | |||
.post-editor { | |||
background: white; | |||
border-radius: 12px; | |||
padding: 20px; | |||
box-shadow: 0 2px 8px rgba(106,27,154,0.1); | |||
} | |||
.editor-input { | |||
width: 100%; | |||
min-height: 100px; | |||
border: 2px solid var(--light-purple); | |||
border-radius: 8px; | |||
padding: 15px; | |||
margin-bottom: 15px; | |||
font-size: 16px; | |||
} | |||
.post-actions { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
} | |||
.action-buttons button { | |||
background: var(--light-purple); | |||
color: var(--dark-purple); | |||
padding: 8px 15px; | |||
border-radius: 20px; | |||
margin-right: 10px; | |||
transition: all 0.3s; | |||
} | |||
.action-buttons button:hover { | |||
background: var(--secondary-purple); | |||
color: white; | |||
} | |||
.post-button { | |||
background: var(--primary-purple); | |||
color: white; | |||
padding: 10px 30px; | |||
border-radius: 25px; | |||
font-weight: 500; | |||
transition: transform 0.2s; | |||
} | |||
.post-button:hover { | |||
transform: translateY(-2px); | |||
} | |||
/* 动态卡片 */ | |||
.post-card { | |||
background: white; | |||
border-radius: 12px; | |||
padding: 20px; | |||
margin-top: 20px; | |||
margin-bottom: 20px; | |||
box-shadow: 0 2px 8px rgba(106,27,154,0.1); | |||
} | |||
.user-avatar { | |||
width: 50px; | |||
height: 50px; | |||
border-radius: 50%; | |||
border: 2px solid var(--light-purple); | |||
} | |||
.post-header { | |||
display: flex; | |||
gap: 15px; | |||
align-items: center; | |||
margin-bottom: 15px; | |||
} | |||
.post-content { | |||
color: var(--text-dark); | |||
line-height: 1.6; | |||
margin-bottom: 15px; | |||
} | |||
.post-media{ | |||
width: 80%; | |||
height: auto; | |||
border-radius: 8px; | |||
margin-top: 10px; | |||
margin-bottom: 15px; | |||
} | |||
.tag { | |||
display: inline-block; | |||
background: var(--light-purple); | |||
color: var(--dark-purple); | |||
padding: 4px 12px; | |||
border-radius: 15px; | |||
margin-right: 8px; | |||
margin-bottom: 15px; | |||
font-size: 14px; | |||
} | |||
/* 侧边栏 */ | |||
.sidebar { | |||
position: sticky; | |||
top: 80px; | |||
height: fit-content; | |||
} | |||
.trending-topics { | |||
background: white; | |||
padding: 20px; | |||
border-radius: 12px; | |||
width: 80%; | |||
box-shadow: 0 2px 8px rgba(106,27,154,0.1); | |||
} | |||
.topic-item { | |||
padding: 10px; | |||
margin: 8px 0; | |||
background: var(--light-purple); | |||
border-radius: 8px; | |||
color: var(--dark-purple); | |||
transition: all 0.3s; | |||
} | |||
.topic-item:hover { | |||
background: var(--secondary-purple); | |||
color: white; | |||
} | |||
/* 响应式设计 */ | |||
@media (max-width: 1200px) { | |||
.container { | |||
width: 90%; | |||
grid-template-columns: 1fr; | |||
} | |||
.nav-menu { | |||
display: none; | |||
} | |||
.search-box { | |||
margin-left: 50px; | |||
} | |||
} | |||
/* 活动推荐模块 */ | |||
.container-0 { | |||
max-width: 1400px; | |||
margin: 0 auto; | |||
padding: 10px; | |||
margin-top: 5px; | |||
} | |||
.activity-section { | |||
padding: 60px 0; | |||
} | |||
.section-title { | |||
text-align: center; | |||
font-size: 2.4rem; | |||
color: var(--dark-purple); | |||
margin-bottom: 50px; | |||
position: relative; | |||
} | |||
.section-title::after { | |||
content: ''; | |||
position: absolute; | |||
bottom: -15px; | |||
left: 50%; | |||
transform: translateX(-50%); | |||
width: 120px; | |||
height: 3px; | |||
background: var(--primary-purple); | |||
} | |||
.news-list { | |||
display: grid; | |||
grid-template-columns: repeat(4, 1fr); | |||
gap: 30px; | |||
padding: 0 20px; | |||
} | |||
.news-item { | |||
background: white; | |||
border-radius: 12px; | |||
overflow: hidden; | |||
box-shadow: 0 6px 15px rgba(106,27,154,0.1); | |||
transition: transform 0.3s ease, box-shadow 0.3s ease; | |||
} | |||
.news-item:hover { | |||
transform: translateY(-5px); | |||
box-shadow: 0 8px 20px rgba(106,27,154,0.15); | |||
} | |||
.news-media { | |||
position: relative; | |||
height: 220px; | |||
overflow: hidden; | |||
} | |||
.news-image { | |||
width: 100%; | |||
height: 100%; | |||
object-fit: cover; | |||
transition: transform 0.4s ease; | |||
} | |||
.news-item:hover .news-image { | |||
transform: scale(1.05); | |||
} | |||
.news-meta { | |||
position: absolute; | |||
bottom: 0; | |||
left: 0; | |||
right: 0; | |||
display: flex; | |||
justify-content: space-between; | |||
padding: 12px 20px; | |||
background: linear-gradient(0deg, rgba(106,27,154,0.9) 0%, transparent 100%); | |||
color: white; | |||
} | |||
.news-date { | |||
font-size: 0.9rem; | |||
opacity: 0.9; | |||
} | |||
.news-category { | |||
background: var(--light-purple); | |||
color: var(--dark-purple); | |||
padding: 6px 15px; | |||
border-radius: 20px; | |||
font-size: 0.85rem; | |||
font-weight: 500; | |||
} | |||
.news-content { | |||
padding: 20px; | |||
} | |||
.news-title { | |||
color: var(--dark-purple); | |||
font-size: 1.3rem; | |||
margin-bottom: 12px; | |||
line-height: 1.4; | |||
min-height: 60px; | |||
} | |||
.news-desc { | |||
color: #666; | |||
font-size: 0.95rem; | |||
line-height: 1.6; | |||
display: -webkit-box; | |||
-webkit-box-orient: vertical; | |||
overflow: hidden; | |||
min-height: 72px; | |||
} | |||
/* 更多活动提示 */ | |||
.news-item:last-child { | |||
grid-column: 1 / -1; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
background: var(--light-purple); | |||
min-height: 150px; | |||
border: 2px dashed var(--primary-purple); | |||
} | |||
.news-item:last-child h4 { | |||
color: var(--dark-purple); | |||
font-size: 1.6rem; | |||
margin: 0; | |||
padding: 30px; | |||
text-align: center; | |||
} | |||
/* 响应式设计 */ | |||
@media (max-width: 1440px) { | |||
.news-list { | |||
grid-template-columns: repeat(3, 1fr); | |||
} | |||
} | |||
@media (max-width: 1200px) { | |||
.container { | |||
padding: 20px; | |||
} | |||
.news-list { | |||
grid-template-columns: repeat(2, 1fr); | |||
} | |||
.section-title { | |||
font-size: 2rem; | |||
} | |||
} | |||
@media (max-width: 768px) { | |||
.news-list { | |||
grid-template-columns: 1fr; | |||
} | |||
.news-item:last-child { | |||
min-height: 100px; | |||
} | |||
.news-item:last-child h4 { | |||
font-size: 1.2rem; | |||
padding: 20px; | |||
} | |||
} | |||
@media (max-width: 480px) { | |||
.section-title { | |||
font-size: 1.8rem; | |||
} | |||
.news-media { | |||
height: 180px; | |||
} | |||
.news-title { | |||
font-size: 1.1rem; | |||
min-height: auto; | |||
} | |||
.news-desc { | |||
min-height: 54px; | |||
} | |||
} |
@ -0,0 +1,835 @@ | |||
:root { | |||
--primary-purple: #6a1b9a; | |||
--secondary-purple: #9c27b0; | |||
--light-purple: #d1c4e9; | |||
--dark-purple: #4a148c; | |||
--background: #f3e5f5; | |||
} | |||
* { | |||
box-sizing: border-box; | |||
margin: 0; | |||
padding: 0; | |||
font-family: 'Segoe UI', system-ui, sans-serif; | |||
} | |||
body { | |||
background: var(--background); | |||
min-height: 100vh; | |||
padding: 20px; | |||
} | |||
/* 新增内容容器边距 */ | |||
.main-container { | |||
padding: 0 2rem; | |||
/* 内容区二级边距 */ | |||
margin: 0 auto; | |||
} | |||
.card { | |||
background-color: var(--card-bg); | |||
border: 1px solid var(--border-color); | |||
transition: transform .3s, box-shadow .3s, border-color .3s; | |||
margin: 1rem 0; | |||
/* 新增卡片外边距 */ | |||
padding: 1.5rem; | |||
/* 新增卡片内边距[2](@ref) */ | |||
} | |||
.card:hover { | |||
border-color: var(--primary); | |||
transform: translateY(-5px); | |||
box-shadow: 0 10px 20px #0000001a | |||
} | |||
.btn { | |||
transition: transform .2s, background-color .2s | |||
} | |||
.btn:hover { | |||
transform: scale(1.05) | |||
} | |||
.fade-in { | |||
animation: .8s ease-in fadeIn | |||
} | |||
@keyframes fadeIn { | |||
0% { | |||
opacity: 0; | |||
transform: translateY(20px) | |||
} | |||
to { | |||
opacity: 1; | |||
transform: translateY(0) | |||
} | |||
} | |||
/* 导航栏样式 */ | |||
.nav-link { | |||
position: relative | |||
} | |||
.nav-link:after { | |||
content: ""; | |||
background-color: var(--primary); | |||
width: 0; | |||
height: 2px; | |||
transition: width .3s; | |||
position: absolute; | |||
bottom: -2px; | |||
left: 0 | |||
} | |||
.nav-link:hover:after,.nav-link.active:after { | |||
width: 100% | |||
} | |||
.navbar { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
padding: 15px 30px; | |||
background: linear-gradient(135deg, #E6E6FA, #D8BFD8); /* 浅紫色渐变背景 */ | |||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |||
border-bottom-left-radius: 30px; | |||
border-bottom-right-radius: 30px; | |||
} | |||
/* Logo 样式 */ | |||
.navbar .logo { | |||
font-size: 28px; | |||
font-weight: bold; | |||
color: #4B0082; | |||
} | |||
/* 标题 样式 */ | |||
.title-highlight { | |||
font-size: 30px; | |||
font-weight: bold; | |||
color: #fff; | |||
background: #7B1FA2; | |||
padding: 10px 20px; | |||
border-radius: 20px; | |||
box-shadow: 3px 3px 12px rgba(0, 0, 0, 0.2); | |||
position: relative; | |||
display: inline-block; | |||
transition: transform 0.3s ease-in-out; | |||
} | |||
.title-highlight::after { | |||
content: ''; | |||
position: absolute; | |||
left: 50%; | |||
bottom: -6px; | |||
width: 60%; | |||
height: 5px; | |||
background: #BA68C8; | |||
border-radius: 10px; | |||
transform: translateX(-50%); | |||
transition: width 0.3s ease-in-out; | |||
} | |||
.title-highlight:hover { | |||
transform: translateY(-5px) scale(1.05); | |||
} | |||
.title-highlight:hover::after { | |||
width: 80%; | |||
} | |||
/* 导航栏样式 */ | |||
.nav-container { | |||
background: white; | |||
padding: 1rem 2rem; | |||
border-radius: 12px; | |||
box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |||
margin: 2rem auto; | |||
max-width: 1300px; | |||
font-size:larger; | |||
} | |||
.nav-menu { | |||
display: flex; | |||
gap: 10rem; | |||
list-style: none; | |||
} | |||
.nav-item { | |||
position: relative; | |||
padding: 0.8rem 1.5rem; | |||
border-radius: 8px; | |||
cursor: pointer; | |||
transition: all 0.3s; | |||
color: var(--dark-purple); | |||
font-size: large; | |||
font-weight: 700; | |||
} | |||
.nav-item:hover { | |||
background: var(--light-purple); | |||
transform: translateY(-2px); | |||
} | |||
.nav-item:hover::after { | |||
content: ''; | |||
position: absolute; | |||
bottom: -5px; | |||
left: 50%; | |||
transform: translateX(-50%); | |||
width: 80%; | |||
height: 3px; | |||
background: var(--primary-purple); | |||
} | |||
/* 内容容器 */ | |||
.content-container { | |||
display: none; | |||
max-width: 1300px; | |||
margin: 2rem auto; | |||
background: white; | |||
border-radius: 12px; | |||
box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |||
padding: 2rem; | |||
} | |||
/* 默认显示第一个模块 */ | |||
#travel-service { | |||
display: block; | |||
} | |||
/* 保持原有卡片样式 */ | |||
.service-card { | |||
background: #f8f5fc; | |||
padding: 1.5rem; | |||
border-radius: 8px; | |||
margin-bottom: 1.5rem; | |||
border-left: 4px solid var(--primary-purple); | |||
} | |||
/* 响应式设计 */ | |||
@media (max-width: 768px) { | |||
.nav-menu { | |||
flex-direction: column; | |||
gap: 1rem; | |||
} | |||
.nav-item { | |||
text-align: center; | |||
} | |||
} | |||
.traffic-wrapper { | |||
display: grid; | |||
grid-template-columns: 1fr 300px; | |||
gap: 20px; | |||
margin-bottom: 2rem; | |||
} | |||
.map-container { | |||
width: 100%; | |||
height: 400px; | |||
border-radius: 12px; | |||
overflow: hidden; | |||
position: relative; | |||
} | |||
.traffic-legend { | |||
position: absolute; | |||
bottom: 20px; | |||
left: 20px; | |||
background: rgba(255,255,255,0.9); | |||
padding: 12px; | |||
border-radius: 8px; | |||
box-shadow: 0 2px 8px rgba(106,27,154,0.1); | |||
} | |||
.traffic-dot { | |||
display: inline-block; | |||
width: 12px; | |||
height: 12px; | |||
border-radius: 50%; | |||
margin-right: 8px; | |||
} | |||
.traffic-smooth { background: #4CAF50; } | |||
.traffic-slow { background: #FFC107; } | |||
.traffic-congested { background: #F44336; } | |||
.traffic-sidebar { | |||
background: #f8f5fc; | |||
padding: 20px; | |||
border-radius: 12px; | |||
} | |||
.alert-list li { | |||
padding: 10px; | |||
margin: 8px 0; | |||
background: white; | |||
border-left: 3px solid var(--primary-purple); | |||
border-radius: 6px; | |||
} | |||
.accessibility-recommend { | |||
display: grid; | |||
grid-template-columns: repeat(3,1fr); | |||
gap: 20px; | |||
} | |||
.recommend-card { | |||
text-align: center; | |||
padding: 20px; | |||
background: white; | |||
border-radius: 12px; | |||
transition: transform 0.3s; | |||
} | |||
.recommend-card:hover { | |||
transform: translateY(-5px); | |||
} | |||
.recommend-card .icon { | |||
font-size: 2.5rem; | |||
margin-bottom: 1rem; | |||
} | |||
.purple-btn { | |||
background: var(--primary-purple); | |||
color: white; | |||
border: none; | |||
margin-top: 20px; | |||
padding: 8px 20px; | |||
border-radius: 20px; | |||
cursor: pointer; | |||
transition: background 0.3s; | |||
} | |||
.purple-btn:hover { | |||
background: var(--dark-purple); | |||
} | |||
/* 好物推荐样式 */ | |||
.category-nav { | |||
display: flex; | |||
gap: 15px; | |||
margin-bottom: 2rem; | |||
padding: 1rem; | |||
background: #f8f5fc; | |||
border-radius: 12px; | |||
} | |||
.category-btn { | |||
padding: 8px 20px; | |||
border: 2px solid var(--light-purple); | |||
border-radius: 20px; | |||
background: transparent; | |||
cursor: pointer; | |||
transition: all 0.3s; | |||
} | |||
.category-btn.active { | |||
background: var(--primary-purple); | |||
color: white; | |||
border-color: var(--primary-purple); | |||
} | |||
.goods-grid { | |||
display: grid; | |||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); | |||
gap: 25px; | |||
} | |||
.goods-card { | |||
background: white; | |||
border-radius: 12px; | |||
overflow: hidden; | |||
box-shadow: 0 4px 12px rgba(106,27,154,0.1); | |||
transition: transform 0.3s; | |||
} | |||
.goods-card:hover { | |||
transform: translateY(-5px); | |||
} | |||
.goods-img { | |||
height: 220px; | |||
background: #d1c4e9; | |||
position: relative; | |||
background-size: cover; | |||
background-position: center; | |||
} | |||
.favorite-btn { | |||
position: absolute; | |||
top: 10px; | |||
right: 10px; | |||
background: rgba(255,255,255,0.9); | |||
padding: 6px 12px; | |||
border-radius: 20px; | |||
border: none; | |||
cursor: pointer; | |||
} | |||
.goods-info { | |||
padding: 1.5rem; | |||
} | |||
.price-tag { | |||
margin: 1rem 0; | |||
display: flex; | |||
align-items: center; | |||
gap: 10px; | |||
} | |||
.current-price { | |||
color: var(--primary-purple); | |||
font-size: 1.4rem; | |||
font-weight: bold; | |||
} | |||
.original-price { | |||
color: #999; | |||
text-decoration: line-through; | |||
} | |||
.buy-btn { | |||
width: 100%; | |||
padding: 12px; | |||
background: var(--primary-purple); | |||
color: white; | |||
border: none; | |||
border-radius: 8px; | |||
cursor: pointer; | |||
transition: background 0.3s; | |||
} | |||
.buy-btn:hover { | |||
background: var(--dark-purple); | |||
} | |||
/* 就业信息样式 */ | |||
.job-filter { | |||
background: #f8f5fc; | |||
padding: 20px; | |||
border-radius: 12px; | |||
margin-bottom: 2rem; | |||
} | |||
.search-box { | |||
display: flex; | |||
gap: 15px; | |||
margin-bottom: 1rem; | |||
} | |||
.search-input { | |||
flex: 1; | |||
padding: 12px; | |||
border: 2px solid var(--light-purple); | |||
border-radius: 8px; | |||
} | |||
.search-btn { | |||
background: var(--primary-purple); | |||
color: white; | |||
padding: 12px 25px; | |||
border: none; | |||
border-radius: 8px; | |||
cursor: pointer; | |||
} | |||
.filter-tags { | |||
display: flex; | |||
gap: 10px; | |||
flex-wrap: wrap; | |||
} | |||
.tag-btn { | |||
padding: 8px 15px; | |||
border: 2px solid var(--light-purple); | |||
border-radius: 20px; | |||
background: white; | |||
cursor: pointer; | |||
} | |||
.tag-btn.active { | |||
background: var(--primary-purple); | |||
color: white; | |||
border-color: var(--primary-purple); | |||
} | |||
.job-list { | |||
display: grid; | |||
gap: 20px; | |||
} | |||
.job-card { | |||
background: white; | |||
padding: 20px; | |||
border-radius: 12px; | |||
box-shadow: 0 2px 8px rgba(106,27,154,0.1); | |||
} | |||
.job-card.highlighted { | |||
border-left: 4px solid var(--primary-purple); | |||
} | |||
.job-header { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
margin-bottom: 1rem; | |||
} | |||
.salary { | |||
color: var(--primary-purple); | |||
font-weight: bold; | |||
} | |||
.job-meta { | |||
display: flex; | |||
gap: 15px; | |||
color: #666; | |||
margin-bottom: 1rem; | |||
} | |||
.job-tags { | |||
display: flex; | |||
gap: 10px; | |||
margin-bottom: 1rem; | |||
} | |||
.tag { | |||
padding: 4px 12px; | |||
border-radius: 15px; | |||
font-size: 0.9em; | |||
} | |||
.tag.purple { | |||
background: var(--light-purple); | |||
color: var(--dark-purple); | |||
} | |||
.job-desc { | |||
margin: 1rem 0; | |||
color: #444; | |||
} | |||
.job-desc ul { | |||
padding-left: 20px; | |||
margin-top: 0.5rem; | |||
} | |||
.job-actions { | |||
display: flex; | |||
gap: 15px; | |||
margin-top: 1.5rem; | |||
} | |||
.apply-btn { | |||
background: var(--primary-purple); | |||
color: white; | |||
padding: 10px 25px; | |||
border: none; | |||
border-radius: 20px; | |||
cursor: pointer; | |||
} | |||
.save-btn { | |||
background: none; | |||
border: 2px solid var(--primary-purple); | |||
color: var(--primary-purple); | |||
padding: 10px 25px; | |||
border-radius: 20px; | |||
cursor: pointer; | |||
} | |||
/* Section 容器 */ | |||
section { | |||
display: flex; | |||
/* 启用 Flexbox 布局 */ | |||
justify-content: space-between; | |||
/* 卡片之间均匀分布 */ | |||
gap: 20px; | |||
/* 卡片之间的间距 */ | |||
padding: 50px; | |||
background-color: linear-gradient(to right, #830f76, #ce7b9a); | |||
} | |||
/* 卡片样式 */ | |||
.card { | |||
background-color: #fff; | |||
border-radius: 50px; | |||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |||
overflow: hidden; | |||
width: calc(33.333% - 30px); | |||
/* 3 列布局,减去间距 */ | |||
text-align: center; | |||
padding: 25px; | |||
box-sizing: border-box; | |||
/* 确保 padding 不影响宽度 */ | |||
width: 30%; | |||
} | |||
.card:hover { | |||
transform: scale(1.05); | |||
transition: transform 0.3s ease; | |||
} | |||
.card img { | |||
width: 100%; | |||
height: 50%; | |||
border-radius: 10px 10px 0 0; | |||
} | |||
.card h1 { | |||
font-size: 1.5rem; | |||
margin: 10px 0; | |||
} | |||
.card .price { | |||
font-size: 1.25rem; | |||
color: #333; | |||
margin: 10px 0; | |||
} | |||
.card p { | |||
font-size: 1rem; | |||
color: #666; | |||
} | |||
.card button { | |||
background-color: #3498db; | |||
color: #fff; | |||
border: none; | |||
padding: 15px 50px; | |||
border-radius: 5px; | |||
cursor: pointer; | |||
transition: background-color 0.3s ease; | |||
} | |||
.card button:hover { | |||
background-color: #2980b9; | |||
} | |||
/* 响应式设计 */ | |||
@media (max-width: 768px) { | |||
section { | |||
flex-wrap: wrap; | |||
/* 小屏幕上卡片换行 */ | |||
} | |||
.card { | |||
width: calc(40% - 20px); | |||
/* 2 列布局 */ | |||
} | |||
} | |||
@media (max-width: 480px) { | |||
.card { | |||
width: 100%; | |||
/* 1 列布局 */ | |||
} | |||
} | |||
.news-container { | |||
max-width: 85%; | |||
margin: 0 auto; | |||
margin-top: 60px; | |||
background: rgb(240, 211, 237); | |||
border-radius: 12px; | |||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1); | |||
padding: 2rem; | |||
} | |||
/* 区块标题 */ | |||
.section-title { | |||
color: #2c3e50; | |||
border-bottom: 3px solid #3498db; | |||
padding-bottom: 0.5rem; | |||
margin-bottom: 2rem; | |||
} | |||
/* 新闻列表 */ | |||
.news-list { | |||
display: grid; | |||
gap: 1.5rem; | |||
} | |||
/* 单个新闻项 */ | |||
.news-item { | |||
display: grid; | |||
grid-template-columns: 120px 1fr; | |||
gap: 1.5rem; | |||
padding: 1.5rem; | |||
background: #fff; | |||
border-radius: 8px; | |||
transition: all 0.3s ease; | |||
border: 1px solid #eee; | |||
} | |||
.news-media { | |||
position: relative; | |||
overflow: hidden; | |||
} | |||
.news-image { | |||
width: 100%; | |||
height: 80px; | |||
object-fit: cover; | |||
transition: transform 0.3s ease; | |||
} | |||
.news-item:hover { | |||
transform: translateY(-3px); | |||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); | |||
} | |||
/* 元信息 */ | |||
.news-meta { | |||
display: flex; | |||
flex-direction: column; | |||
align-items: flex-end; | |||
} | |||
.news-date { | |||
color: #7f8c8d; | |||
font-size: 0.9rem; | |||
margin-bottom: 0.5rem; | |||
} | |||
.news-category { | |||
background: #3498db; | |||
color: white; | |||
padding: 0.3rem 0.8rem; | |||
border-radius: 20px; | |||
font-size: 0.8rem; | |||
} | |||
/* 内容区域 */ | |||
.news-title { | |||
color: #2c3e50; | |||
margin: 0 0 0.5rem; | |||
font-size: 1.4rem; | |||
} | |||
.news-desc { | |||
color: #7f8c8d; | |||
margin: 0; | |||
font-size: 0.95rem; | |||
} | |||
hr.dashed { | |||
border-top: 3px dashed #bbb; | |||
} | |||
#activity h4 { | |||
color: #b44286; | |||
text-align: center; | |||
margin-bottom: 30px; | |||
font-size: 24px; | |||
position: relative; | |||
} | |||
.news-list { | |||
display: grid; | |||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |||
gap: 20px; | |||
margin-top: 30px; | |||
} | |||
.news-item { | |||
background: white; | |||
border-radius: 8px; | |||
overflow: hidden; | |||
box-shadow: 0 3px 6px rgba(0,0,0,0.1); | |||
transition: all 0.3s ease; | |||
} | |||
.news-item:hover { | |||
transform: translateY(-5px); | |||
box-shadow: 0 10px 20px rgba(0,0,0,0.1); | |||
} | |||
.news-media { | |||
position: relative; | |||
height: 180px; | |||
overflow: hidden; | |||
} | |||
.news-image { | |||
width: 100%; | |||
height: 100%; | |||
object-fit: cover; | |||
transition: transform 0.5s ease; | |||
} | |||
.news-item:hover .news-image { | |||
transform: scale(1.05); | |||
} | |||
.news-meta { | |||
position: absolute; | |||
bottom: 0; | |||
left: 0; | |||
right: 0; | |||
background: rgba(0,0,0,0.7); | |||
color: white; | |||
padding: 10px; | |||
display: flex; | |||
justify-content: space-between; | |||
} | |||
.news-date { | |||
font-size: 12px; | |||
} | |||
.news-category { | |||
background: #e74c3c; | |||
padding: 2px 8px; | |||
border-radius: 4px; | |||
font-size: 12px; | |||
} | |||
.news-content { | |||
padding: 15px; | |||
} | |||
.news-title { | |||
color: #2c3e50; | |||
margin: 0 0 10px; | |||
font-size: 18px; | |||
transition: color 0.3s; | |||
} | |||
.news-item:hover .news-title { | |||
color: #e74c3c; | |||
} |
@ -0,0 +1,538 @@ | |||
body { | |||
font-family: Arial, sans-serif; | |||
margin: 0; | |||
background-color: #f9f9f9; | |||
} | |||
/* 导航栏样式 */ | |||
.navbar { | |||
margin: 0 20px; | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
padding: 15px 30px; | |||
background: linear-gradient(135deg, #E6E6FA, #D8BFD8); /* 浅紫色渐变背景 */ | |||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |||
border-bottom-left-radius: 30px; | |||
border-bottom-right-radius: 30px; | |||
} | |||
/* Logo 样式 */ | |||
.navbar .logo { | |||
font-size: 28px; | |||
font-weight: bold; | |||
color: #4B0082; | |||
} | |||
/* 标题 样式 */ | |||
.title-highlight { | |||
font-size: 30px; | |||
font-weight: bold; | |||
color: #fff; | |||
background: #7B1FA2; | |||
padding: 10px 20px; | |||
border-radius: 20px; | |||
box-shadow: 3px 3px 12px rgba(0, 0, 0, 0.2); | |||
position: relative; | |||
display: inline-block; | |||
transition: transform 0.3s ease-in-out; | |||
} | |||
.title-highlight::after { | |||
content: ''; | |||
position: absolute; | |||
left: 50%; | |||
bottom: -6px; | |||
width: 60%; | |||
height: 5px; | |||
background: #BA68C8; | |||
border-radius: 10px; | |||
transform: translateX(-50%); | |||
transition: width 0.3s ease-in-out; | |||
} | |||
.title-highlight:hover { | |||
transform: translateY(-5px) scale(1.05); | |||
} | |||
.title-highlight:hover::after { | |||
width: 80%; | |||
} | |||
/* 中间 样式 */ | |||
.nav-divider { | |||
flex-grow: 1; | |||
height: 4px; | |||
background: linear-gradient(to right, transparent, #BA68C8, transparent); | |||
border-radius: 10px; | |||
margin: 0 20px; | |||
opacity: 0.8; | |||
transition: opacity 0.3s ease-in-out; | |||
} | |||
.nav-divider:hover { | |||
opacity: 1; | |||
} | |||
/* 图标容器样式 */ | |||
.icon-container { | |||
display: flex; | |||
gap: 30px; /* 增加图标间距 */ | |||
} | |||
.navbaricon i { | |||
font-size: 25px; | |||
color: #4B0082; | |||
animation: gemAnim 1.5s ease infinite; | |||
} | |||
/* 每个图标的圆形背景 */ | |||
.icon-circle { | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
background-color: #F3E5F5; /* 浅紫色背景 */ | |||
border-radius: 50%; /* 圆形背景 */ | |||
width: 80px; /* 圆形宽度 */ | |||
width: 50px; /* 圆形宽度 */ | |||
height: 50px; /* 圆形高度 */ | |||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |||
} | |||
/* 图标悬停容器样式 */ | |||
.hover-container { | |||
position: relative; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
} | |||
.hover-card { | |||
position: absolute; | |||
top: 50px; | |||
left: 50%; | |||
transform: translateX(-50%); | |||
width: 280px; /* 增大卡片宽度 */ | |||
background: #F3E5F5; | |||
border: 1px solid #D8BFD8; | |||
border-radius: 12px; | |||
box-shadow: 0 6px 12px rgba(180, 136, 255, 0.3); | |||
padding: 16px; | |||
font-size: 14px; | |||
display: none; | |||
z-index: 100; | |||
text-align: center; | |||
max-width: 90vw; /* 防止超出屏幕 */ | |||
max-height: 80vh; | |||
overflow: auto; | |||
} | |||
/* 调整卡片在屏幕边界时的位置 */ | |||
.hover-container:hover .hover-card { | |||
display: block; | |||
left: auto; | |||
right: 0; | |||
transform: translateX(0); | |||
} | |||
@media (max-width: 400px) { | |||
.hover-card { | |||
width: 95vw; /* 在小屏幕下占满大部分宽度 */ | |||
} | |||
} | |||
/* popout 动画效果 */ | |||
@keyframes popout { | |||
0% { | |||
opacity: 0; | |||
transform: translateY(-10px); | |||
} | |||
100% { | |||
opacity: 1; | |||
transform: translateY(0); | |||
} | |||
} | |||
/* 图标悬停样式 */ | |||
.hover-container i { | |||
transition: transform 0.3s ease, color 0.3s ease; | |||
} | |||
/* 悬停时图标放大 */ | |||
.hover-container:hover i { | |||
transform: scale(1.2); | |||
} | |||
/* 进度条样式 */ | |||
.progress-bar { | |||
width: 100%; | |||
height: 8px; | |||
background-color: #f0f0f0; | |||
border-radius: 4px; | |||
margin-top: 10px; | |||
} | |||
.progress { | |||
height: 100%; | |||
background-color: #8A2BE2; | |||
border-radius: 4px; | |||
} | |||
/* 日历样式 */ | |||
.calendar { | |||
width: 100%; | |||
border-collapse: collapse; | |||
} | |||
.calendar th, | |||
.calendar td { | |||
width: 36px; | |||
/* 适配卡片增大 */ | |||
height: 36px; | |||
text-align: center; | |||
border-radius: 8px; | |||
} | |||
.calendar th { | |||
color: #4B0082; | |||
} | |||
.calendar td { | |||
background: #F8F0FF; | |||
cursor: pointer; | |||
} | |||
.calendar td:hover { | |||
background: #E0B0FF; | |||
} | |||
/* 高亮当前日期 */ | |||
.current-day { | |||
background: #BA68C8 !important; | |||
color: white; | |||
font-weight: bold; | |||
} | |||
/* 随机选中以前的日期 */ | |||
.random-day { | |||
background: #BA68C8 !important; | |||
color: white; | |||
font-weight: bold; | |||
} | |||
/* 我的积分卡片 - 宝石旋转效果 */ | |||
.gem-spin i { | |||
animation: gem-spin 2s infinite linear; | |||
} | |||
@keyframes gem-spin { | |||
0% { | |||
transform: rotate(0deg); | |||
} | |||
100% { | |||
transform: rotate(360deg); | |||
} | |||
} | |||
/* 主内容区域 */ | |||
.content { | |||
margin: 40px 40px; /* 上下留白 + 水平居中 */ | |||
background: #F3E5F5; | |||
border-radius: 12px; | |||
padding: 30px; | |||
box-shadow: 0 4px 16px rgba(180, 136, 255, 0.2); | |||
display: flex; | |||
flex-direction: row; /* 水平排列子元素 */ | |||
gap: 20px; /* 子元素间距 */ | |||
border: 1px solid rgba(180, 136, 255, 0.3); | |||
transition: box-shadow 0.3s ease; | |||
} | |||
/* 侧边栏 - 垂直选项卡按钮区域 */ | |||
.tab { | |||
display: flex; | |||
flex-direction: column; /* 垂直排列按钮 */ | |||
width: 30%; /* 设置为左侧栏的二分之一 */ | |||
margin-right: 20px; /* 为按钮区域添加右间距 */ | |||
} | |||
/* 为选项卡按钮设置样式 */ | |||
.tab button { | |||
background-color: inherit; | |||
color: black; | |||
padding: 16px; | |||
border: 1px solid #ccc; | |||
width: 100%; | |||
border-radius: 20px; /* 改为更圆润的曲线 */ | |||
cursor: pointer; | |||
text-align: left; | |||
transition: 0.3s; | |||
font-size: 17px; | |||
margin-bottom: 8px; /* 为按钮之间添加间距 */ | |||
background-color: #F1E6FB; /* 浅紫色背景 */ | |||
} | |||
/* 悬停时改变按钮的背景颜色 */ | |||
.tab button:hover { | |||
background-color: #D1A7F7; /* 更加明显的浅紫色悬停背景 */ | |||
border: 1px solid #9C63D1; /* 加强边框颜色 */ | |||
} | |||
/* 为活动的选项卡按钮设置样式 */ | |||
.tab button.active { | |||
background-color: #9C63D1; /* 选中状态的浅紫色背景 */ | |||
color: white; /* 文字变为白色 */ | |||
border: 1px solid #9C63D1; /* 选中时边框颜色与背景一致 */ | |||
} | |||
/* 选项卡内容区域样式 */ | |||
.tabcontent { | |||
display: none; /* 默认隐藏内容 */ | |||
padding: 15px; | |||
background-color: #f9f9f9; | |||
border: 1px solid #ccc; | |||
border-top: none; | |||
margin-top: 10px; | |||
border-radius: 8px; | |||
width: 100%; /* 让内容区域自适应填充剩余宽度 */ | |||
flex-grow: 1; /* 让内容区域占据剩余空间 */ | |||
} | |||
/* 显示活动选项卡内容 */ | |||
.tabcontent.active { | |||
display: block; | |||
} | |||
/* 内容区域的背景及边距调整 */ | |||
.sidebar .tabcontent { | |||
background-color: #FFF; | |||
margin-top: 10px; | |||
padding: 20px; | |||
border-radius: 8px; | |||
box-shadow: 0 4px 8px rgba(180, 136, 255, 0.2); | |||
} | |||
/* 高亮显示选中内容 */ | |||
.sidebar .tabcontent.active { | |||
background-color: #F3E5F5; /* 给选中内容加个浅紫色背景 */ | |||
box-shadow: 0 4px 8px rgba(156, 99, 209, 0.3); | |||
} | |||
/* 地图 */ | |||
.map-container { | |||
position: relative; | |||
width: 100%; | |||
display: flex; | |||
flex-direction: column; | |||
align-items: center; | |||
padding: 20px 0; | |||
} | |||
/* 进度线 */ | |||
.progress-line { | |||
width: 4px; | |||
height: 50px; | |||
background: #777; | |||
margin: 10px 0; | |||
} | |||
/* 已解锁进度线 */ | |||
.unlocked-line { | |||
background: linear-gradient(to bottom, #BA68C8, transparent); | |||
} | |||
/* 关卡点 */ | |||
.map-point { | |||
width: 120px; | |||
height: 120px; | |||
background: rgba(255, 255, 255, 0.9); | |||
border-radius: 50%; | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: center; | |||
align-items: center; | |||
font-size: 16px; | |||
font-weight: bold; | |||
cursor: pointer; | |||
transition: transform 0.3s, box-shadow 0.3s; | |||
} | |||
/* 已解锁关卡 */ | |||
.unlocked { | |||
border: 3px solid #BA68C8; | |||
color: #4B0082; | |||
animation: glow 1.5s infinite alternate; | |||
} | |||
/* 未解锁关卡 */ | |||
.locked { | |||
border: 3px dashed #777; | |||
color: #777; | |||
filter: grayscale(50%); | |||
} | |||
/* 动态光晕 */ | |||
@keyframes glow { | |||
from { | |||
box-shadow: 0 0 10px #BA68C8; | |||
} | |||
to { | |||
box-shadow: 0 0 20px #BA68C8; | |||
} | |||
} | |||
/* 悬停放大效果 */ | |||
.map-point:hover { | |||
transform: scale(1.1); | |||
} | |||
/* 关卡文本 */ | |||
.lesson-name { | |||
margin-top: 5px; | |||
font-size: 14px; | |||
} | |||
/* 视频教程 */ | |||
.lesson-container { | |||
display: flex; | |||
flex-direction: column; | |||
gap: 20px; | |||
} | |||
.lesson-card { | |||
background: #FFF; | |||
border-radius: 10px; | |||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |||
padding: 15px; | |||
transition: transform 0.3s ease-in-out; | |||
} | |||
.lesson-card:hover { | |||
transform: scale(1.05); | |||
} | |||
.lesson-header { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
font-size: 18px; | |||
font-weight: bold; | |||
margin-bottom: 10px; | |||
} | |||
.completed .completed-icon { | |||
color: #4CAF50; | |||
} | |||
.in-progress .progress-icon { | |||
color: #FFC107; | |||
} | |||
.locked .lock-icon { | |||
color: #9E9E9E; | |||
} | |||
.lesson-progress { | |||
width: 100%; | |||
height: 8px; | |||
background: #E0E0E0; | |||
border-radius: 4px; | |||
margin-top: 10px; | |||
} | |||
.progress-bar { | |||
height: 100%; | |||
background: #BA68C8; | |||
border-radius: 4px; | |||
} | |||
.locked-message { | |||
text-align: center; | |||
font-size: 14px; | |||
color: #777; | |||
} | |||
/* 互动 */ | |||
.practice-container { | |||
display: flex; | |||
gap: 20px; | |||
} | |||
.practice-card { | |||
width: 180px; | |||
height: 220px; | |||
background: #FFF; | |||
border-radius: 12px; | |||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: center; | |||
align-items: center; | |||
cursor: pointer; | |||
transform-style: preserve-3d; | |||
transition: transform 0.6s; | |||
} | |||
.practice-card img { | |||
width: 80px; | |||
height: 80px; | |||
} | |||
.practice-card .card-front, | |||
.practice-card .card-back { | |||
position: absolute; | |||
width: 100%; | |||
height: 100%; | |||
backface-visibility: hidden; | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: center; | |||
align-items: center; | |||
} | |||
.card-back { | |||
transform: rotateY(180deg); | |||
font-size: 18px; | |||
font-weight: bold; | |||
color: #4B0082; | |||
} | |||
.practice-card.flipped { | |||
transform: rotateY(180deg); | |||
} | |||
@ -0,0 +1,438 @@ | |||
:root { | |||
--primary-purple: #6a1b9a; | |||
--secondary-purple: #9c27b0; | |||
--light-purple: #d1c4e9; | |||
--dark-purple: #4a148c; | |||
--background: #f3e5f5; | |||
} | |||
* { | |||
box-sizing: border-box; | |||
margin: 0; | |||
padding: 0; | |||
font-family: 'Segoe UI', system-ui, sans-serif; | |||
} | |||
body { | |||
background: var(--background); | |||
color: var(--text-color); | |||
min-height: 100vh; | |||
padding: 20px; | |||
} | |||
@media (max-width: 768px) { | |||
body { | |||
padding: 1.5rem; | |||
/* 移动端统一边距 */ | |||
} | |||
} | |||
/* 新增内容容器边距 */ | |||
.main-container { | |||
padding: 0 2rem; | |||
/* 内容区二级边距 */ | |||
margin: 0 auto; | |||
} | |||
.card { | |||
background-color: var(--card-bg); | |||
border: 1px solid var(--border-color); | |||
transition: transform .3s, box-shadow .3s, border-color .3s; | |||
margin: 1rem 0; | |||
/* 新增卡片外边距 */ | |||
padding: 1.5rem; | |||
/* 新增卡片内边距[2](@ref) */ | |||
} | |||
.card:hover { | |||
border-color: var(--primary); | |||
transform: translateY(-5px); | |||
box-shadow: 0 10px 20px #0000001a | |||
} | |||
.btn { | |||
transition: transform .2s, background-color .2s | |||
} | |||
.btn:hover { | |||
transform: scale(1.05) | |||
} | |||
.fade-in { | |||
animation: .8s ease-in fadeIn | |||
} | |||
@keyframes fadeIn { | |||
0% { | |||
opacity: 0; | |||
transform: translateY(20px) | |||
} | |||
to { | |||
opacity: 1; | |||
transform: translateY(0) | |||
} | |||
} | |||
/* 导航栏样式 */ | |||
.nav-link { | |||
position: relative | |||
} | |||
.nav-link:after { | |||
content: ""; | |||
background-color: var(--primary); | |||
width: 0; | |||
height: 2px; | |||
transition: width .3s; | |||
position: absolute; | |||
bottom: -2px; | |||
left: 0 | |||
} | |||
.nav-link:hover:after, | |||
.nav-link.active:after { | |||
width: 100% | |||
} | |||
.navbar { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
padding: 15px 30px; | |||
background: linear-gradient(135deg, #E6E6FA, #D8BFD8); | |||
/* 浅紫色渐变背景 */ | |||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |||
border-bottom-left-radius: 30px; | |||
border-bottom-right-radius: 30px; | |||
} | |||
/* Logo 样式 */ | |||
.navbar .logo { | |||
font-size: 28px; | |||
font-weight: bold; | |||
color: #4B0082; | |||
} | |||
/* 标题 样式 */ | |||
.title-highlight { | |||
font-size: 30px; | |||
font-weight: bold; | |||
color: #fff; | |||
background: #7B1FA2; | |||
padding: 10px 20px; | |||
border-radius: 20px; | |||
box-shadow: 3px 3px 12px rgba(0, 0, 0, 0.2); | |||
position: relative; | |||
display: inline-block; | |||
transition: transform 0.3s ease-in-out; | |||
} | |||
.title-highlight::after { | |||
content: ''; | |||
position: absolute; | |||
left: 50%; | |||
bottom: -6px; | |||
width: 60%; | |||
height: 5px; | |||
background: #BA68C8; | |||
border-radius: 10px; | |||
transform: translateX(-50%); | |||
transition: width 0.3s ease-in-out; | |||
} | |||
.title-highlight:hover { | |||
transform: translateY(-5px) scale(1.05); | |||
} | |||
.title-highlight:hover::after { | |||
width: 80%; | |||
} | |||
/* 导航栏样式 */ | |||
.nav-container { | |||
background: white; | |||
padding: 1rem 2rem; | |||
border-radius: 12px; | |||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |||
margin: 2rem auto; | |||
max-width: 1300px; | |||
font-size: larger; | |||
} | |||
.nav-menu { | |||
display: flex; | |||
gap: 10rem; | |||
list-style: none; | |||
} | |||
.nav-item { | |||
position: relative; | |||
padding: 0.8rem 1.5rem; | |||
border-radius: 8px; | |||
cursor: pointer; | |||
transition: all 0.3s; | |||
color: var(--dark-purple); | |||
font-size: large; | |||
font-weight: 700; | |||
} | |||
.nav-item:hover { | |||
background: var(--light-purple); | |||
transform: translateY(-2px); | |||
} | |||
.nav-item:hover::after { | |||
content: ''; | |||
position: absolute; | |||
bottom: -5px; | |||
left: 50%; | |||
transform: translateX(-50%); | |||
width: 80%; | |||
height: 3px; | |||
background: var(--primary-purple); | |||
} | |||
/* 内容容器 */ | |||
.content-container { | |||
display: none; | |||
max-width: 1300px; | |||
margin: 2rem auto; | |||
background: white; | |||
border-radius: 12px; | |||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |||
padding: 2rem; | |||
} | |||
.dashboard { | |||
transform: scale(0.92); | |||
display: flex; | |||
margin-top: 30px; | |||
gap: 20px; | |||
max-width: 1800px; | |||
margin: 20 auto; | |||
} | |||
/* 通用卡片样式 */ | |||
.card { | |||
background: white; | |||
border-radius: 12px; | |||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |||
padding: 20px; | |||
flex: 1; | |||
min-height: 80vh; | |||
} | |||
h2 { | |||
color: var(--dark-purple); | |||
margin-bottom: 25px; | |||
padding-bottom: 10px; | |||
border-bottom: 2px solid var(--light-purple); | |||
} | |||
/* 日历区块 */ | |||
.calendar-container { | |||
flex: 2; | |||
min-width: 500px; | |||
} | |||
.calendar { | |||
display: grid; | |||
grid-template-columns: repeat(7, 1fr); | |||
gap: 5px; | |||
margin-top: 10px; | |||
} | |||
.day-header { | |||
background: var(--light-purple); | |||
color: var(--dark-purple); | |||
padding: 12px; | |||
text-align: center; | |||
border-radius: 8px; | |||
} | |||
.calendar-day { | |||
min-height: 80px; | |||
border: 1px solid var(--light-purple); | |||
padding: 10px; | |||
border-radius: 8px; | |||
position: relative; | |||
} | |||
.day-number { | |||
color: var(--secondary-purple); | |||
font-weight: bold; | |||
} | |||
/* 待办事项 */ | |||
.todo-container { | |||
flex: 1; | |||
min-width: 300px; | |||
} | |||
#taskInput { | |||
width: 100%; | |||
padding: 12px; | |||
border: 2px solid var(--light-purple); | |||
border-radius: 8px; | |||
margin-bottom: 15px; | |||
margin-top: 10px; | |||
} | |||
.addBtn { | |||
background: var(--primary-purple); | |||
color: white; | |||
padding: 12px; | |||
border-radius: 12px; | |||
cursor: pointer; | |||
display: block; | |||
text-align: center; | |||
margin-bottom: 15px; | |||
margin-top: 2px; | |||
} | |||
.close { | |||
position: absolute; | |||
right: 15px; | |||
color: var(--primary-purple); | |||
cursor: pointer; | |||
} | |||
/* 日程表单 */ | |||
.schedule-form { | |||
flex: 1; | |||
min-width: 300px; | |||
} | |||
.form-group { | |||
margin-bottom: 15px; | |||
margin-top: 10px; | |||
} | |||
input[type="date"], | |||
input[type="time"], | |||
input[type="text"] { | |||
width: 100%; | |||
padding: 10px; | |||
border: 2px solid var(--light-purple); | |||
border-radius: 8px; | |||
} | |||
.submit-btn { | |||
background: var(--primary-purple); | |||
color: white; | |||
padding: 12px; | |||
border: none; | |||
border-radius: 8px; | |||
margin-top: 8px; | |||
width: 100%; | |||
cursor: pointer; | |||
} | |||
@media (max-width: 1200px) { | |||
.dashboard { | |||
flex-wrap: wrap; | |||
} | |||
.calendar-container { | |||
flex: 100%; | |||
} | |||
} | |||
/* 任务列表 */ | |||
#taskList { | |||
margin-top: 15px; | |||
list-style: none; | |||
padding: 0; | |||
} | |||
#taskList li { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
padding: 12px 15px; | |||
margin-bottom: 8px; | |||
background: #fff; | |||
border-radius: 8px; | |||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | |||
transition: all 0.3s ease; | |||
border-left: 4px solid #4a90e2; | |||
} | |||
#taskList li:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |||
} | |||
.task-text { | |||
flex-grow: 1; | |||
font-size: 16px; | |||
color: #333; | |||
padding-right: 10px; | |||
word-break: break-word; | |||
} | |||
.completed { | |||
text-decoration: line-through; | |||
color: #95a5a6; | |||
position: relative; | |||
} | |||
.completed::after { | |||
content: ""; | |||
position: absolute; | |||
left: 0; | |||
top: 50%; | |||
width: 100%; | |||
height: 1px; | |||
background: linear-gradient(90deg, rgba(149, 165, 166, 0.5), rgba(149, 165, 166, 0.2)); | |||
} | |||
.task-actions { | |||
display: flex; | |||
gap: 8px; | |||
} | |||
.task-actions button { | |||
width: 28px; | |||
height: 28px; | |||
border: none; | |||
border-radius: 50%; | |||
cursor: pointer; | |||
font-size: 14px; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
transition: all 0.2s; | |||
} | |||
.task-actions button:first-child { | |||
background: #2ecc71; | |||
color: white; | |||
} | |||
.task-actions button:last-child { | |||
background: #e74c3c; | |||
color: white; | |||
} | |||
.task-actions button:hover { | |||
transform: scale(1.1); | |||
} | |||
/* 空状态提示 */ | |||
#taskList:empty::before { | |||
content: "暂无待办事项,添加你的第一个任务吧~"; | |||
display: block; | |||
text-align: center; | |||
color: #95a5a6; | |||
padding: 20px 0; | |||
font-size: 14px; | |||
} |
@ -0,0 +1,59 @@ | |||
.user-menu-box-container { | |||
position: relative; | |||
display: inline-block; | |||
z-index: 100; /* ✅ 确保下拉在最上层 */ | |||
} | |||
.user-menu-box-button { | |||
display: flex; | |||
align-items: center; | |||
gap: 8px; | |||
text-decoration: none; | |||
color: #333; | |||
padding: 6px 10px; | |||
border-radius: 6px; | |||
transition: background-color 0.2s ease; | |||
cursor: pointer; | |||
} | |||
.user-menu-box-button:hover { | |||
background-color: #f4f4f4; | |||
} | |||
.user-menu-box-avatar { | |||
width: 32px; | |||
height: 32px; | |||
border-radius: 50%; | |||
object-fit: cover; | |||
} | |||
.user-menu-box-dropdown { | |||
display: none; | |||
position: absolute; | |||
top: 100%; | |||
right: 0; | |||
min-width: 160px; | |||
background: white; | |||
border: 1px solid #ddd; | |||
border-radius: 6px; | |||
box-shadow: 0 2px 8px rgba(0,0,0,0.15); | |||
z-index: 999; /* ✅ 确保悬浮框在其他元素上方 */ | |||
} | |||
.user-menu-box-dropdown a { | |||
display: block; | |||
padding: 10px 15px; | |||
color: #333; | |||
text-decoration: none; | |||
font-size: 14px; | |||
white-space: nowrap; | |||
transition: background-color 0.2s ease; | |||
} | |||
.user-menu-box-dropdown a:hover { | |||
background-color: #f0f0f0; | |||
} | |||
.user-menu-box-container:hover .user-menu-box-dropdown { | |||
display: block; | |||
} |
@ -0,0 +1,931 @@ | |||
/* 全局样式 */ | |||
body { | |||
font-family: Arial, sans-serif; | |||
background-color: #f5f5f5; | |||
margin: 0; | |||
padding: 0; | |||
} | |||
/* 自定义背景 */ | |||
/* 左上角背景上传按钮样式 */ | |||
.bg-upload-container { | |||
position: fixed; | |||
top: 20px; | |||
left: 10px; | |||
background: rgba(0, 0, 0, 0.5); | |||
padding: 8px 12px; | |||
border-radius: 6px; | |||
z-index: 1000; | |||
} | |||
.bg-upload-label { | |||
color: white; | |||
font-size: 14px; | |||
cursor: pointer; | |||
} | |||
.bg-upload { | |||
display: none; | |||
} | |||
/* 让背景图片自适应网页并平铺 */ | |||
body { | |||
background-size: cover; | |||
/* 确保图片适应整个屏幕 */ | |||
background-repeat: no-repeat; | |||
/* 不重复 */ | |||
background-attachment: fixed; | |||
/* 固定背景 */ | |||
background-position: center; | |||
/* 居中对齐 */ | |||
} | |||
/* 个人信息区域 */ | |||
/* 个人资料容器 */ | |||
.profile-container { | |||
width: 100%; | |||
max-width: 1000px; | |||
background: white; | |||
border-radius: 12px; | |||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); | |||
overflow: hidden; | |||
text-align: center; | |||
margin: 20px auto; | |||
position: relative; | |||
} | |||
/* 背景横幅 */ | |||
.profile-banner { | |||
height: 200px; | |||
background: url("https://via.placeholder.com/800x200/7f7fff") center/cover no-repeat; | |||
position: relative; | |||
} | |||
/* 更换背景按钮 */ | |||
.change-banner-btn { | |||
position: absolute; | |||
right: 10px; | |||
bottom: 10px; | |||
background: rgba(0, 0, 0, 0.5); | |||
color: white; | |||
border: none; | |||
padding: 6px 12px; | |||
border-radius: 5px; | |||
cursor: pointer; | |||
font-size: 12px; | |||
} | |||
.change-banner-btn:hover { | |||
background: rgba(0, 0, 0, 0.7); | |||
} | |||
/* 头像容器 */ | |||
.profile-avatar-container { | |||
position: absolute; | |||
top: 120px; | |||
/* 让头像悬浮在背景之上 */ | |||
left: 50%; | |||
transform: translateX(-50%); | |||
width: 130px; | |||
height: 130px; | |||
background: white; | |||
/* 白色背景 */ | |||
border-radius: 50%; | |||
border: 4px solid #ddd; | |||
/* 灰色边框 */ | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | |||
} | |||
/* 头像 */ | |||
.profile-avatar { | |||
width: 120px; | |||
height: 120px; | |||
border-radius: 50%; | |||
object-fit: cover; | |||
cursor: pointer; | |||
} | |||
/* 个人信息部分 */ | |||
.profile-content { | |||
padding-top: 60px; | |||
/* 预留空间给悬浮的头像 */ | |||
} | |||
/* 用户昵称 */ | |||
.profile-name { | |||
font-size: 22px; | |||
font-weight: bold; | |||
color: #333; | |||
} | |||
/* 所在地 */ | |||
.profile-location { | |||
font-size: 14px; | |||
color: #666; | |||
margin-bottom: 5px; | |||
} | |||
/* 个人签名 */ | |||
.profile-bio { | |||
font-size: 14px; | |||
color: #444; | |||
padding: 0 15px; | |||
margin-bottom: 10px; | |||
} | |||
/* 编辑资料按钮 */ | |||
.edit-profile-btn { | |||
background: #BA68C8; | |||
color: white; | |||
padding: 10px 20px; | |||
border: none; | |||
border-radius: 6px; | |||
cursor: pointer; | |||
margin-bottom: 15px; | |||
font-size: 14px; | |||
} | |||
.edit-profile-btn:hover { | |||
background: #9C4ABC; | |||
} | |||
/* 修改个人资料 */ | |||
/* 遮罩层 */ | |||
.modal-overlay { | |||
display: none; | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
width: 100%; | |||
height: 100%; | |||
background: rgba(0, 0, 0, 0.5); | |||
/* 加深遮罩,提高可读性 */ | |||
z-index: 9998; | |||
} | |||
/* 弹窗样式 */ | |||
.profile-modal { | |||
display: none; | |||
position: fixed; | |||
top: 50%; | |||
left: 50%; | |||
transform: translate(-50%, -50%); | |||
width: 400px; | |||
background: #fff; | |||
padding: 25px; | |||
border-radius: 12px; | |||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); | |||
z-index: 9999; | |||
animation: fadeIn 0.3s ease-in-out; | |||
} | |||
/* 关闭按钮 */ | |||
.close-modal { | |||
position: absolute; | |||
top: 10px; | |||
right: 15px; | |||
font-size: 24px; | |||
cursor: pointer; | |||
color: #666; | |||
} | |||
.close-modal:hover { | |||
color: #000; | |||
} | |||
/* 输入框样式 */ | |||
.profile-modal input, | |||
.profile-modal textarea { | |||
width: 100%; | |||
padding: 10px; | |||
margin-top: 5px; | |||
margin-bottom: 15px; | |||
border: 1px solid #ddd; | |||
border-radius: 8px; | |||
font-size: 14px; | |||
} | |||
/* 按钮样式 */ | |||
.update-profile-btn { | |||
background: #9C4ABC; | |||
color: white; | |||
border: none; | |||
padding: 12px; | |||
border-radius: 8px; | |||
width: 100%; | |||
cursor: pointer; | |||
font-size: 16px; | |||
transition: background 0.2s; | |||
} | |||
.update-profile-btn:hover { | |||
background: #9C4ABC; | |||
} | |||
/* 更新成功通知 */ | |||
.profile-update-notice { | |||
position: fixed; | |||
top: 20px; | |||
left: 50%; | |||
transform: translateX(-50%); | |||
background: #4CAF50; | |||
color: white; | |||
padding: 12px 20px; | |||
border-radius: 8px; | |||
font-size: 16px; | |||
display: none; | |||
z-index: 10000; | |||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); | |||
animation: fadeInOut 3s ease-in-out; | |||
} | |||
/* 动画效果 */ | |||
@keyframes fadeIn { | |||
from { | |||
opacity: 0; | |||
transform: translate(-50%, -55%); | |||
} | |||
to { | |||
opacity: 1; | |||
transform: translate(-50%, -50%); | |||
} | |||
} | |||
@keyframes fadeInOut { | |||
0% { | |||
opacity: 0; | |||
transform: translate(-50%, -30px); | |||
} | |||
10% { | |||
opacity: 1; | |||
transform: translate(-50%, 0); | |||
} | |||
90% { | |||
opacity: 1; | |||
} | |||
100% { | |||
opacity: 0; | |||
transform: translate(-50%, -30px); | |||
} | |||
} | |||
/* 分栏布局 */ | |||
.main-content { | |||
display: flex; | |||
justify-content: space-between; | |||
margin: 20px; | |||
padding-left: 80px; | |||
padding-right: 80px; | |||
} | |||
/* 左栏:好友列表 */ | |||
.left-column { | |||
flex: 3; | |||
margin: 5px; | |||
padding: 10px; | |||
background: #F3E5F5; | |||
border-radius: 10px; | |||
box-shadow: 0 6px 12px rgba(180, 136, 255, 0.2); | |||
} | |||
.left-column h3 { | |||
text-align: center; | |||
font-size: 20px; | |||
margin-bottom: 10px; | |||
color: #6A1B9A; | |||
font-weight: bold; | |||
} | |||
/* 搜索框*/ | |||
.search-input { | |||
width: 100%; | |||
padding: 8px; | |||
margin-bottom: 5px; | |||
border-radius: 6px; | |||
border: 1px solid #ddd; | |||
font-size: 14px; | |||
outline: none; | |||
} | |||
.search-input:focus { | |||
border-color: #BA68C8; | |||
} | |||
/* 滑动好友列表容器 */ | |||
.friend-list-container { | |||
display: flex; | |||
flex-direction: column; | |||
padding: 5px; | |||
max-height: 250px; | |||
/* 限制最大高度 */ | |||
overflow-y: auto; | |||
/* 启用垂直滚动 */ | |||
margin-bottom: 15px; | |||
} | |||
/* 滚动条轨道 */ | |||
.friend-list-container::-webkit-scrollbar { | |||
width: 8px; | |||
/* 设置滚动条的宽度 */ | |||
} | |||
/* 滚动条滑块 */ | |||
.friend-list-container::-webkit-scrollbar-thumb { | |||
background-color: #BA68C8; | |||
/* 滑块的颜色 */ | |||
border-radius: 4px; | |||
/* 圆角 */ | |||
} | |||
/* 分割线 */ | |||
.divider { | |||
border: 1px solid #ffffff; | |||
margin-top: 20px; | |||
margin-bottom: 20px; | |||
} | |||
/* 好友分组标题 */ | |||
.friend-group { | |||
margin-bottom: 5px; | |||
} | |||
.group-title { | |||
display: flex; | |||
justify-content: space-between; | |||
font-size: 16px; | |||
font-weight: bold; | |||
color: #6A1B9A; | |||
cursor: pointer; | |||
padding: 5px 10px; | |||
} | |||
.group-title:hover { | |||
color: #9C27B0; | |||
} | |||
.group-members { | |||
display: none; | |||
} | |||
.group-members.show { | |||
display: block; | |||
} | |||
/* 好友条目 */ | |||
.friend-item { | |||
display: flex; | |||
align-items: center; | |||
background: white; | |||
padding: 10px; | |||
border-radius: 8px; | |||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); | |||
margin-bottom: 5px; | |||
cursor: pointer; | |||
transition: transform 0.2s, box-shadow 0.2s; | |||
} | |||
.friend-item:hover { | |||
transform: translateY(-4px); | |||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); | |||
} | |||
/* 在线/离线状态 */ | |||
.friend-item::before { | |||
content: ""; | |||
width: 10px; | |||
height: 10px; | |||
border-radius: 50%; | |||
margin-right: 10px; | |||
} | |||
.friend-item.online::before { | |||
background: green; | |||
} | |||
.friend-item.offline::before { | |||
background: gray; | |||
} | |||
/* 头像样式 */ | |||
.friend-item img { | |||
justify-self: left; | |||
width: 40px; | |||
height: 40px; | |||
border-radius: 50%; | |||
margin-right: 10px; | |||
border: 2px solid #E1BEE7; | |||
transition: transform 0.2s; | |||
} | |||
.friend-item img:hover { | |||
transform: scale(1.1); | |||
} | |||
/* 文字部分 */ | |||
.friend-item span { | |||
font-size: 14px; | |||
font-weight: bold; | |||
color: #5E35B1; | |||
} | |||
/* 私信按钮 */ | |||
.message-btn { | |||
background: #BA68C8; | |||
border: none; | |||
color: white; | |||
padding: 6px 8px; | |||
border-radius: 6px; | |||
cursor: pointer; | |||
transition: background 0.3s; | |||
margin-left: auto; | |||
} | |||
.message-btn:hover { | |||
background: #9C27B0; | |||
} | |||
.message-btn i { | |||
font-size: 16px; | |||
} | |||
/* 中栏:帖子区 */ | |||
.middle-column { | |||
flex: 6; | |||
margin: 5px; | |||
padding: 10px; | |||
background: #F3E5F5; | |||
border-radius: 6px; | |||
box-shadow: 0 4px 8px rgba(180, 136, 255, 0.2); | |||
} | |||
/* Tab容器样式 */ | |||
.tab { | |||
display: flex; | |||
justify-content: space-evenly; | |||
align-items: center; | |||
background-color: #ffffff; | |||
padding: 5px; | |||
border-radius: 8px; | |||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |||
margin: 0 auto; | |||
} | |||
/* Tab按钮样式 */ | |||
.tablinks { | |||
background-color: transparent; | |||
color: #6a1b9a; | |||
border: none; | |||
padding: 10px; | |||
font-size: 16px; | |||
cursor: pointer; | |||
text-align: left; | |||
/* 左对齐文字 */ | |||
display: flex; | |||
/* 使用flex布局 */ | |||
align-items: center; | |||
/* 垂直居中对齐内容 */ | |||
gap: 10px; | |||
/* 图标和文字之间的间距 */ | |||
transition: all 0.3s ease; | |||
margin: 5px; | |||
font-weight: 500; | |||
} | |||
/* 鼠标悬停时的样式 */ | |||
.tablinks:hover { | |||
background-color: #f3e5f5; | |||
color: #6a1b9a; | |||
transform: translateY(-2px); | |||
/* 鼠标悬停时按钮稍微上浮 */ | |||
} | |||
/* 激活状态的样式 */ | |||
.tablinks.active { | |||
border-bottom: 3px solid #6a1b9a; | |||
background-color: #f3e5f5; | |||
color: #6a1b9a; | |||
font-weight: bold; | |||
transform: translateY(-2px); | |||
/* 激活状态时按钮稍微上浮 */ | |||
} | |||
/* Tab内容区域 */ | |||
.tab-content { | |||
display: flex; | |||
align-items: center; | |||
} | |||
.tab-content p { | |||
font-size: 16px; | |||
margin-left: 5px; | |||
} | |||
/* 选项卡内容 */ | |||
.tabcontent { | |||
display: none; | |||
padding: 10px; | |||
} | |||
#myPosts { | |||
display: block; | |||
} | |||
/* 让所有 tab 下的帖子容器都适用 */ | |||
.postContainer { | |||
display: flex; | |||
flex-direction: column; | |||
/* 帖子纵向排列 */ | |||
gap: 10px; | |||
/* 帖子之间留间距 */ | |||
align-items: center; | |||
/* 居中对齐 */ | |||
} | |||
/* 单个帖子样式 */ | |||
.feed-post { | |||
width: 100%; | |||
max-width: 600px; | |||
margin: 0 auto; | |||
padding: 15px; | |||
border-radius: 8px; | |||
background-color: white; | |||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); | |||
transition: box-shadow 0.3s ease-in-out; | |||
} | |||
.feed-post:hover { | |||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); | |||
} | |||
/* 帖子头部对齐 */ | |||
.post-header { | |||
display: flex; | |||
align-items: center; | |||
gap: 10px; | |||
} | |||
.post-avatar img { | |||
width: 50px; | |||
height: 50px; | |||
border-radius: 50%; | |||
} | |||
.post-info { | |||
font-size: 14px; | |||
color: gray; | |||
} | |||
.post-user-name { | |||
font-size: 16px; | |||
font-weight: bold; | |||
color: #6A1B9A; | |||
} | |||
.post-time { | |||
font-size: 12px; | |||
color: gray; | |||
} | |||
/* 帖子正文 */ | |||
.post-content { | |||
font-size: 16px; | |||
color: #333; | |||
margin: 10px 0; | |||
line-height: 1.5; | |||
} | |||
/* 帖子操作按钮 */ | |||
/* 右上角扩展栏 */ | |||
.post-options { | |||
position: relative; | |||
margin-left: auto; | |||
cursor: pointer; | |||
} | |||
.post-options i { | |||
font-size: 18px; | |||
color: #888; | |||
transition: color 0.3s; | |||
} | |||
.post-options i:hover { | |||
color: #555; | |||
} | |||
/* 🔹 扩展菜单 */ | |||
.options-menu { | |||
position: absolute; | |||
right: 0; | |||
top: 25px; | |||
background: white; | |||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12); /* 柔和阴影 */ | |||
border-radius: 6px; | |||
display: none; | |||
flex-direction: column; | |||
min-width: 120px; /* 设置最小宽度 */ | |||
width: auto; /* 宽度根据内容自适应 */ | |||
padding: 5px 0; /* 让菜单项不会贴着边界 */ | |||
overflow: hidden; /* 避免内容溢出 */ | |||
} | |||
/* 🔹 菜单项 */ | |||
.options-menu button { | |||
background: none; | |||
border: none; | |||
padding: 8px 15px; /* 适当增加左右间距 */ | |||
text-align: left; | |||
width: 100%; | |||
cursor: pointer; | |||
font-size: 14px; | |||
color: #4A148C; /* 深紫色,符合整体风格 */ | |||
white-space: nowrap; /* 防止换行 */ | |||
transition: background 0.2s, color 0.2s; | |||
} | |||
.options-menu button i{ | |||
color: #4A148C; | |||
margin-right: 5px; | |||
} | |||
/* 🔹 悬浮时的美化 */ | |||
.options-menu button:hover { | |||
background: #f3e5f5; /* 柔和的淡紫色背景 */ | |||
color: #6A1B9A; /* 略深的紫色文本 */ | |||
} | |||
.options-menu button:hover { | |||
background: #f3e5f5; | |||
} | |||
/* 🔹 帖子交互区域 */ | |||
.post-actions { | |||
display: flex; | |||
justify-content: space-between; | |||
gap: 10px; | |||
margin-top: 10px; | |||
padding-top: 8px; | |||
border-top: 1px solid #e0cce5; /* 添加分隔线,使交互区域更融合 */ | |||
} | |||
/* 🔹 交互按钮美化 */ | |||
.post-actions button { | |||
background: none; /* 默认无背景,使其更自然 */ | |||
color: #8e44ad; /* 低饱和度紫色 */ | |||
border: none; | |||
padding: 4px 8px; | |||
font-size: 14px; | |||
cursor: pointer; | |||
display: flex; | |||
align-items: center; | |||
gap: 5px; | |||
transition: color 0.3s, transform 0.2s; | |||
} | |||
/* 🔹 悬浮时增加互动感 */ | |||
.post-actions button:hover { | |||
color: #6a1b9a; /* 悬浮时颜色稍深 */ | |||
transform: scale(1.05); /* 悬浮时略微放大 */ | |||
} | |||
/* 🔹 按钮内图标样式 */ | |||
.post-actions button i { | |||
font-size: 16px; | |||
transition: color 0.3s; | |||
} | |||
/* 🔹 按钮悬浮时图标颜色变化 */ | |||
.post-actions button:hover i { | |||
color: #6a1b9a; | |||
} | |||
/* 分页容器 */ | |||
.pagination { | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
margin-top: 15px; | |||
gap: 8px; | |||
} | |||
/* 分页按钮 */ | |||
.pagination button { | |||
padding: 8px 12px; | |||
border: none; | |||
background: #BA68C8; | |||
color: white; | |||
border-radius: 5px; | |||
cursor: pointer; | |||
transition: background 0.3s; | |||
} | |||
.pagination button:hover { | |||
background: #9C27B0; | |||
} | |||
/* 禁用按钮样式 */ | |||
.pagination button:disabled { | |||
background: #D1C4E9; | |||
cursor: not-allowed; | |||
} | |||
/* 分页信息 */ | |||
.page-info { | |||
font-size: 14px; | |||
color: #666; | |||
} | |||
/* 页码输入框 */ | |||
.page-input { | |||
width: 50px; | |||
padding: 6px; | |||
border: 1px solid #ccc; | |||
border-radius: 4px; | |||
text-align: center; | |||
} | |||
.page-input:focus { | |||
outline: 2px solid #BA68C8; | |||
} | |||
/* 右栏:通知区 */ | |||
/* 🎯 右栏整体布局 */ | |||
.right-column { | |||
flex: 3.5; | |||
margin: 5px; | |||
padding: 10px; | |||
background: #F3E5F5; | |||
border-radius: 6px; | |||
box-shadow: 0 4px 8px rgba(180, 136, 255, 0.2); | |||
} | |||
/* 📌 通知卡片基础样式 */ | |||
.notification-card { | |||
background: linear-gradient(135deg, #ffffff 0%, #f9f9f9 100%); | |||
padding: 15px; | |||
margin-bottom: 15px; | |||
border-radius: 10px; | |||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.08); | |||
transition: transform 0.2s ease, box-shadow 0.2s ease; | |||
} | |||
.notification-card:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12); | |||
} | |||
.notification-card h4 { | |||
font-size: 18px; | |||
margin-bottom: 10px; | |||
} | |||
/* 📅 日程管理 */ | |||
.todo-list { | |||
list-style: none; | |||
padding: 0; | |||
} | |||
.todo-list li { | |||
display: flex; | |||
align-items: center; | |||
gap: 8px; | |||
font-size: 14px; | |||
margin-bottom: 6px; | |||
cursor: pointer; | |||
transition: color 0.2s ease; | |||
} | |||
.todo-list li:hover { | |||
color: #6A1B9A; | |||
} | |||
/* 🏫 手语教室按钮 */ | |||
.join-class-btn { | |||
background: #6A1B9A; | |||
color: white; | |||
border: none; | |||
padding: 8px 12px; | |||
border-radius: 6px; | |||
cursor: pointer; | |||
margin-top: 10px; | |||
transition: background 0.3s ease, transform 0.2s ease; | |||
} | |||
.join-class-btn:hover { | |||
background: #5C1380; | |||
transform: scale(1.05); | |||
} | |||
/* 🔔 互动通知 */ | |||
.notifications ul { | |||
list-style: none; | |||
padding: 0; | |||
} | |||
.notifications li { | |||
display: flex; | |||
align-items: center; | |||
gap: 10px; | |||
font-size: 14px; | |||
margin-bottom: 6px; | |||
padding: 6px 8px; | |||
border-radius: 6px; | |||
transition: background 0.2s ease; | |||
cursor: pointer; | |||
} | |||
.notifications li:hover { | |||
background: rgba(106, 27, 154, 0.08); | |||
} | |||
.notifications img { | |||
width: 30px; | |||
height: 30px; | |||
border-radius: 50%; | |||
} | |||
/* 🌟 个性化推荐 */ | |||
.recommendations p { | |||
background: rgba(186, 104, 200, 0.15); | |||
padding: 8px 10px; | |||
border-radius: 6px; | |||
margin: 5px 0; | |||
font-size: 14px; | |||
transition: background 0.3s ease; | |||
cursor: pointer; | |||
} | |||
.recommendations p:hover { | |||
background: rgba(186, 104, 200, 0.3); | |||
} | |||
/* 🏆 每日签到 */ | |||
.checkin-btn { | |||
background: #BA68C8; | |||
color: white; | |||
border: none; | |||
padding: 6px 12px; | |||
border-radius: 5px; | |||
cursor: pointer; | |||
display: block; | |||
margin-top: 5px; | |||
transition: background 0.3s ease, transform 0.2s ease; | |||
} | |||
.checkin-btn:hover { | |||
background: #9C27B0; | |||
transform: scale(1.05); | |||
} | |||
/* 🎖️ 勋章墙 */ | |||
.badge-list { | |||
display: flex; | |||
flex-wrap: wrap; | |||
gap: 8px; | |||
margin-top: 10px; | |||
} | |||
.badge { | |||
background: rgba(186, 104, 200, 0.2); | |||
padding: 5px 10px; | |||
border-radius: 20px; | |||
font-size: 12px; | |||
color: #6A1B9A; | |||
} |
@ -0,0 +1,216 @@ | |||
[ | |||
[ | |||
{ | |||
"x": 0.6320064067840576, | |||
"y": 0.6639246344566345, | |||
"z": -2.3567096718579705e-8 | |||
}, | |||
{ | |||
"x": 0.6277491450309753, | |||
"y": 0.5509824156761169, | |||
"z": -0.02596704475581646 | |||
}, | |||
{ | |||
"x": 0.6014074683189392, | |||
"y": 0.46007394790649414, | |||
"z": -0.05003476142883301 | |||
}, | |||
{ | |||
"x": 0.5511794090270996, | |||
"y": 0.45773810148239136, | |||
"z": -0.07211336493492126 | |||
}, | |||
{ | |||
"x": 0.5334982872009277, | |||
"y": 0.5253194570541382, | |||
"z": -0.09125160425901413 | |||
}, | |||
{ | |||
"x": 0.6521157026290894, | |||
"y": 0.48740556836128235, | |||
"z": -0.06543701142072678 | |||
}, | |||
{ | |||
"x": 0.6120480895042419, | |||
"y": 0.44475099444389343, | |||
"z": -0.09859855473041534 | |||
}, | |||
{ | |||
"x": 0.6045635938644409, | |||
"y": 0.4603981375694275, | |||
"z": -0.11079375445842743 | |||
}, | |||
{ | |||
"x": 0.618733823299408, | |||
"y": 0.47828903794288635, | |||
"z": -0.1165243610739708 | |||
}, | |||
{ | |||
"x": 0.6504209637641907, | |||
"y": 0.5616504549980164, | |||
"z": -0.07005614787340164 | |||
}, | |||
{ | |||
"x": 0.5382657647132874, | |||
"y": 0.5441007614135742, | |||
"z": -0.098802350461483 | |||
}, | |||
{ | |||
"x": 0.5524492859840393, | |||
"y": 0.5471982359886169, | |||
"z": -0.0959862768650055 | |||
}, | |||
{ | |||
"x": 0.5857524871826172, | |||
"y": 0.5444830060005188, | |||
"z": -0.0920417457818985 | |||
}, | |||
{ | |||
"x": 0.6375548839569092, | |||
"y": 0.639783501625061, | |||
"z": -0.07619240134954453 | |||
}, | |||
{ | |||
"x": 0.5259181261062622, | |||
"y": 0.6331387758255005, | |||
"z": -0.09718460589647293 | |||
}, | |||
{ | |||
"x": 0.5531055927276611, | |||
"y": 0.6360833048820496, | |||
"z": -0.0775383710861206 | |||
}, | |||
{ | |||
"x": 0.595370352268219, | |||
"y": 0.6322951316833496, | |||
"z": -0.059992071241140366 | |||
}, | |||
{ | |||
"x": 0.6223289966583252, | |||
"y": 0.7055690884590149, | |||
"z": -0.08569794148206711 | |||
}, | |||
{ | |||
"x": 0.5446730852127075, | |||
"y": 0.6974737644195557, | |||
"z": -0.10043548792600632 | |||
}, | |||
{ | |||
"x": 0.5667818188667297, | |||
"y": 0.6949626207351685, | |||
"z": -0.08594515919685364 | |||
}, | |||
{ | |||
"x": 0.6028042435646057, | |||
"y": 0.6969892382621765, | |||
"z": -0.07056321948766708 | |||
} | |||
], | |||
[ | |||
{ | |||
"x": 0.6159699559211731, | |||
"y": 0.8369808793067932, | |||
"z": -3.8672851587762125e-7 | |||
}, | |||
{ | |||
"x": 0.6080248951911926, | |||
"y": 0.713093638420105, | |||
"z": -0.029090309515595436 | |||
}, | |||
{ | |||
"x": 0.5984982252120972, | |||
"y": 0.5673580169677734, | |||
"z": -0.04789982736110687 | |||
}, | |||
{ | |||
"x": 0.6059970259666443, | |||
"y": 0.45955491065979004, | |||
"z": -0.061475686728954315 | |||
}, | |||
{ | |||
"x": 0.6314979791641235, | |||
"y": 0.3899309039115906, | |||
"z": -0.0721668154001236 | |||
}, | |||
{ | |||
"x": 0.5945241451263428, | |||
"y": 0.6108418703079224, | |||
"z": -0.07783110439777374 | |||
}, | |||
{ | |||
"x": 0.5036718249320984, | |||
"y": 0.5679571032524109, | |||
"z": -0.12085898965597153 | |||
}, | |||
{ | |||
"x": 0.5202652812004089, | |||
"y": 0.6145290732383728, | |||
"z": -0.13886433839797974 | |||
}, | |||
{ | |||
"x": 0.5580585598945618, | |||
"y": 0.6238594651222229, | |||
"z": -0.1484847217798233 | |||
}, | |||
{ | |||
"x": 0.5865462422370911, | |||
"y": 0.7028706073760986, | |||
"z": -0.08085721731185913 | |||
}, | |||
{ | |||
"x": 0.4799419045448303, | |||
"y": 0.6708379983901978, | |||
"z": -0.11611701548099518 | |||
}, | |||
{ | |||
"x": 0.5056391358375549, | |||
"y": 0.7051485180854797, | |||
"z": -0.11365342885255814 | |||
}, | |||
{ | |||
"x": 0.5432829856872559, | |||
"y": 0.7081708312034607, | |||
"z": -0.1135244220495224 | |||
}, | |||
{ | |||
"x": 0.5734222531318665, | |||
"y": 0.7860034704208374, | |||
"z": -0.08634401112794876 | |||
}, | |||
{ | |||
"x": 0.48193442821502686, | |||
"y": 0.7633798122406006, | |||
"z": -0.11750930547714233 | |||
}, | |||
{ | |||
"x": 0.5072769522666931, | |||
"y": 0.7826641201972961, | |||
"z": -0.10152815282344818 | |||
}, | |||
{ | |||
"x": 0.546349287033081, | |||
"y": 0.7849104404449463, | |||
"z": -0.09001252055168152 | |||
}, | |||
{ | |||
"x": 0.5629417896270752, | |||
"y": 0.8544789552688599, | |||
"z": -0.0937301442027092 | |||
}, | |||
{ | |||
"x": 0.4911764860153198, | |||
"y": 0.8383667469024658, | |||
"z": -0.1155904158949852 | |||
}, | |||
{ | |||
"x": 0.5122630596160889, | |||
"y": 0.8497434854507446, | |||
"z": -0.10698479413986206 | |||
}, | |||
{ | |||
"x": 0.5474485754966736, | |||
"y": 0.8557199239730835, | |||
"z": -0.09859214723110199 | |||
} | |||
] | |||
] |
@ -0,0 +1,323 @@ | |||
[ | |||
[ | |||
{ | |||
"x": 0.5821961760520935, | |||
"y": 0.4449542164802551, | |||
"z": -9.344361160401604e-7 | |||
}, | |||
{ | |||
"x": 0.5408991575241089, | |||
"y": 0.3645845055580139, | |||
"z": -0.007521727588027716 | |||
}, | |||
{ | |||
"x": 0.49161839485168457, | |||
"y": 0.3183490037918091, | |||
"z": -0.021221067756414413 | |||
}, | |||
{ | |||
"x": 0.42823928594589233, | |||
"y": 0.32109713554382324, | |||
"z": -0.03742992505431175 | |||
}, | |||
{ | |||
"x": 0.374701589345932, | |||
"y": 0.3474753499031067, | |||
"z": -0.05756435915827751 | |||
}, | |||
{ | |||
"x": 0.5522622466087341, | |||
"y": 0.268653005361557, | |||
"z": -0.027303414419293404 | |||
}, | |||
{ | |||
"x": 0.46853742003440857, | |||
"y": 0.30156755447387695, | |||
"z": -0.04382767528295517 | |||
}, | |||
{ | |||
"x": 0.4176906943321228, | |||
"y": 0.32629409432411194, | |||
"z": -0.05896339938044548 | |||
}, | |||
{ | |||
"x": 0.39042484760284424, | |||
"y": 0.33372417092323303, | |||
"z": -0.0706745982170105 | |||
}, | |||
{ | |||
"x": 0.566277265548706, | |||
"y": 0.33881819248199463, | |||
"z": -0.04086381942033768 | |||
}, | |||
{ | |||
"x": 0.45396167039871216, | |||
"y": 0.38698825240135193, | |||
"z": -0.04755357652902603 | |||
}, | |||
{ | |||
"x": 0.40190404653549194, | |||
"y": 0.4009895920753479, | |||
"z": -0.05840885266661644 | |||
}, | |||
{ | |||
"x": 0.37351930141448975, | |||
"y": 0.4007323384284973, | |||
"z": -0.0743001326918602 | |||
}, | |||
{ | |||
"x": 0.5683354139328003, | |||
"y": 0.41107070446014404, | |||
"z": -0.05355175584554672 | |||
}, | |||
{ | |||
"x": 0.45562678575515747, | |||
"y": 0.45991820096969604, | |||
"z": -0.062346797436475754 | |||
}, | |||
{ | |||
"x": 0.40588200092315674, | |||
"y": 0.4656679630279541, | |||
"z": -0.0669768825173378 | |||
}, | |||
{ | |||
"x": 0.3785693049430847, | |||
"y": 0.4577176868915558, | |||
"z": -0.07633139193058014 | |||
}, | |||
{ | |||
"x": 0.5607712268829346, | |||
"y": 0.47670793533325195, | |||
"z": -0.06756220012903214 | |||
}, | |||
{ | |||
"x": 0.47159236669540405, | |||
"y": 0.524438202381134, | |||
"z": -0.07291802763938904 | |||
}, | |||
{ | |||
"x": 0.4328312873840332, | |||
"y": 0.5266100168228149, | |||
"z": -0.0667572095990181 | |||
}, | |||
{ | |||
"x": 0.4060453772544861, | |||
"y": 0.5178602337837219, | |||
"z": -0.0656297355890274 | |||
} | |||
], | |||
[ | |||
{ | |||
"x": 0.6141078472137451, | |||
"y": 0.6236022710800171, | |||
"z": 6.703137387376046e-7 | |||
}, | |||
{ | |||
"x": 0.5680003762245178, | |||
"y": 0.6110613942146301, | |||
"z": -0.023212945088744164 | |||
}, | |||
{ | |||
"x": 0.5315503478050232, | |||
"y": 0.5757322311401367, | |||
"z": -0.04985279217362404 | |||
}, | |||
{ | |||
"x": 0.5001187324523926, | |||
"y": 0.5689186453819275, | |||
"z": -0.07786590605974197 | |||
}, | |||
{ | |||
"x": 0.4662664234638214, | |||
"y": 0.5739880204200745, | |||
"z": -0.10950223356485367 | |||
}, | |||
{ | |||
"x": 0.5384328365325928, | |||
"y": 0.40875574946403503, | |||
"z": -0.0495375357568264 | |||
}, | |||
{ | |||
"x": 0.5277424454689026, | |||
"y": 0.29326391220092773, | |||
"z": -0.08207526803016663 | |||
}, | |||
{ | |||
"x": 0.5212165117263794, | |||
"y": 0.21330133080482483, | |||
"z": -0.10592856258153915 | |||
}, | |||
{ | |||
"x": 0.517288088798523, | |||
"y": 0.14644545316696167, | |||
"z": -0.1231740415096283 | |||
}, | |||
{ | |||
"x": 0.5493270754814148, | |||
"y": 0.4067770540714264, | |||
"z": -0.061399269849061966 | |||
}, | |||
{ | |||
"x": 0.5365923047065735, | |||
"y": 0.2741358280181885, | |||
"z": -0.09309384971857071 | |||
}, | |||
{ | |||
"x": 0.5305213332176208, | |||
"y": 0.18132558465003967, | |||
"z": -0.11716707795858383 | |||
}, | |||
{ | |||
"x": 0.528063952922821, | |||
"y": 0.09768706560134888, | |||
"z": -0.13444815576076508 | |||
}, | |||
{ | |||
"x": 0.5639584064483643, | |||
"y": 0.42088162899017334, | |||
"z": -0.07484324276447296 | |||
}, | |||
{ | |||
"x": 0.5528886318206787, | |||
"y": 0.29208576679229736, | |||
"z": -0.10330238938331604 | |||
}, | |||
{ | |||
"x": 0.545394778251648, | |||
"y": 0.2104230523109436, | |||
"z": -0.12457089871168137 | |||
}, | |||
{ | |||
"x": 0.5377002954483032, | |||
"y": 0.13965079188346863, | |||
"z": -0.13888947665691376 | |||
}, | |||
{ | |||
"x": 0.5833427309989929, | |||
"y": 0.44702526926994324, | |||
"z": -0.09025055915117264 | |||
}, | |||
{ | |||
"x": 0.5803030729293823, | |||
"y": 0.3531721532344818, | |||
"z": -0.1144757866859436 | |||
}, | |||
{ | |||
"x": 0.5725305676460266, | |||
"y": 0.2919052243232727, | |||
"z": -0.12837167084217072 | |||
}, | |||
{ | |||
"x": 0.5623204708099365, | |||
"y": 0.23511989414691925, | |||
"z": -0.13815775513648987 | |||
} | |||
], | |||
[ | |||
{ | |||
"x": 0.6159699559211731, | |||
"y": 0.8369808793067932, | |||
"z": -3.8672851587762125e-7 | |||
}, | |||
{ | |||
"x": 0.6080248951911926, | |||
"y": 0.713093638420105, | |||
"z": -0.029090309515595436 | |||
}, | |||
{ | |||
"x": 0.5984982252120972, | |||
"y": 0.5673580169677734, | |||
"z": -0.04789982736110687 | |||
}, | |||
{ | |||
"x": 0.6059970259666443, | |||
"y": 0.45955491065979004, | |||
"z": -0.061475686728954315 | |||
}, | |||
{ | |||
"x": 0.6314979791641235, | |||
"y": 0.3899309039115906, | |||
"z": -0.0721668154001236 | |||
}, | |||
{ | |||
"x": 0.5945241451263428, | |||
"y": 0.6108418703079224, | |||
"z": -0.07783110439777374 | |||
}, | |||
{ | |||
"x": 0.5036718249320984, | |||
"y": 0.5679571032524109, | |||
"z": -0.12085898965597153 | |||
}, | |||
{ | |||
"x": 0.5202652812004089, | |||
"y": 0.6145290732383728, | |||
"z": -0.13886433839797974 | |||
}, | |||
{ | |||
"x": 0.5580585598945618, | |||
"y": 0.6238594651222229, | |||
"z": -0.1484847217798233 | |||
}, | |||
{ | |||
"x": 0.5865462422370911, | |||
"y": 0.7028706073760986, | |||
"z": -0.08085721731185913 | |||
}, | |||
{ | |||
"x": 0.4799419045448303, | |||
"y": 0.6708379983901978, | |||
"z": -0.11611701548099518 | |||
}, | |||
{ | |||
"x": 0.5056391358375549, | |||
"y": 0.7051485180854797, | |||
"z": -0.11365342885255814 | |||
}, | |||
{ | |||
"x": 0.5432829856872559, | |||
"y": 0.7081708312034607, | |||
"z": -0.1135244220495224 | |||
}, | |||
{ | |||
"x": 0.5734222531318665, | |||
"y": 0.7860034704208374, | |||
"z": -0.08634401112794876 | |||
}, | |||
{ | |||
"x": 0.48193442821502686, | |||
"y": 0.7633798122406006, | |||
"z": -0.11750930547714233 | |||
}, | |||
{ | |||
"x": 0.5072769522666931, | |||
"y": 0.7826641201972961, | |||
"z": -0.10152815282344818 | |||
}, | |||
{ | |||
"x": 0.546349287033081, | |||
"y": 0.7849104404449463, | |||
"z": -0.09001252055168152 | |||
}, | |||
{ | |||
"x": 0.5629417896270752, | |||
"y": 0.8544789552688599, | |||
"z": -0.0937301442027092 | |||
}, | |||
{ | |||
"x": 0.4911764860153198, | |||
"y": 0.8383667469024658, | |||
"z": -0.1155904158949852 | |||
}, | |||
{ | |||
"x": 0.5122630596160889, | |||
"y": 0.8497434854507446, | |||
"z": -0.10698479413986206 | |||
}, | |||
{ | |||
"x": 0.5474485754966736, | |||
"y": 0.8557199239730835, | |||
"z": -0.09859214723110199 | |||
} | |||
] | |||
] |
@ -0,0 +1,430 @@ | |||
[ | |||
[ | |||
{ | |||
"x": 0.5766940712928772, | |||
"y": 0.6520374417304993, | |||
"z": -5.891508862987394e-7 | |||
}, | |||
{ | |||
"x": 0.55990070104599, | |||
"y": 0.564600944519043, | |||
"z": -0.041584618389606476 | |||
}, | |||
{ | |||
"x": 0.5691367983818054, | |||
"y": 0.4611101448535919, | |||
"z": -0.06258521229028702 | |||
}, | |||
{ | |||
"x": 0.5788955092430115, | |||
"y": 0.3869872987270355, | |||
"z": -0.07633058726787567 | |||
}, | |||
{ | |||
"x": 0.582440197467804, | |||
"y": 0.31022900342941284, | |||
"z": -0.0848177894949913 | |||
}, | |||
{ | |||
"x": 0.6187899112701416, | |||
"y": 0.5000607967376709, | |||
"z": -0.06497752666473389 | |||
}, | |||
{ | |||
"x": 0.5407440662384033, | |||
"y": 0.5068030953407288, | |||
"z": -0.1019575223326683 | |||
}, | |||
{ | |||
"x": 0.5392554998397827, | |||
"y": 0.5241277813911438, | |||
"z": -0.12286828458309174 | |||
}, | |||
{ | |||
"x": 0.5672339200973511, | |||
"y": 0.5229101777076721, | |||
"z": -0.1368086040019989 | |||
}, | |||
{ | |||
"x": 0.6329519152641296, | |||
"y": 0.5783523321151733, | |||
"z": -0.06159749999642372 | |||
}, | |||
{ | |||
"x": 0.5314179062843323, | |||
"y": 0.5986599922180176, | |||
"z": -0.09116323292255402 | |||
}, | |||
{ | |||
"x": 0.5409641861915588, | |||
"y": 0.602307915687561, | |||
"z": -0.0961412638425827 | |||
}, | |||
{ | |||
"x": 0.5735096335411072, | |||
"y": 0.5913326740264893, | |||
"z": -0.10176314413547516 | |||
}, | |||
{ | |||
"x": 0.6320325136184692, | |||
"y": 0.6550064086914062, | |||
"z": -0.06200835481286049 | |||
}, | |||
{ | |||
"x": 0.5383253693580627, | |||
"y": 0.6733283996582031, | |||
"z": -0.09224426746368408 | |||
}, | |||
{ | |||
"x": 0.5453531742095947, | |||
"y": 0.6689015030860901, | |||
"z": -0.08138420432806015 | |||
}, | |||
{ | |||
"x": 0.5751619935035706, | |||
"y": 0.6548710465431213, | |||
"z": -0.07285572588443756 | |||
}, | |||
{ | |||
"x": 0.6205134391784668, | |||
"y": 0.7178999185562134, | |||
"z": -0.06690721958875656 | |||
}, | |||
{ | |||
"x": 0.5517123341560364, | |||
"y": 0.7373326420783997, | |||
"z": -0.09246259927749634 | |||
}, | |||
{ | |||
"x": 0.5568342804908752, | |||
"y": 0.7287055253982544, | |||
"z": -0.09025078266859055 | |||
}, | |||
{ | |||
"x": 0.5817087292671204, | |||
"y": 0.719586968421936, | |||
"z": -0.08630824089050293 | |||
} | |||
], | |||
[ | |||
{ | |||
"x": 0.5766940712928772, | |||
"y": 0.6520374417304993, | |||
"z": -5.891508862987394e-7 | |||
}, | |||
{ | |||
"x": 0.55990070104599, | |||
"y": 0.564600944519043, | |||
"z": -0.041584618389606476 | |||
}, | |||
{ | |||
"x": 0.5691367983818054, | |||
"y": 0.4611101448535919, | |||
"z": -0.06258521229028702 | |||
}, | |||
{ | |||
"x": 0.5788955092430115, | |||
"y": 0.3869872987270355, | |||
"z": -0.07633058726787567 | |||
}, | |||
{ | |||
"x": 0.582440197467804, | |||
"y": 0.41022900342941284, | |||
"z": 0.3048177894949913 | |||
}, | |||
{ | |||
"x": 0.6187899112701416, | |||
"y": 0.5000607967376709, | |||
"z": -0.06497752666473389 | |||
}, | |||
{ | |||
"x": 0.5407440662384033, | |||
"y": 0.5068030953407288, | |||
"z": -0.1019575223326683 | |||
}, | |||
{ | |||
"x": 0.5392554998397827, | |||
"y": 0.5241277813911438, | |||
"z": -0.12286828458309174 | |||
}, | |||
{ | |||
"x": 0.5672339200973511, | |||
"y": 0.5229101777076721, | |||
"z": -0.1368086040019989 | |||
}, | |||
{ | |||
"x": 0.6329519152641296, | |||
"y": 0.5783523321151733, | |||
"z": -0.06159749999642372 | |||
}, | |||
{ | |||
"x": 0.5314179062843323, | |||
"y": 0.5986599922180176, | |||
"z": -0.09116323292255402 | |||
}, | |||
{ | |||
"x": 0.5409641861915588, | |||
"y": 0.602307915687561, | |||
"z": -0.0961412638425827 | |||
}, | |||
{ | |||
"x": 0.5735096335411072, | |||
"y": 0.5913326740264893, | |||
"z": -0.10176314413547516 | |||
}, | |||
{ | |||
"x": 0.6320325136184692, | |||
"y": 0.6550064086914062, | |||
"z": -0.06200835481286049 | |||
}, | |||
{ | |||
"x": 0.5383253693580627, | |||
"y": 0.6733283996582031, | |||
"z": -0.09224426746368408 | |||
}, | |||
{ | |||
"x": 0.5453531742095947, | |||
"y": 0.6689015030860901, | |||
"z": -0.08138420432806015 | |||
}, | |||
{ | |||
"x": 0.5751619935035706, | |||
"y": 0.6548710465431213, | |||
"z": -0.07285572588443756 | |||
}, | |||
{ | |||
"x": 0.6205134391784668, | |||
"y": 0.7178999185562134, | |||
"z": -0.06690721958875656 | |||
}, | |||
{ | |||
"x": 0.5517123341560364, | |||
"y": 0.7373326420783997, | |||
"z": -0.09246259927749634 | |||
}, | |||
{ | |||
"x": 0.5568342804908752, | |||
"y": 0.7287055253982544, | |||
"z": -0.09025078266859055 | |||
}, | |||
{ | |||
"x": 0.5817087292671204, | |||
"y": 0.719586968421936, | |||
"z": -0.08630824089050293 | |||
} | |||
], | |||
[ | |||
{ | |||
"x": 0.5766940712928772, | |||
"y": 0.6520374417304993, | |||
"z": -5.891508862987394e-7 | |||
}, | |||
{ | |||
"x": 0.55990070104599, | |||
"y": 0.564600944519043, | |||
"z": -0.041584618389606476 | |||
}, | |||
{ | |||
"x": 0.5691367983818054, | |||
"y": 0.4611101448535919, | |||
"z": -0.06258521229028702 | |||
}, | |||
{ | |||
"x": 0.5788955092430115, | |||
"y": 0.3869872987270355, | |||
"z": -0.07633058726787567 | |||
}, | |||
{ | |||
"x": 0.582440197467804, | |||
"y": 0.31022900342941284, | |||
"z": -0.0848177894949913 | |||
}, | |||
{ | |||
"x": 0.6187899112701416, | |||
"y": 0.5000607967376709, | |||
"z": -0.06497752666473389 | |||
}, | |||
{ | |||
"x": 0.5407440662384033, | |||
"y": 0.5068030953407288, | |||
"z": -0.1019575223326683 | |||
}, | |||
{ | |||
"x": 0.5392554998397827, | |||
"y": 0.5241277813911438, | |||
"z": -0.12286828458309174 | |||
}, | |||
{ | |||
"x": 0.5672339200973511, | |||
"y": 0.5229101777076721, | |||
"z": -0.1368086040019989 | |||
}, | |||
{ | |||
"x": 0.6329519152641296, | |||
"y": 0.5783523321151733, | |||
"z": -0.06159749999642372 | |||
}, | |||
{ | |||
"x": 0.5314179062843323, | |||
"y": 0.5986599922180176, | |||
"z": -0.09116323292255402 | |||
}, | |||
{ | |||
"x": 0.5409641861915588, | |||
"y": 0.602307915687561, | |||
"z": -0.0961412638425827 | |||
}, | |||
{ | |||
"x": 0.5735096335411072, | |||
"y": 0.5913326740264893, | |||
"z": -0.10176314413547516 | |||
}, | |||
{ | |||
"x": 0.6320325136184692, | |||
"y": 0.6550064086914062, | |||
"z": -0.06200835481286049 | |||
}, | |||
{ | |||
"x": 0.5383253693580627, | |||
"y": 0.6733283996582031, | |||
"z": -0.09224426746368408 | |||
}, | |||
{ | |||
"x": 0.5453531742095947, | |||
"y": 0.6689015030860901, | |||
"z": -0.08138420432806015 | |||
}, | |||
{ | |||
"x": 0.5751619935035706, | |||
"y": 0.6548710465431213, | |||
"z": -0.07285572588443756 | |||
}, | |||
{ | |||
"x": 0.6205134391784668, | |||
"y": 0.7178999185562134, | |||
"z": -0.06690721958875656 | |||
}, | |||
{ | |||
"x": 0.5517123341560364, | |||
"y": 0.7373326420783997, | |||
"z": -0.09246259927749634 | |||
}, | |||
{ | |||
"x": 0.5568342804908752, | |||
"y": 0.7287055253982544, | |||
"z": -0.09025078266859055 | |||
}, | |||
{ | |||
"x": 0.5817087292671204, | |||
"y": 0.719586968421936, | |||
"z": -0.08630824089050293 | |||
} | |||
], | |||
[ | |||
{ | |||
"x": 0.5766940712928772, | |||
"y": 0.6520374417304993, | |||
"z": -5.891508862987394e-7 | |||
}, | |||
{ | |||
"x": 0.55990070104599, | |||
"y": 0.564600944519043, | |||
"z": -0.041584618389606476 | |||
}, | |||
{ | |||
"x": 0.5691367983818054, | |||
"y": 0.4611101448535919, | |||
"z": -0.06258521229028702 | |||
}, | |||
{ | |||
"x": 0.5788955092430115, | |||
"y": 0.3869872987270355, | |||
"z": -0.07633058726787567 | |||
}, | |||
{ | |||
"x": 0.582440197467804, | |||
"y": 0.41022900342941284, | |||
"z": 0.3048177894949913 | |||
}, | |||
{ | |||
"x": 0.6187899112701416, | |||
"y": 0.5000607967376709, | |||
"z": -0.06497752666473389 | |||
}, | |||
{ | |||
"x": 0.5407440662384033, | |||
"y": 0.5068030953407288, | |||
"z": -0.1019575223326683 | |||
}, | |||
{ | |||
"x": 0.5392554998397827, | |||
"y": 0.5241277813911438, | |||
"z": -0.12286828458309174 | |||
}, | |||
{ | |||
"x": 0.5672339200973511, | |||
"y": 0.5229101777076721, | |||
"z": -0.1368086040019989 | |||
}, | |||
{ | |||
"x": 0.6329519152641296, | |||
"y": 0.5783523321151733, | |||
"z": -0.06159749999642372 | |||
}, | |||
{ | |||
"x": 0.5314179062843323, | |||
"y": 0.5986599922180176, | |||
"z": -0.09116323292255402 | |||
}, | |||
{ | |||
"x": 0.5409641861915588, | |||
"y": 0.602307915687561, | |||
"z": -0.0961412638425827 | |||
}, | |||
{ | |||
"x": 0.5735096335411072, | |||
"y": 0.5913326740264893, | |||
"z": -0.10176314413547516 | |||
}, | |||
{ | |||
"x": 0.6320325136184692, | |||
"y": 0.6550064086914062, | |||
"z": -0.06200835481286049 | |||
}, | |||
{ | |||
"x": 0.5383253693580627, | |||
"y": 0.6733283996582031, | |||
"z": -0.09224426746368408 | |||
}, | |||
{ | |||
"x": 0.5453531742095947, | |||
"y": 0.6689015030860901, | |||
"z": -0.08138420432806015 | |||
}, | |||
{ | |||
"x": 0.5751619935035706, | |||
"y": 0.6548710465431213, | |||
"z": -0.07285572588443756 | |||
}, | |||
{ | |||
"x": 0.6205134391784668, | |||
"y": 0.7178999185562134, | |||
"z": -0.06690721958875656 | |||
}, | |||
{ | |||
"x": 0.5517123341560364, | |||
"y": 0.7373326420783997, | |||
"z": -0.09246259927749634 | |||
}, | |||
{ | |||
"x": 0.5568342804908752, | |||
"y": 0.7287055253982544, | |||
"z": -0.09025078266859055 | |||
}, | |||
{ | |||
"x": 0.5817087292671204, | |||
"y": 0.719586968421936, | |||
"z": -0.08630824089050293 | |||
} | |||
] | |||
] |
@ -0,0 +1,71 @@ | |||
function openTab(evt, tabName) { | |||
var i, tabcontent, tablinks; | |||
tabcontent = document.getElementsByClassName("tabcontent"); | |||
for (i = 0; i < tabcontent.length; i++) { | |||
tabcontent[i].style.display = "none"; // 隐藏所有选项卡内容 | |||
} | |||
tablinks = document.getElementsByClassName("tablinks"); | |||
for (i = 0; i < tablinks.length; i++) { | |||
tablinks[i].className = tablinks[i].className.replace(" active", ""); // 移除活动状态 | |||
} | |||
document.getElementById(tabName).style.display = "block"; // 显示当前选中的内容 | |||
evt.currentTarget.className += " active"; // 给当前按钮添加活动状态 | |||
} | |||
// 动画监听绑定 | |||
document.querySelectorAll('.task-item').forEach(item => { | |||
item.addEventListener('mouseenter', () => { | |||
const bonus = item.querySelector('.bonus'); | |||
bonus.classList.add('show'); // 显示积分动画 | |||
}); | |||
item.addEventListener('mouseleave', () => { | |||
const bonus = item.querySelector('.bonus'); | |||
bonus.classList.remove('show'); // 隐藏积分动画 | |||
}); | |||
}); | |||
function flipCard(card) { | |||
card.classList.toggle('flipped'); | |||
} | |||
function openLesson(lessonNumber) { | |||
alert("你正在学习:" + lessonNumber + " 号课程"); | |||
} | |||
// ✅ 页面加载后再执行默认 tab 和视频绑定 | |||
document.addEventListener("DOMContentLoaded", function () { | |||
// 👉 默认点击 tab | |||
const defaultBtn = document.getElementById("defaultOpen"); | |||
if (defaultBtn) { | |||
defaultBtn.click(); | |||
} else { | |||
console.warn("#defaultOpen not found"); | |||
} | |||
// 👉 视频绑定 | |||
const standardVideo = document.getElementById("standardVideo"); | |||
const gestureSelect = document.getElementById("gestureSelect"); | |||
if (gestureSelect && standardVideo) { | |||
const videoMap = { | |||
"你好": "/static/videos/你好.mp4", | |||
"谢谢": "/static/videos/谢谢.mp4", | |||
"早上好": "/static/videos/早上好.mp4" | |||
}; | |||
// 默认设置一次 | |||
standardVideo.src = videoMap[gestureSelect.value]; | |||
standardVideo.load(); | |||
// 每次切换时更新 | |||
gestureSelect.addEventListener("change", function () { | |||
const selectedGesture = gestureSelect.value; | |||
if (videoMap[selectedGesture]) { | |||
standardVideo.src = videoMap[selectedGesture]; | |||
standardVideo.load(); | |||
} | |||
}); | |||
} | |||
}); |
@ -0,0 +1,625 @@ | |||
// 头像 | |||
function changeAvatar(event) { | |||
const input = event.target; | |||
const file = input.files[0]; | |||
if (file) { | |||
// 预览 | |||
const reader = new FileReader(); | |||
reader.onload = function (e) { | |||
document.getElementById("profileAvatar").src = e.target.result; | |||
}; | |||
reader.readAsDataURL(file); | |||
// 实时上传 | |||
const formData = new FormData(); | |||
formData.append("avatar", file); | |||
fetch("/MyPage/upload-avatar/", { | |||
method: "POST", | |||
headers: { | |||
"X-CSRFToken": getCookie("csrftoken") | |||
}, | |||
body: formData | |||
}) | |||
.then(response => response.json()) | |||
.then(data => { | |||
if (data.success) { | |||
console.log("头像上传成功"); | |||
} else { | |||
console.error("上传失败:", data.message); | |||
} | |||
}) | |||
.catch(error => { | |||
console.error("请求错误:", error); | |||
}); | |||
} | |||
} | |||
// 工具函数:获取 CSRF token | |||
function getCookie(name) { | |||
let cookieValue = null; | |||
if (document.cookie && document.cookie !== "") { | |||
const cookies = document.cookie.split(";"); | |||
for (let i = 0; i < cookies.length; i++) { | |||
const cookie = cookies[i].trim(); | |||
if (cookie.substring(0, name.length + 1) === name + "=") { | |||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); | |||
break; | |||
} | |||
} | |||
} | |||
return cookieValue; | |||
} | |||
// 个人资料背景图 | |||
function changeBanner(event) { | |||
const input = event.target; | |||
const file = input.files[0]; | |||
if (file) { | |||
// 本地预览 | |||
const reader = new FileReader(); | |||
reader.onload = function (e) { | |||
document.getElementById("profileBanner").style.backgroundImage = `url('${e.target.result}')`; | |||
}; | |||
reader.readAsDataURL(file); | |||
// 实时上传 | |||
const formData = new FormData(); | |||
formData.append("personal_background", file); | |||
fetch("/MyPage/upload-banner/", { | |||
method: "POST", | |||
headers: { | |||
"X-CSRFToken": getCookie("csrftoken") | |||
}, | |||
body: formData | |||
}) | |||
.then(response => response.json()) | |||
.then(data => { | |||
if (data.success) { | |||
console.log("背景图上传成功"); | |||
} else { | |||
console.error("上传失败:", data.message); | |||
} | |||
}) | |||
.catch(error => { | |||
console.error("请求错误:", error); | |||
}); | |||
} | |||
} | |||
// 页面背景 | |||
function changePageBg(event) { | |||
const input = event.target; | |||
const file = input.files[0]; | |||
if (file) { | |||
// 本地预览 | |||
const reader = new FileReader(); | |||
reader.onload = function (e) { | |||
document.body.style.backgroundImage = `url('${e.target.result}')`; | |||
}; | |||
reader.readAsDataURL(file); | |||
// 实时上传 | |||
const formData = new FormData(); | |||
formData.append("page_background", file); | |||
fetch("/MyPage/upload-pagebg/", { | |||
method: "POST", | |||
headers: { | |||
"X-CSRFToken": getCookie("csrftoken") | |||
}, | |||
body: formData | |||
}) | |||
.then(response => response.json()) | |||
.then(data => { | |||
if (data.success) { | |||
console.log("页面背景上传成功"); | |||
} else { | |||
console.error("上传失败:", data.message); | |||
} | |||
}) | |||
.catch(error => { | |||
console.error("请求错误:", error); | |||
}); | |||
} | |||
} | |||
// 打开弹窗 | |||
function openProfileEdit() { | |||
document.getElementById("profileModal").style.display = "block"; | |||
document.getElementById("modalOverlay").style.display = "block"; | |||
} | |||
// 关闭弹窗 | |||
function closeProfileEdit() { | |||
document.getElementById("profileModal").style.display = "none"; | |||
document.getElementById("modalOverlay").style.display = "none"; | |||
} | |||
// 显示更新成功通知 | |||
function showProfileUpdateNotice() { | |||
const notice = document.getElementById("profileUpdateNotice"); | |||
notice.style.display = "block"; | |||
// 3秒后自动消失 | |||
setTimeout(() => { | |||
notice.style.display = "none"; | |||
}, 3000); | |||
} | |||
// 中栏 | |||
function openTab(evt, tabName) { | |||
const tabcontent = document.getElementsByClassName("tabcontent"); | |||
for (let i = 0; i < tabcontent.length; i++) { | |||
tabcontent[i].style.display = "none"; | |||
} | |||
const tablinks = document.getElementsByClassName("tablinks"); | |||
for (let i = 0; i < tablinks.length; i++) { | |||
tablinks[i].classList.remove("active"); | |||
} | |||
document.getElementById(tabName).style.display = "block"; | |||
evt.currentTarget.classList.add("active"); | |||
} | |||
document.addEventListener("DOMContentLoaded", () => { | |||
document.getElementById("defaultOpen").click(); | |||
}); | |||
//删除帖子 | |||
function toggleOptionsMenuDelete(button) { | |||
const container = button.parentElement; | |||
const menu = container.querySelector('.options-menu'); | |||
// 关闭其他菜单 | |||
document.querySelectorAll('.options-menu').forEach(m => { | |||
if (m !== menu) m.style.display = 'none'; | |||
}); | |||
// 切换当前菜单 | |||
if (menu.style.display === 'block') { | |||
menu.style.display = 'none'; | |||
} else { | |||
menu.style.display = 'block'; | |||
} | |||
} | |||
// 点击空白处自动关闭 | |||
document.addEventListener('click', function (event) { | |||
if (!event.target.closest('.options-menu') && !event.target.closest('button')) { | |||
document.querySelectorAll('.options-menu').forEach(menu => { | |||
menu.style.display = 'none'; | |||
}); | |||
} | |||
}); | |||
// //自定义背景 | |||
// document.getElementById("bgInput").addEventListener("change", function () { | |||
// let file = this.files[0]; | |||
// if (file) { | |||
// let reader = new FileReader(); | |||
// reader.onload = function (e) { | |||
// document.body.style.backgroundImage = `url('${e.target.result}')`; | |||
// }; | |||
// reader.readAsDataURL(file); | |||
// } | |||
// }); | |||
// // 编辑个人资料 | |||
// // 打开弹窗 | |||
// function openProfileEdit() { | |||
// document.getElementById("profileModal").style.display = "block"; | |||
// document.getElementById("modalOverlay").style.display = "block"; | |||
// // 预填充信息 | |||
// document.getElementById("editName").value = document.querySelector(".profile-name").textContent; | |||
// document.getElementById("editLocation").value = document.querySelector(".profile-location").textContent.replace("📍 ", ""); | |||
// document.getElementById("editBio").value = document.querySelector(".profile-bio").textContent; | |||
// } | |||
// // 关闭弹窗 | |||
// function closeProfileEdit() { | |||
// document.getElementById("profileModal").style.display = "none"; | |||
// document.getElementById("modalOverlay").style.display = "none"; | |||
// } | |||
// // 更新资料 | |||
// document.getElementById("saveProfile").addEventListener("click", function () { | |||
// const newName = document.getElementById("editName").value.trim(); | |||
// const newLocation = document.getElementById("editLocation").value.trim(); | |||
// const newBio = document.getElementById("editBio").value.trim(); | |||
// if (newName) document.querySelector(".profile-name").textContent = newName; | |||
// if (newLocation) document.querySelector(".profile-location").innerHTML = `<i class="fas fa-map-marker-alt"></i> ${newLocation}`; | |||
// if (newBio) document.querySelector(".profile-bio").textContent = newBio; | |||
// // 关闭弹窗 | |||
// closeProfileEdit(); | |||
// // 显示更新成功通知 | |||
// showProfileUpdateNotice(); | |||
// }); | |||
// // 显示更新成功通知 | |||
// function showProfileUpdateNotice() { | |||
// const notice = document.getElementById("profileUpdateNotice"); | |||
// notice.style.display = "block"; | |||
// // 3秒后自动消失 | |||
// setTimeout(() => { | |||
// notice.style.display = "none"; | |||
// }, 3000); | |||
// } | |||
// // 更换个人资料背景图 | |||
// function changeBanner(event) { | |||
// const file = event.target.files[0]; | |||
// if (file) { | |||
// const reader = new FileReader(); | |||
// reader.onload = function (e) { | |||
// document.getElementById("profileBanner").style.backgroundImage = `url(${e.target.result})`; | |||
// }; | |||
// reader.readAsDataURL(file); | |||
// } | |||
// } | |||
// // 更换头像 | |||
// function changeAvatar(event) { | |||
// const file = event.target.files[0]; | |||
// if (file) { | |||
// const reader = new FileReader(); | |||
// reader.onload = function (e) { | |||
// document.getElementById("profileAvatar").src = e.target.result; | |||
// }; | |||
// reader.readAsDataURL(file); | |||
// } | |||
// } | |||
// // 好友显示区 | |||
// // 生成 DiceBear 头像 URL | |||
// function getAvatarUrl(id) { | |||
// return `https://api.dicebear.com/7.x/pixel-art/svg?seed=${id}`; | |||
// } | |||
// // 生成好友 HTML 结构 | |||
// function createFriendElement(friend) { | |||
// return ` | |||
// <div class="friend-item ${friend.status}"> | |||
// <img src="${getAvatarUrl(friend.id)}" alt="${friend.name} 头像" onclick="viewProfile('${friend.id}')"> | |||
// <span onclick="viewProfile('${friend.id}')">${friend.name}</span> | |||
// <button class="message-btn" onclick="sendMessage('${friend.id}')"> | |||
// <i class="fas fa-comment-dots"></i> | |||
// </button> | |||
// </div> | |||
// `; | |||
// } | |||
// // 按照姓名首字母排序 | |||
// function sortFriendsByName(friendsList) { | |||
// return friendsList.sort((a, b) => a.name.localeCompare(b.name, 'zh')); | |||
// } | |||
// // 渲染好友列表(按默认排序) | |||
// function renderFriends(searchTerm = "") { | |||
// const friendListContainer = document.getElementById("friendList"); | |||
// const specialGroup = document.getElementById("special-group"); | |||
// const otherGroup = document.getElementById("other-group"); | |||
// // 清空原有内容 | |||
// friendListContainer.innerHTML = ""; | |||
// specialGroup.innerHTML = ""; | |||
// otherGroup.innerHTML = ""; | |||
// // 按姓名首字母排序 | |||
// const sortedFriends = sortFriendsByName(friends); | |||
// sortedFriends.forEach(friend => { | |||
// const friendHTML = createFriendElement(friend); | |||
// if (searchTerm) { | |||
// // 进行搜索 | |||
// if (friend.name.toLowerCase().includes(searchTerm.toLowerCase())) { | |||
// friendListContainer.innerHTML += friendHTML; | |||
// } | |||
// } else { | |||
// // 默认展示所有好友(按首字母排序) | |||
// friendListContainer.innerHTML += friendHTML; | |||
// } | |||
// // 分组显示(保持不变) | |||
// if (friend.group === "special") { | |||
// specialGroup.innerHTML += friendHTML; | |||
// } else if (friend.group === "other") { | |||
// otherGroup.innerHTML += friendHTML; | |||
// } | |||
// }); | |||
// } | |||
// // 搜索好友(只影响主列表) | |||
// function searchFriends() { | |||
// let input = document.getElementById('friendSearch').value.trim(); | |||
// renderFriends(input); | |||
// } | |||
// // 展开/收起好友分组 | |||
// function toggleGroup(groupId) { | |||
// const group = document.getElementById(groupId); | |||
// const icon = document.getElementById(groupId + '-icon'); | |||
// group.classList.toggle("show"); | |||
// icon.classList.toggle("fa-chevron-up"); | |||
// icon.classList.toggle("fa-chevron-down"); | |||
// } | |||
// // 好友卡片操作 | |||
// function viewProfile(friendId) { | |||
// alert("查看 " + friendId + " 的主页"); | |||
// window.location.href = "../手语教室/SLClassroom.html"; | |||
// } | |||
// function sendMessage(friendId) { | |||
// alert("与 " + friendId + " 发送私信"); | |||
// window.location.href = "../手语教室/SLClassroom.html"; | |||
// } | |||
// // 页面加载时渲染好友列表 | |||
// document.addEventListener("DOMContentLoaded", () => renderFriends()); | |||
// // 默认展示n条帖子 | |||
// const postsPerPage = 5; | |||
// // 🟢 **主函数:切换 Tab 并渲染帖子** | |||
// function openTab(evt, tabName) { | |||
// var i, tabcontent, tablinks; | |||
// tabcontent = document.getElementsByClassName("tabcontent"); | |||
// for (i = 0; i < tabcontent.length; i++) { | |||
// tabcontent[i].style.display = "none"; | |||
// } | |||
// tablinks = document.getElementsByClassName("tablinks"); | |||
// for (i = 0; i < tablinks.length; i++) { | |||
// tablinks[i].className = tablinks[i].className.replace(" active", ""); | |||
// } | |||
// document.getElementById(tabName).style.display = "block"; | |||
// evt.currentTarget.className += " active"; | |||
// tabPages[tabName] = 1; // 切换 Tab 时重置到第一页 | |||
// renderPosts(tabName); | |||
// } | |||
// // 🟢 **渲染帖子列表** | |||
// function renderPosts(tabName) { | |||
// const postContainer = document.querySelector(`#${tabName} .postContainer`); | |||
// postContainer.innerHTML = ""; | |||
// const postList = postsData[tabName] || []; | |||
// const currentPage = tabPages[tabName]; | |||
// // 计算总页数 | |||
// const totalPages = Math.ceil(postList.length / postsPerPage) || 1; | |||
// // 计算分页范围 | |||
// const start = (currentPage - 1) * postsPerPage; | |||
// const end = start + postsPerPage; | |||
// const postsToShow = postList.slice(start, end); | |||
// // 渲染帖子 | |||
// postsToShow.forEach(post => { | |||
// const postElement = document.createElement("div"); | |||
// postElement.className = "feed-post"; | |||
// postElement.innerHTML = ` | |||
// <div class="post-header"> | |||
// <div class="post-avatar"> | |||
// <img src="https://api.dicebear.com/7.x/pixel-art/svg?seed=${post.user}321" alt="像素风头像"> | |||
// </div> | |||
// <div class="post-info"> | |||
// <p class="post-user-name">${post.user}</p> | |||
// <p class="post-time">${post.time}</p> | |||
// </div> | |||
// <!-- 🔹 右上角扩展栏 --> | |||
// <div class="post-options"> | |||
// <i class="fas fa-ellipsis-h" onclick="toggleOptionsMenu(this)"></i> | |||
// <div class="options-menu"> | |||
// <button onclick="toggleFavorite('${post.id}')"> | |||
// <i class="fas fa-heart"></i> 收藏 | |||
// </button> | |||
// <button onclick="gotoPage('${post.user}')"> | |||
// <i class="fas fa-flag"></i> 查看个人主页 | |||
// </button> | |||
// </div> | |||
// </div> | |||
// </div> | |||
// <div class="post-content"> | |||
// <p>${post.content}</p> | |||
// </div> | |||
// <!-- 🔹 帖子交互区 --> | |||
// <div class="post-actions"> | |||
// <button class="like-btn" onclick="toggleLike('${post.id}')"> | |||
// <i class="fas fa-thumbs-up"></i> <span id="likeCount-${post.id}">${post.likes}</span> | |||
// </button> | |||
// <button class="comment-btn" onclick="openComments('${post.id}')"> | |||
// <i class="fas fa-comment-dots"></i> 评论 | |||
// </button> | |||
// <button class="share-btn" onclick="sharePost('${post.id}')"> | |||
// <i class="fas fa-share"></i> 转发 | |||
// </button> | |||
// </div> | |||
// `; | |||
// postContainer.appendChild(postElement); | |||
// }); | |||
// // 更新分页信息 | |||
// document.querySelector(`#${tabName} .page-info`).textContent = `第 ${currentPage} 页 / 共 ${totalPages} 页`; | |||
// // 更新分页按钮状态 | |||
// document.querySelector(`#${tabName} .prev-btn`).disabled = currentPage === 1; | |||
// document.querySelector(`#${tabName} .next-btn`).disabled = currentPage === totalPages; | |||
// } | |||
// // 🟢 **分页功能** | |||
// function changePage(tabName, offset) { | |||
// const maxPage = Math.ceil(postsData[tabName].length / postsPerPage) || 1; | |||
// const newPage = tabPages[tabName] + offset; | |||
// if (newPage >= 1 && newPage <= maxPage) { | |||
// tabPages[tabName] = newPage; | |||
// renderPosts(tabName); | |||
// } | |||
// } | |||
// function jumpToPage(tabName) { | |||
// const input = document.querySelector(`#${tabName} .page-input`); | |||
// const maxPage = Math.ceil(postsData[tabName].length / postsPerPage) || 1; | |||
// const newPage = Math.max(1, Math.min(maxPage, parseInt(input.value, 10) || 1)); | |||
// if (newPage !== tabPages[tabName]) { | |||
// tabPages[tabName] = newPage; | |||
// renderPosts(tabName); | |||
// } | |||
// } | |||
// // 🟢 **页面加载时,默认打开第一个 Tab** | |||
// document.addEventListener("DOMContentLoaded", function () { | |||
// document.getElementById("defaultOpen").click(); | |||
// }); | |||
// // 帖子交互 | |||
// // 1️⃣ 切换收藏 | |||
// function toggleFavorite(postId) { | |||
// alert("已收藏帖子:" + postId); | |||
// } | |||
// // 2️⃣ 查看个人主页 | |||
// function gotoPage(postUser) { | |||
// alert("进入" + postUser + "的主页"); | |||
// window.location.href = "./myPage.html"; | |||
// } | |||
// // 3️⃣ 点赞帖子 | |||
// function toggleLike(postId) { | |||
// let likeCountElement = document.getElementById(`likeCount-${postId}`); | |||
// let currentLikes = parseInt(likeCountElement.innerText); | |||
// likeCountElement.innerText = currentLikes + 1; | |||
// } | |||
// // 4️⃣ 展开/隐藏扩展菜单 | |||
// function toggleOptionsMenu(iconElement) { | |||
// let menu = iconElement.nextElementSibling; | |||
// menu.style.display = menu.style.display === "flex" ? "none" : "flex"; | |||
// } | |||
// // 5️⃣ 打开评论框(示例) | |||
// function openComments(postId) { | |||
// alert("打开评论区:" + postId); | |||
// } | |||
// // 6️⃣ 转发帖子(示例) | |||
// function sharePost(postId) { | |||
// alert("分享帖子:" + postId); | |||
// } | |||
// 通知区 | |||
// 进入教室 | |||
// function gotoClassroom() { | |||
// window.location.href = "../手语教室/SLClassroom.html"; | |||
// } | |||
// // 其他互动 | |||
// document.addEventListener("DOMContentLoaded", function () { | |||
// const checkinBtn = document.querySelector(".checkin-btn"); | |||
// let checkinDays = 3; // 假设已签到3天 | |||
// // 🏆 签到功能 | |||
// checkinBtn.addEventListener("click", function () { | |||
// checkinBtn.innerText = "✅ 已签到"; | |||
// checkinBtn.disabled = true; | |||
// checkinBtn.style.background = "#9E9E9E"; | |||
// checkinDays += 1; | |||
// document.querySelector(".checkin-btn + p b").innerText = checkinDays; | |||
// // 签到成功提示 | |||
// let successMsg = document.createElement("div"); | |||
// successMsg.innerText = "🎉 签到成功!"; | |||
// successMsg.style.position = "fixed"; | |||
// successMsg.style.top = "50px"; | |||
// successMsg.style.right = "20px"; | |||
// successMsg.style.background = "#6A1B9A"; | |||
// successMsg.style.color = "white"; | |||
// successMsg.style.padding = "10px 15px"; | |||
// successMsg.style.borderRadius = "5px"; | |||
// successMsg.style.opacity = "0"; | |||
// successMsg.style.transition = "opacity 0.5s ease"; | |||
// document.body.appendChild(successMsg); | |||
// setTimeout(() => (successMsg.style.opacity = "1"), 100); | |||
// setTimeout(() => { | |||
// successMsg.style.opacity = "0"; | |||
// setTimeout(() => successMsg.remove(), 500); | |||
// }, 1500); | |||
// }); | |||
// // 🔔 互动通知 | |||
// document.querySelectorAll(".notifications li").forEach((item) => { | |||
// item.addEventListener("click", function () { | |||
// this.style.opacity = "0.6"; | |||
// }); | |||
// }); | |||
// // 📅 TODO 任务完成状态 | |||
// document.querySelectorAll(".todo-list li").forEach((task) => { | |||
// task.addEventListener("click", function () { | |||
// this.style.textDecoration = "line-through"; | |||
// this.style.opacity = "0.6"; | |||
// }); | |||
// }); | |||
// // 🔥 监听新互动消息(模拟) | |||
// setTimeout(() => { | |||
// let newNotif = document.createElement("li"); | |||
// newNotif.innerHTML = | |||
// '<img src="https://api.dicebear.com/7.x/pixel-art/svg?seed=5"> <b>王五</b> 给你发了私信'; | |||
// newNotif.style.color = "#6A1B9A"; | |||
// newNotif.style.fontWeight = "bold"; | |||
// document.querySelector(".notifications ul").prepend(newNotif); | |||
// }, 3000); | |||
// }); |
@ -0,0 +1,387 @@ | |||
import { | |||
FilesetResolver, | |||
HandLandmarker, | |||
} from "/static/js/mediapipe/vision_bundle.mjs"; | |||
let handLandmarker; | |||
let standardLandmarks = []; | |||
let frameScores = []; | |||
let matched = false; | |||
let canvas, ctx; | |||
let bestMatches = []; | |||
let standardPlayTimer = null; | |||
function normalizeLandmarks(landmarks) { | |||
if (!landmarks || landmarks.length !== 21) return []; | |||
const base = landmarks[0]; | |||
const normalized = landmarks.map(p => ({ | |||
x: p.x - base.x, | |||
y: p.y - base.y | |||
})); | |||
const scale = Math.sqrt( | |||
normalized.reduce((sum, p) => sum + p.x * p.x + p.y * p.y, 0) / normalized.length | |||
); | |||
return scale > 0 ? normalized.map(p => ({ x: p.x / scale, y: p.y / scale })) : normalized; | |||
} | |||
function drawCenteredLandmarks(ctx, landmarks, width, height) { | |||
ctx.clearRect(0, 0, width, height); | |||
const norm = normalizeLandmarks(landmarks); | |||
const xs = norm.map(p => p.x); | |||
const ys = norm.map(p => p.y); | |||
const minX = Math.min(...xs), maxX = Math.max(...xs); | |||
const minY = Math.min(...ys), maxY = Math.max(...ys); | |||
const scale = Math.min(width, height) / Math.max(maxX - minX, maxY - minY) * 0.8; | |||
const offsetX = width / 2 - (minX + maxX) / 2 * scale; | |||
const offsetY = height / 2 - (minY + maxY) / 2 * scale; | |||
const mapped = norm.map(p => ({ x: p.x * scale + offsetX, y: p.y * scale + offsetY })); | |||
const connections = [ | |||
[0, 1], [1, 2], [2, 3], [3, 4], | |||
[0, 5], [5, 6], [6, 7], [7, 8], | |||
[0, 9], [9, 10], [10, 11], [11, 12], | |||
[0, 13], [13, 14], [14, 15], [15, 16], | |||
[0, 17], [17, 18], [18, 19], [19, 20], | |||
]; | |||
ctx.strokeStyle = "#4f46e5"; | |||
ctx.lineWidth = 2; | |||
connections.forEach(([start, end]) => { | |||
ctx.beginPath(); | |||
ctx.moveTo(mapped[start].x, mapped[start].y); | |||
ctx.lineTo(mapped[end].x, mapped[end].y); | |||
ctx.stroke(); | |||
}); | |||
mapped.forEach(p => { | |||
ctx.beginPath(); | |||
ctx.arc(p.x, p.y, 3, 0, 2 * Math.PI); | |||
ctx.fillStyle = "#1d4ed8"; | |||
ctx.fill(); | |||
}); | |||
} | |||
async function playStandardFrames(frames) { | |||
const canvas = document.getElementById("standardCanvas"); | |||
const ctx = canvas.getContext("2d"); | |||
if (standardPlayTimer) clearInterval(standardPlayTimer); | |||
let frameIndex = 0; | |||
standardPlayTimer = setInterval(() => { | |||
if (!frames[frameIndex]) return; | |||
drawCenteredLandmarks(ctx, frames[frameIndex], canvas.width, canvas.height); | |||
frameIndex++; | |||
if (frameIndex >= frames.length) clearInterval(standardPlayTimer); | |||
}, 1000); | |||
} | |||
document.getElementById("replayBtn").addEventListener("click", () => { | |||
playStandardFrames(standardLandmarks); | |||
}); | |||
document.getElementById("gestureSelect").addEventListener("change", async function () { | |||
const word = this.value; | |||
const res = await fetch(`/static/data/${word}.json`); | |||
standardLandmarks = await res.json(); | |||
const video = document.getElementById("standardVideo"); | |||
video.src = `/static/videos/${word}.mp4`; | |||
playStandardFrames(standardLandmarks); | |||
}); | |||
function computeScore(user, standard) { | |||
if (!user || !standard || user.length !== 21 || standard.length !== 21) return 1.0; | |||
const normUser = normalizeLandmarks(user); | |||
const normStandard = normalizeLandmarks(standard); | |||
let total = 0, validCount = 0; | |||
for (let i = 0; i < 21; i++) { | |||
const u = normUser[i], s = normStandard[i]; | |||
if (!u || !s) continue; | |||
const dx = u.x - s.x, dy = u.y - s.y; | |||
total += Math.sqrt(dx * dx + dy * dy); | |||
validCount++; | |||
} | |||
return validCount > 0 ? total / validCount : 1.0; | |||
} | |||
function updateFeedback(score, final = false, matchedFrames = []) { | |||
const feedback = document.getElementById("feedback"); | |||
const avgMatchedScore = matchedFrames.length > 0 | |||
? matchedFrames.reduce((a, b) => a + (1 - b), 0) / matchedFrames.length | |||
: (1 - score); | |||
if (avgMatchedScore > 0.8) { | |||
feedback.textContent = `🎯 匹配优秀(得分 ${avgMatchedScore.toFixed(2)})`; | |||
feedback.className = "text-green-700 bg-green-100 ..."; | |||
} else if (avgMatchedScore > 0.6) { | |||
feedback.textContent = `🙂 大致相似(得分 ${avgMatchedScore.toFixed(2)})`; | |||
feedback.className = "text-yellow-700 bg-yellow-100 ..."; | |||
} else { | |||
feedback.textContent = final | |||
? `⚠️ 动作不匹配(平均得分 ${avgMatchedScore.toFixed(2)})` | |||
: `继续分析中...`; | |||
feedback.className = "text-gray-700 bg-gray-100 ..."; | |||
} | |||
} | |||
function drawAbsoluteLandmarks(ctx, landmarks, width, height) { | |||
if (!landmarks) return; | |||
const connections = [ | |||
[0, 1], [1, 2], [2, 3], [3, 4], | |||
[0, 5], [5, 6], [6, 7], [7, 8], | |||
[0, 9], [9, 10], [10, 11], [11, 12], | |||
[0, 13], [13, 14], [14, 15], [15, 16], | |||
[0, 17], [17, 18], [18, 19], [19, 20], | |||
]; | |||
const points = landmarks.map(p => ({ x: p.x * width, y: p.y * height })); | |||
ctx.strokeStyle = "#f43f5e"; | |||
ctx.lineWidth = 2; | |||
connections.forEach(([s, e]) => { | |||
ctx.beginPath(); | |||
ctx.moveTo(points[s].x, points[s].y); | |||
ctx.lineTo(points[e].x, points[e].y); | |||
ctx.stroke(); | |||
}); | |||
ctx.fillStyle = "#f43f5e"; | |||
points.forEach(p => { | |||
ctx.beginPath(); | |||
ctx.arc(p.x, p.y, 4, 0, 2 * Math.PI); | |||
ctx.fill(); | |||
}); | |||
} | |||
function displayBestMatches(matches) { | |||
const container = document.getElementById("topFramesContainer"); | |||
container.innerHTML = ""; | |||
if (!matches.length) return; | |||
matches.forEach(({ imageData, landmarks }) => { | |||
const card = document.createElement("canvas"); | |||
card.width = 320; | |||
card.height = 240; | |||
card.className = "rounded shadow"; | |||
const context = card.getContext("2d"); | |||
const img = new Image(); | |||
img.onload = () => { | |||
context.drawImage(img, 0, 0, 320, 240); | |||
drawAbsoluteLandmarks(context, landmarks, 320, 240); | |||
}; | |||
img.src = imageData; | |||
container.appendChild(card); | |||
}); | |||
} | |||
async function analyzeVideo(video) { | |||
const interval = 200; | |||
const duration = video.duration * 1000; | |||
const frameResults = []; | |||
for (let t = 0; t < duration; t += interval) { | |||
video.currentTime = t / 1000; | |||
await new Promise((res) => (video.onseeked = res)); | |||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height); | |||
const bitmap = await createImageBitmap(canvas); | |||
const result = await handLandmarker.detect(bitmap); | |||
if (result.landmarks.length > 0) { | |||
frameResults.push({ | |||
time: t, | |||
landmarks: result.landmarks[0], | |||
imageData: canvas.toDataURL(), | |||
}); | |||
} | |||
} | |||
frameScores = frameResults.map(() => 1.0); | |||
const topFrames = []; | |||
const matchedFrameScores = []; | |||
standardLandmarks.forEach((standard, idx) => { | |||
let best = { score: Infinity, index: -1 }; | |||
frameResults.forEach((frame, i) => { | |||
const score = computeScore(frame.landmarks, standard); | |||
if (score < best.score) { | |||
best = { score, index: i }; | |||
} | |||
frameScores[i] = Math.min(frameScores[i], score); | |||
}); | |||
if (best.index >= 0) { | |||
topFrames.push(frameResults[best.index]); | |||
matchedFrameScores.push(best.score); | |||
} | |||
}); | |||
const matchRatio = frameScores.filter(s => 1 - s >= 0.75).length / frameScores.length; | |||
const showMatch = matchRatio > 0; | |||
const container = document.getElementById("topFramesContainer"); | |||
container.style.display = showMatch ? "flex" : "none"; | |||
if (!showMatch) { | |||
document.getElementById("feedback").textContent = "❌ 未识别出有效手势,请重新尝试。"; | |||
container.innerHTML = ""; | |||
return; | |||
} | |||
updateFeedback(0, true, matchedFrameScores); | |||
displayBestMatches(topFrames); | |||
} | |||
document.getElementById("videoUpload").addEventListener("change", function () { | |||
const file = this.files[0]; | |||
if (!file) return; | |||
const video = document.getElementById("uploadedVideo"); | |||
video.src = URL.createObjectURL(file); | |||
video.onloadedmetadata = async () => { | |||
matched = false; | |||
frameScores = []; | |||
canvas = document.getElementById("hiddenCanvas"); | |||
ctx = canvas.getContext("2d"); | |||
console.log("📽️ 视频元信息已加载,开始处理"); | |||
await analyzeVideo(video); | |||
}; | |||
}); | |||
async function setup() { | |||
const vision = await FilesetResolver.forVisionTasks( | |||
'/static/js/mediapipe/wasm' | |||
); | |||
handLandmarker = await HandLandmarker.createFromOptions(vision, { | |||
baseOptions: { | |||
modelAssetPath: "/static/models/hand_landmarker.task", | |||
}, | |||
runningMode: "IMAGE", | |||
numHands: 1, | |||
}); | |||
const initWord = document.getElementById("gestureSelect").value; | |||
const res = await fetch(`/static/data/${initWord}.json`); | |||
standardLandmarks = await res.json(); | |||
const standardVideo = document.getElementById("standardVideo"); | |||
if (standardVideo) { | |||
standardVideo.src = `/static/videos/${initWord}.mp4`; | |||
standardVideo.style.maxWidth = "100%"; | |||
standardVideo.style.height = "auto"; | |||
} | |||
playStandardFrames(standardLandmarks); | |||
console.log("✅ 模型和标准姿势加载完毕"); | |||
} | |||
setup(); | |||
// 选择框 | |||
const cardVideo = document.getElementById("card-video"); | |||
const cardLive = document.getElementById("card-live"); | |||
const uploadSection = document.getElementById("uploadSection"); | |||
const liveSection = document.getElementById("liveSection"); | |||
// 初始状态 | |||
setActiveMode("video"); | |||
// 切换处理 | |||
cardVideo.addEventListener("click", () => setActiveMode("video")); | |||
cardLive.addEventListener("click", () => { | |||
setActiveMode("live"); | |||
startLiveCamera(); // ⬅️ 显式调用摄像头 | |||
}); | |||
function setActiveMode(mode) { | |||
if (mode === "video") { | |||
uploadSection.style.display = "block"; | |||
liveSection.style.display = "none"; | |||
cardVideo.classList.add("ring-2", "ring-blue-500", "bg-blue-50"); | |||
cardLive.classList.remove("ring-2", "ring-blue-500", "bg-blue-50"); | |||
} else { | |||
uploadSection.style.display = "none"; | |||
liveSection.style.display = "block"; | |||
cardLive.classList.add("ring-2", "ring-blue-500", "bg-blue-50"); | |||
cardVideo.classList.remove("ring-2", "ring-blue-500", "bg-blue-50"); | |||
} | |||
} | |||
let liveStream = null; | |||
let liveTimer = null; | |||
const liveCanvas = document.createElement("canvas"); | |||
const liveCtx = liveCanvas.getContext("2d"); | |||
async function startLiveCamera() { | |||
const video = document.getElementById("liveVideo"); | |||
video.style.transform = "scaleX(-1)"; | |||
try { | |||
liveStream = await navigator.mediaDevices.getUserMedia({ video: true }); | |||
video.srcObject = liveStream; | |||
video.onloadedmetadata = () => { | |||
video.play(); | |||
liveCanvas.width = video.videoWidth; | |||
liveCanvas.height = video.videoHeight; | |||
}; | |||
document.getElementById("startLiveBtn").classList.add("hidden"); | |||
document.getElementById("stopLiveBtn").classList.remove("hidden"); | |||
liveLoop(); | |||
} catch (err) { | |||
alert("🚫 无法访问摄像头: " + err.message); | |||
} | |||
} | |||
function stopLiveCamera() { | |||
if (liveStream) { | |||
liveStream.getTracks().forEach(track => track.stop()); | |||
liveStream = null; | |||
} | |||
cancelAnimationFrame(liveTimer); | |||
document.getElementById("startLiveBtn").classList.remove("hidden"); | |||
document.getElementById("stopLiveBtn").classList.add("hidden"); | |||
document.getElementById("liveFeedback").textContent = "已停止实时检测。"; | |||
} | |||
function mirrorLandmarksX(landmarks) { | |||
return landmarks.map(p => ({ | |||
x: 1 - p.x, | |||
y: p.y | |||
})); | |||
} | |||
async function liveLoop() { | |||
const video = document.getElementById("liveVideo"); | |||
liveCtx.drawImage(video, 0, 0, liveCanvas.width, liveCanvas.height); | |||
const bitmap = await createImageBitmap(liveCanvas); | |||
const result = await handLandmarker.detect(bitmap); | |||
if (result.landmarks.length > 0) { | |||
const mirroredLandmarks = mirrorLandmarksX(result.landmarks[0]); | |||
let bestScore = Infinity; | |||
for (const standard of standardLandmarks) { | |||
const score = computeScore(mirroredLandmarks, standard); | |||
bestScore = Math.min(bestScore, score); | |||
} | |||
const finalScore = 1 - bestScore; | |||
const label = finalScore > 0.8 ? "🎯 匹配优秀" | |||
: finalScore > 0.6 ? "🙂 大致相似" | |||
: "⚠️ 动作不匹配"; | |||
document.getElementById("liveFeedback").textContent = `${label}(得分 ${finalScore.toFixed(2)})`; | |||
} else { | |||
document.getElementById("liveFeedback").textContent = "未检测到手部。"; | |||
} | |||
liveTimer = requestAnimationFrame(liveLoop); | |||
} | |||
// === 绑定按钮 === // | |||
document.getElementById("startLiveBtn").addEventListener("click", startLiveCamera); | |||
document.getElementById("stopLiveBtn").addEventListener("click", stopLiveCamera); |
@ -0,0 +1,474 @@ | |||
{% load static %} | |||
<!DOCTYPE html> | |||
<html lang="zh-CN"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
<title>手语社区 - 动态</title> | |||
<link rel="stylesheet" href="{% static 'css/Community.css' %}"> | |||
<link rel="stylesheet" href="{% static 'css/login_button.css' %}"> | |||
<!-- 最新免费版 Font Awesome --> | |||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |||
<script src=https://cdn.tailwindcss.com></script> | |||
<script> | |||
tailwind.config = { | |||
darkMode: `class` | |||
} | |||
</script> | |||
</head> | |||
<body> | |||
<header class="py-6 px-4 md:px-8 flex justify-between items-center"> | |||
<div class="absolute left-1/2 transform -translate-x-1/2"> | |||
<a class="text-3xl md:text-4xl font-bold fade-in">WaveSign手语通</a> | |||
<p class="text-lg md:text-xl opacity-80 fade-in mt-2">连接双手与世界,构建无声的桥梁 | |||
</p> | |||
</div> | |||
<div class="flex items-center space-x-4 ml-auto"> | |||
{% if user.is_authenticated %} | |||
<div class="user-menu-box-container"> | |||
<a href="{% url 'my_page' %}" class="user-menu-box-button"> | |||
{% if user.avatar %} | |||
<img src="{{ user.avatar.url }}" alt="头像" class="user-menu-box-avatar"> | |||
{% else %} | |||
<img src="{% static 'images/default_avatar.png' %}" alt="默认头像" class="user-menu-box-avatar"> | |||
{% endif %} | |||
{{ user.username }} | |||
</a> | |||
<div class="user-menu-box-dropdown"> | |||
<a href="{% url 'my_page' %}">进入个人主页</a> | |||
<a href="{% url 'logout' %}">退出登录</a> | |||
</div> | |||
</div> | |||
{% else %} | |||
<a href="{% url 'login' %}" class="user-menu-box-login"> | |||
<i class="fas fa-user"></i> 登录 | |||
</a> | |||
{% endif %} | |||
</div> | |||
</header> | |||
<nav class="sticky top-0 bg-opacity-90 backdrop-blur-sm py-4 px-4 md:px-8 z-50 fade-in" | |||
style="background-color:var(--card-bg)"> | |||
<div class="container mx-auto flex justify-center space-x-8"> | |||
<a class="nav-link text-lg font-medium" href="{% url 'home' %}" style="color:var(--text-color)">首页</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'sl_classroom' %}" | |||
style="color:var(--text-color)">手语教室</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'life_serving' %}" | |||
style="color:var(--text-color)">生活服务</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'community' %}" | |||
style="color:var(--text-color)">手语社区</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'schedule' %}" style="color:var(--text-color)">日程管理</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'my_page' %}" style="color:var(--text-color)">我的</a> | |||
</div> | |||
</nav> | |||
<nav class="navbar"> | |||
<div class="logo"> | |||
<span class="title-highlight"><i class="fa-regular fa-hand-spock"></i>手语社区 | |||
</div></span> | |||
<div class="search-box"> | |||
<input type="text" class="search-input" placeholder="搜索动态或用户..."> | |||
<button class="search-button">🔍</button> | |||
</div> | |||
</nav> | |||
<h1 class="section-title"></h1> | |||
<div class="container"> | |||
<div class="post-list"> | |||
<!-- 发布框 --> | |||
<form method="post" action="/Community/create_post/" enctype="multipart/form-data"> | |||
{% csrf_token %} | |||
<div class="post-editor"> | |||
<textarea class="editor-input" placeholder="分享你的想法或手语视频..." name="content"></textarea> | |||
<div class="post-actions"> | |||
<div class="action-buttons"> | |||
<button type="button" onclick="document.getElementById('image').click()" title="添加图片"> | |||
📸 图片 | |||
</button> | |||
<input type="file" id="image" name="image" accept="image/*" hidden> | |||
<button type="button" onclick="document.getElementById('video').click()" title="添加视频"> | |||
🎥 视频 | |||
</button> | |||
<input type="file" id="video" name="video" accept="video/*" hidden> | |||
<button type="button" onclick="addTag()" title="添加标签"> | |||
🏷️ 标签 | |||
</button> | |||
<input type="text" id="tags" name="tags" placeholder="#标签" hidden> | |||
</div> | |||
<button type="submit" class="post-button">发布</button> | |||
</div> | |||
</div> | |||
</form> | |||
<!-- 动态示例 --> | |||
<!-- <div class="post-card"> | |||
<div class="post-header"> | |||
<img src="{% static 'images/proflie1.png'%}" class="user-avatar" alt="用户头像"> | |||
<div> | |||
<h3>手语达人小王</h3> | |||
<p>2小时前 · 📍北京</p> | |||
</div> | |||
</div> | |||
<div class="post-content"> | |||
<p>今天的手语教学视频来啦!教大家"谢谢"和"不客气"的表达方式 👇</p> | |||
</div> | |||
<div class="post-media"> | |||
<video controls style="max-width: 50%; height: auto;"> | |||
<source src="{% static 'images/community_video.mp4'%}" type="video/mp4"> | |||
</video> | |||
</div> | |||
<div class="post-tags"> | |||
<span class="tag">#手语教学</span> | |||
<span class="tag">#每日一课</span> | |||
</div> | |||
<div class="post-actions"> | |||
<div class="action-button">❤️ 1.2万</div> | |||
<div class="action-button">💬 845</div> | |||
<div class="action-button">🔄 326</div> | |||
</div> | |||
</div> | |||
<div class="post-card"> | |||
<div class="post-header"> | |||
<img src="{% static 'images/profile2.png'%}" class="user-avatar" alt="用户头像"> | |||
<div> | |||
<h3>努力生长的小苗</h3> | |||
<p>1天前 · 📍上海</p> | |||
</div> | |||
</div> | |||
<div class="post-content"> | |||
<p>好开心!今天收到社区的通知,发布了好多适合咱们聋哑人的岗位,感受到了满满爱意~</p> | |||
</div> | |||
<div class="post-tags"> | |||
<span class="tag">#工作岗位</span> | |||
<span class="tag">#心情分享</span> | |||
</div> | |||
<div class="post-actions"> | |||
<div class="action-button">❤️ 3208</div> | |||
<div class="action-button">💬 45</div> | |||
<div class="action-button">🔄 285</div> | |||
</div> | |||
</div> | |||
<div class="post-card"> | |||
<div class="post-header"> | |||
<img src="{% static 'images/profile3.png'%}" class="user-avatar" alt="用户头像"> | |||
<div> | |||
<h3>超超家的花</h3> | |||
<p>1天前 · 📍云南</p> | |||
</div> | |||
</div> | |||
<div class="post-content"> | |||
<p>给猫猫草按时浇水的第9天,已经长出3cm高了!感谢好天气</p> | |||
</div> | |||
<div class="post-tags"> | |||
<span class="tag">#种植</span> | |||
<span class="tag">#心情分享</span> | |||
</div> | |||
<div class="post-actions"> | |||
<div class="action-button">❤️ 227</div> | |||
<div class="action-button">💬 85</div> | |||
<div class="action-button">🔄 32</div> | |||
</div> | |||
</div> --> | |||
{% for post in posts %} | |||
<div class="post-card"> | |||
<!-- 用户信息区 --> | |||
<div class="post-header relative"> | |||
{% if post.user.avatar %} | |||
<img class="w-10 h-10 rounded-full object-cover" src="{{ post.user.avatar.url }}" | |||
class="user-avatar" alt="用户头像"> | |||
{% else %} | |||
<img src="{% static 'images/default_avatar.png' %}" class="user-avatar" alt="默认头像"> | |||
{% endif %} | |||
<div> | |||
<h3>{{ post.user.username }}</h3> | |||
<p>{{ post.created_at|timesince }}前</p> | |||
</div> | |||
<!-- 关注按钮 --> | |||
{% if request.user != post.user %} | |||
<button | |||
class="absolute top-2 right-2 bg-blue-500 text-white text-sm px-3 py-1 rounded hover:bg-blue-600 follow-btn action-button" | |||
data-user-id="{{ post.user.id }}"> | |||
<i class="fas {% if post.is_followed %}fa-user-check{% else %}fa-user-plus{% endif %}"></i> | |||
<span class="follow-text"> | |||
{% if post.is_followed %}已关注{% else %}关注{% endif %} | |||
</span> | |||
</button> | |||
{% endif %} | |||
</div> | |||
<!-- 帖子内容 --> | |||
<div class="post-content"> | |||
<p>{{ post.content }}</p> | |||
</div> | |||
<!-- 媒体文件(视频/图片) --> | |||
<div class="post-media"> | |||
{% if post.image %} | |||
<img src="{{ post.image.url }}" style="max-width: 100%;" alt="帖子图片"> | |||
{% endif %} | |||
{% if post.video %} | |||
<video controls style="max-width: 50%; height: auto;"> | |||
<source src="{{ post.video.url }}" type="video/mp4"> | |||
</video> | |||
{% endif %} | |||
</div> | |||
<!-- 互动按钮 --> | |||
<div class="post-actions"> | |||
<!-- ❤️ 点赞 --> | |||
<button class="action-button like-btn" data-post-id="{{ post.id }}"> | |||
<i class="fa-regular fa-heart"></i> {{ post.like_count }} | |||
</button> | |||
<!-- 💬 评论 --> | |||
<div class="flex justify-between items-center mb-2"> | |||
<div class="text-sm text-gray-600"> | |||
<i class="fa-regular fa-comment-dots"></i> {{ post.comment_count }} | |||
</div> | |||
</div> | |||
<!-- ⭐ 收藏 --> | |||
<button class="action-button favorite-btn" data-post-id="{{ post.id }}"> | |||
<i class="fa-regular fa-bookmark"></i> {{ post.favorite_count }} | |||
</button> | |||
</div> | |||
<!-- 评论区 --> | |||
<div class="post-comments"> | |||
<div class="comment-list" id="comment-list-{{ post.id }}"> | |||
{% for comment in post.comment_set.all %} | |||
<div class="comment-item"> | |||
<strong>{{ comment.user.username }}</strong>: {{ comment.content }} | |||
</div> | |||
{% endfor %} | |||
</div> | |||
<form class="comment-form flex items-center mt-2 gap-2" data-post-id="{{ post.id }}"> | |||
<input type="text" name="content" | |||
class="flex-1 px-3 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-400" | |||
placeholder="写下你的评论..." required> | |||
<button type="submit" | |||
class="px-4 py-2 bg-blue-500 text-white rounded-xl hover:bg-blue-600 transition">发送</button> | |||
</form> | |||
</div> | |||
</div> | |||
{% endfor %} | |||
</div> | |||
<!-- 侧边栏 --> | |||
<div class="sidebar"> | |||
<div class="trending-topics"> | |||
<h3>热门话题</h3> | |||
<div class="topic-item">#无障碍设施指南</div> | |||
<div class="topic-item">#手语故事分享</div> | |||
<div class="topic-item">#就业信息速递</div> | |||
<div class="topic-item">#手语诗歌创作</div> | |||
</div> | |||
</div> | |||
</div> | |||
<hr class="dashed"> | |||
<footer class="py-8 px-4 md:px-8 text-center fade-in" style="animation-delay:1.2s"> | |||
<div class="mx-auto max-w-4xl"> <!-- 典型内容宽度 --> | |||
<div class="flex flex-col items-center justify-center"> | |||
<div class="mb-4 text-lg font-medium">作者: backpack</div> | |||
<p class="text-sm opacity-70">© 2025 WaveSign手语通.</p> | |||
</div> | |||
</div> | |||
</footer> | |||
<script> | |||
const isLoggedIn = {{ user.is_authenticated|yesno:"true,false"|safe }}; | |||
// 获取 CSRF Token 辅助函数 | |||
function getCookie(name) { | |||
let cookieValue = null; | |||
if (document.cookie && document.cookie !== '') { | |||
const cookies = document.cookie.split(';'); | |||
for (let cookie of cookies) { | |||
cookie = cookie.trim(); | |||
if (cookie.startsWith(name + '=')) { | |||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); | |||
break; | |||
} | |||
} | |||
} | |||
return cookieValue; | |||
} | |||
// 点赞按钮 | |||
document.querySelectorAll('.like-btn').forEach(button => { | |||
button.addEventListener('click', () => { | |||
if (!isLoggedIn) { | |||
alert("请先登录再点赞!"); | |||
window.location.href = "/users/login/"; | |||
return; | |||
} | |||
const postId = button.getAttribute('data-post-id'); | |||
fetch(`/Community/like_post/${postId}/`, { | |||
method: 'POST', | |||
headers: { | |||
'X-CSRFToken': getCookie('csrftoken'), | |||
'Content-Type': 'application/json' | |||
}, | |||
credentials: 'same-origin', | |||
}) | |||
.then(response => response.json()) | |||
.then(data => { | |||
if (data.success) { | |||
button.innerHTML = `❤️ ${data.likes}`; | |||
button.disabled = true; | |||
} else { | |||
alert(data.message || '您已经点过赞了'); | |||
} | |||
}); | |||
}); | |||
}); | |||
// 评论提交 | |||
document.querySelectorAll('.comment-form').forEach(form => { | |||
form.addEventListener('submit', function (e) { | |||
e.preventDefault(); | |||
if (!isLoggedIn) { | |||
alert("请先登录后再发表评论!"); | |||
window.location.href = "/users/login/"; | |||
return; | |||
} | |||
const postId = form.getAttribute('data-post-id'); | |||
const input = form.querySelector('input[name="content"]'); | |||
const content = input.value.trim(); | |||
if (!content) return; | |||
fetch(`/Community/comment_post/${postId}/`, { | |||
method: 'POST', | |||
headers: { | |||
'X-CSRFToken': getCookie('csrftoken'), | |||
'Content-Type': 'application/x-www-form-urlencoded' | |||
}, | |||
body: `content=${encodeURIComponent(content)}`, | |||
credentials: 'same-origin', | |||
}) | |||
.then(res => res.json()) | |||
.then(data => { | |||
if (data.success) { | |||
const list = document.getElementById(`comment-list-${postId}`); | |||
const newComment = document.createElement('div'); | |||
newComment.className = 'comment-item'; | |||
newComment.innerHTML = `<strong>${data.username}</strong>: ${data.content}`; | |||
list.appendChild(newComment); | |||
input.value = ''; | |||
} else { | |||
alert(data.message || '评论失败'); | |||
} | |||
}); | |||
}); | |||
}); | |||
// 收藏按钮 | |||
document.querySelectorAll('.favorite-btn').forEach(button => { | |||
button.addEventListener('click', () => { | |||
if (!isLoggedIn) { | |||
alert("请先登录后再收藏!"); | |||
window.location.href = "/users/login/"; | |||
return; | |||
} | |||
const postId = button.getAttribute('data-post-id'); | |||
fetch(`/Community/favorite_post/${postId}/`, { | |||
method: 'POST', | |||
headers: { | |||
'X-CSRFToken': getCookie('csrftoken'), | |||
'Content-Type': 'application/json' | |||
}, | |||
credentials: 'same-origin', | |||
}) | |||
.then(response => response.json()) | |||
.then(data => { | |||
if (data.success) { | |||
button.innerHTML = `⭐ ${data.favorites}`; | |||
button.disabled = true; | |||
} else { | |||
alert(data.message || '您已经收藏过了'); | |||
} | |||
}); | |||
}); | |||
}); | |||
// 关注按钮 | |||
document.querySelectorAll('.follow-btn').forEach(button => { | |||
button.addEventListener('click', () => { | |||
if (!isLoggedIn) { | |||
alert("请先登录后再关注用户!"); | |||
window.location.href = "/users/login/"; | |||
return; | |||
} | |||
const userId = button.getAttribute('data-user-id'); | |||
fetch(`/Community/toggle_follow/${userId}/`, { | |||
method: 'POST', | |||
headers: { | |||
'X-CSRFToken': getCookie('csrftoken'), | |||
'Content-Type': 'application/json' | |||
}, | |||
credentials: 'same-origin', | |||
}) | |||
.then(res => res.json()) | |||
.then(data => { | |||
if (data.success) { | |||
const icon = button.querySelector('i'); | |||
const label = button.querySelector('.follow-text'); | |||
if (data.following) { | |||
icon.classList.remove('fa-user-plus'); | |||
icon.classList.add('fa-user-check'); | |||
label.textContent = '已关注'; | |||
} else { | |||
icon.classList.remove('fa-user-check'); | |||
icon.classList.add('fa-user-plus'); | |||
label.textContent = '关注'; | |||
} | |||
} else { | |||
alert(data.message); | |||
} | |||
}); | |||
}); | |||
}); | |||
</script> | |||
</body> | |||
</html> |
@ -0,0 +1,692 @@ | |||
{% load static %} | |||
<!doctype html> | |||
<html lang=zh-CN> | |||
<head> | |||
<meta charset=UTF-8> | |||
<title>WaveSign手语通</title> | |||
<link rel="stylesheet" href="{% static 'css/login_button.css' %}"> | |||
<script src=https://cdn.tailwindcss.com></script> | |||
<script> | |||
tailwind.config = { | |||
darkMode: `class` | |||
} | |||
</script> | |||
<link href=https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css rel=stylesheet> | |||
<style> | |||
/* 新增幻灯片样式 */ | |||
.slider-container { | |||
position: relative; | |||
overflow: hidden; | |||
border-radius: 12px; | |||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); | |||
margin: 2rem 0; | |||
} | |||
.slider-track { | |||
display: flex; | |||
width: 300%; | |||
transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.slider-item { | |||
flex: 0 0 33.33333%; | |||
position: relative; | |||
overflow: hidden; | |||
} | |||
.slider-item img { | |||
width: 100%; | |||
height: 400px; | |||
object-fit: cover; | |||
transform: scale(1); | |||
transition: transform 10s linear; | |||
} | |||
.slider-item:hover img { | |||
transform: scale(1.05); | |||
} | |||
.slider-caption { | |||
position: absolute; | |||
bottom: 0; | |||
left: 0; | |||
right: 0; | |||
padding: 2rem; | |||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent); | |||
color: white; | |||
} | |||
.slider-control { | |||
position: absolute; | |||
top: 50%; | |||
transform: translateY(-50%); | |||
width: 100%; | |||
display: flex; | |||
justify-content: space-between; | |||
padding: 0 1rem; | |||
} | |||
.slider-nav { | |||
background: rgba(255, 255, 255, 0.2); | |||
backdrop-filter: blur(5px); | |||
width: 40px; | |||
height: 40px; | |||
border-radius: 50%; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
cursor: pointer; | |||
transition: all 0.3s; | |||
} | |||
.slider-nav:hover { | |||
background: rgba(255, 255, 255, 0.3); | |||
transform: scale(1.1); | |||
} | |||
.slider-dots { | |||
position: absolute; | |||
bottom: 1rem; | |||
left: 50%; | |||
transform: translateX(-50%); | |||
display: flex; | |||
gap: 8px; | |||
} | |||
.slider-dot { | |||
width: 10px; | |||
height: 10px; | |||
border-radius: 50%; | |||
background: rgba(255, 255, 255, 0.5); | |||
cursor: pointer; | |||
transition: all 0.3s; | |||
} | |||
.slider-dot.active { | |||
background: var(--primary); | |||
width: 30px; | |||
border-radius: 5px; | |||
} | |||
@media (max-width: 768px) { | |||
.slider-item img { | |||
height: 300px; | |||
} | |||
.slider-nav { | |||
width: 35px; | |||
height: 35px; | |||
} | |||
} | |||
@import "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap"; | |||
:root { | |||
--primary: #8d6a95; | |||
--primary-light: #cebbf3; | |||
--dark-bg: #1a1a1a; | |||
--dark-card: #2a2a2a; | |||
--light-bg: #f9f9f9; | |||
--light-card: #fff | |||
} | |||
@import "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap"; | |||
:root { | |||
--primary: #8d6a95; | |||
--primary-light: #cebbf3; | |||
--dark-bg: #1a1a1a; | |||
--dark-card: #2a2a2a; | |||
--light-bg: #f9f9f9; | |||
--light-card: #fff | |||
} | |||
.dark { | |||
--bg-color: var(--dark-bg); | |||
--card-bg: var(--dark-card); | |||
--text-color: #fff; | |||
--border-color: #692479 | |||
} | |||
.light { | |||
--bg-color: var(--light-bg); | |||
--card-bg: var(--light-card); | |||
--text-color: hsl(301, 89%, 21%); | |||
--border-color: #e5e5e5 | |||
} | |||
body { | |||
background-color: var(--bg-color); | |||
color: var(--text-color); | |||
font-family: Noto Sans SC, sans-serif; | |||
transition: background-color .3s, color .3s; | |||
padding: 2rem 5%; | |||
/* 修改为百分比响应式边距 */ | |||
max-width: 1440px; | |||
margin: 0 auto; | |||
box-sizing: border-box; | |||
} | |||
/* 新增盒模型设置[10](@ref) */ | |||
@media (min-width: 1600px) { | |||
body { | |||
padding: 2rem 8%; | |||
/* 大屏幕增加边距 */ | |||
} | |||
} | |||
@media (max-width: 768px) { | |||
body { | |||
padding: 1.5rem; | |||
/* 移动端统一边距 */ | |||
} | |||
} | |||
/* 新增内容容器边距 */ | |||
.main-container { | |||
padding: 0 2rem; | |||
/* 内容区二级边距 */ | |||
margin: 0 auto; | |||
} | |||
.card { | |||
background-color: var(--card-bg); | |||
border: 1px solid var(--border-color); | |||
transition: transform .3s, box-shadow .3s, border-color .3s; | |||
margin: 1rem 0; | |||
/* 新增卡片外边距 */ | |||
padding: 1.5rem; | |||
/* 新增卡片内边距[2](@ref) */ | |||
} | |||
.card:hover { | |||
border-color: var(--primary); | |||
transform: translateY(-5px); | |||
box-shadow: 0 10px 20px #0000001a | |||
} | |||
.btn { | |||
transition: transform .2s, background-color .2s | |||
} | |||
.btn:hover { | |||
transform: scale(1.05) | |||
} | |||
.fade-in { | |||
animation: .8s ease-in fadeIn | |||
} | |||
@keyframes fadeIn { | |||
0% { | |||
opacity: 0; | |||
transform: translateY(20px) | |||
} | |||
to { | |||
opacity: 1; | |||
transform: translateY(0) | |||
} | |||
} | |||
.nav-link { | |||
position: relative | |||
} | |||
.nav-link:after { | |||
content: ""; | |||
background-color: var(--primary); | |||
width: 0; | |||
height: 2px; | |||
transition: width .3s; | |||
position: absolute; | |||
bottom: -2px; | |||
left: 0 | |||
} | |||
.nav-link:hover:after, | |||
.nav-link.active:after { | |||
width: 100% | |||
} | |||
.image-slide { | |||
border-radius: 10px; | |||
position: relative; | |||
overflow: hidden | |||
} | |||
.image-slide img { | |||
transition: transform 10s ease-in-out | |||
} | |||
.image-slide:hover img { | |||
transform: scale(1.05) | |||
} | |||
.theme-toggle { | |||
cursor: pointer; | |||
border-radius: 50%; | |||
padding: 10px; | |||
transition: background-color .3s | |||
} | |||
.theme-toggle:hover { | |||
background-color: var(--primary-light) | |||
} | |||
/* 添加在style中 */ | |||
.user-menu { | |||
display: none; | |||
position: absolute; | |||
right: 0; | |||
top: 100%; | |||
background: var(--card-bg); | |||
border: 1px solid var(--border-color); | |||
border-radius: 8px; | |||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |||
min-width: 160px; | |||
} | |||
.user-menu.show { | |||
display: block; | |||
} | |||
</style> | |||
<script src="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script> | |||
</head> | |||
<body> | |||
<header class="py-6 px-4 md:px-8 flex justify-between items-center"> | |||
<div class="absolute left-1/2 transform -translate-x-1/2"> | |||
<a class="text-3xl md:text-4xl font-bold fade-in">WaveSign手语通</a> | |||
<p class="text-lg md:text-xl opacity-80 fade-in mt-2">连接双手与世界,构建无声的桥梁 | |||
</p> | |||
</div> | |||
<div class="flex items-center space-x-4 ml-auto"> | |||
{% if user.is_authenticated %} | |||
<div class="user-menu-box-container"> | |||
<a href="{% url 'my_page' %}" class="user-menu-box-button"> | |||
{% if user.avatar %} | |||
<img src="{{ user.avatar.url }}" alt="头像" class="user-menu-box-avatar"> | |||
{% else %} | |||
<img src="{% static 'images/default_avatar.png' %}" alt="默认头像" class="user-menu-box-avatar"> | |||
{% endif %} | |||
{{ user.username }} | |||
</a> | |||
<div class="user-menu-box-dropdown"> | |||
<a href="{% url 'my_page' %}">进入个人主页</a> | |||
<a href="{% url 'logout' %}">退出登录</a> | |||
</div> | |||
</div> | |||
{% else %} | |||
<a href="{% url 'login' %}" class="user-menu-box-login"> | |||
<i class="fas fa-user"></i> 登录 | |||
</a> | |||
{% endif %} | |||
</div> | |||
</header> | |||
<nav class="sticky top-0 bg-opacity-90 backdrop-blur-sm py-4 px-4 md:px-8 z-50 fade-in" | |||
style="background-color:var(--card-bg); border-bottom: 1px solid var(--border-color);"> | |||
<div class="container mx-auto flex justify-center space-x-8"> | |||
<a class="nav-link text-lg font-medium" href="{% url 'home' %}" style="color:var(--text-color)">首页</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'sl_classroom' %}" | |||
style="color:var(--text-color)">手语教室</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'life_serving' %}" | |||
style="color:var(--text-color)">生活服务</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'community' %}" | |||
style="color:var(--text-color)">手语社区</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'schedule' %}" style="color:var(--text-color)">日程管理</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'my_page' %}" style="color:var(--text-color)">我的</a> | |||
</div> | |||
</nav> | |||
<!-- 修改后的幻灯片部分 --> | |||
<section class="mb-12 fade-in" style="animation-delay:.2s"> | |||
<div class="slider-container"> | |||
<div class="slider-track" id="sliderTrack"> | |||
<!-- 幻灯片项1 --> | |||
<div class="slider-item"> | |||
<img src="{% static 'images/1.png' %}" alt="手语交流"> | |||
<div class="slider-caption"> | |||
<h3 class="text-2xl font-bold mb-2">手语交流社区</h3> | |||
<p>与全球手语使用者实时互动</p> | |||
</div> | |||
</div> | |||
<!-- 幻灯片项2 --> | |||
<div class="slider-item"> | |||
<img src="{% static 'images/2.jpg' %}" alt="手语教学"> | |||
<div class="slider-caption"> | |||
<h3 class="text-2xl font-bold mb-2">专业课程体系</h3> | |||
<p>系统化学习手语知识</p> | |||
</div> | |||
</div> | |||
<!-- 幻灯片项3 --> | |||
<div class="slider-item"> | |||
<img src="{% static 'images/3.jpg' %}"> | |||
<div class="slider-caption"> | |||
<h3 class="text-2xl font-bold mb-2">生活服务支持</h3> | |||
<p>一站式解决日常需求</p> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 导航控件 --> | |||
<div class="slider-control"> | |||
<div class="slider-nav" onclick="prevSlide()"> | |||
<i class="fas fa-chevron-left text-white"></i> | |||
</div> | |||
<div class="slider-nav" onclick="nextSlide()"> | |||
<i class="fas fa-chevron-right text-white"></i> | |||
</div> | |||
</div> | |||
<!-- 指示器 --> | |||
<div class="slider-dots" id="sliderDots"></div> | |||
</div> | |||
</section> | |||
<section class="mb-16 fade-in" id=hand-sign style=animation-delay:.4s> | |||
<h2 class="text-2xl md:text-3xl font-bold mb-6 flex items-center"> | |||
<i class="fas fa-sign-language mr-3 text-purple-600"></i> | |||
手语教室 | |||
</h2> | |||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | |||
<div class="card rounded-xl p-6 hover:shadow-lg"> | |||
<div class="flex items-center mb-4"> | |||
<div | |||
class="w-12 h-12 rounded-full bg-purple-100 dark:bg-purple-900 flex items-center justify-center mr-4"> | |||
<i class="fas fa-book text-purple-600 dark:text-purple-400 text-xl"></i> | |||
</div> | |||
<h3 class="text-xl font-semibold">基础手语课程</h3> | |||
</div> | |||
<p class=mb-4>从零开始学习手语,掌握日常交流必备词汇和语法。 | |||
<div class="flex justify-end"> | |||
<a class="btn text-purple-600 dark:text-purple-400 hover:text-purple-800 dark:hover:text-purple-300 font-medium" | |||
href="{% url 'sl_classroom' %}" style="color:var(--text-color)">开始学习</a> | |||
</div> | |||
</div> | |||
<div class="card rounded-xl p-6 hover:shadow-lg"> | |||
<div class="flex items-center mb-4"> | |||
<div | |||
class="w-12 h-12 rounded-full bg-purple-100 dark:bg-purple-900 flex items-center justify-center mr-4"> | |||
<i class="fas fa-video text-purple-600 dark:text-purple-400 text-xl"></i> | |||
</div> | |||
<h3 class="text-xl font-semibold">视频教学</h3> | |||
</div> | |||
<p class=mb-4>通过生动的视频教程,直观学习手语表达方式。 | |||
<div class="flex justify-end"> | |||
<a class="btn text-purple-600 dark:text-purple-400 hover:text-purple-800 dark:hover:text-purple-300 font-medium" | |||
href="{% url 'sl_classroom' %}" style="color:var(--text-color)">观看视频</a> | |||
</div> | |||
</div> | |||
<div class="card rounded-xl p-6 hover:shadow-lg"> | |||
<div class="flex items-center mb-4"> | |||
<div | |||
class="w-12 h-12 rounded-full bg-purple-100 dark:bg-purple-900 flex items-center justify-center mr-4"> | |||
<i class="fas fa-comments text-purple-600 dark:text-purple-400 text-xl"></i> | |||
</div> | |||
<h3 class="text-xl font-semibold">互动练习</h3> | |||
</div> | |||
<p class=mb-4>通过互动练习巩固所学知识,提高手语应用能力。 | |||
<div class="flex justify-end"> | |||
<a class="btn text-purple-600 dark:text-purple-400 hover:text-purple-800 dark:hover:text-purple-300 font-medium" | |||
href="{% url 'sl_classroom' %}" style="color:var(--text-color)">开始练习</a> | |||
</div> | |||
</div> | |||
</div> | |||
</section> | |||
<section class="mb-16 fade-in" id=life-service style=animation-delay:.6s> | |||
<h2 class="text-2xl md:text-3xl font-bold mb-6 flex items-center"> | |||
<i class="fas fa-life-ring mr-3 text-purple-600"></i> | |||
生活服务 | |||
</h2> | |||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> | |||
<div class="card rounded-xl p-6 hover:shadow-lg"> | |||
<div | |||
class="w-12 h-12 rounded-full bg-purple-100 dark:bg-purple-900 flex items-center justify-center mb-4"> | |||
<i class="fas fa-users text-purple-600 dark:text-purple-400 text-xl"></i> | |||
</div> | |||
<h3 class="text-xl font-semibold mb-2">社交活动</h3> | |||
<p>发现附近的聋哑人士聚会和手语交流活动 | |||
</div> | |||
<div class="card rounded-xl p-6 hover:shadow-lg"> | |||
<div | |||
class="w-12 h-12 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center mb-4"> | |||
<i class="fas fa-map-marker-alt text-green-600 dark:text-green-400 text-xl"></i> | |||
</div> | |||
<h3 class="text-xl font-semibold mb-2">无障碍设施</h3> | |||
<p>获取无障碍设施的地点信息和路线指引。 | |||
</div> | |||
<div class="card rounded-xl p-6 hover:shadow-lg"> | |||
<div | |||
class="w-12 h-12 rounded-full bg-orange-100 dark:bg-orange-900 flex items-center justify-center mb-4"> | |||
<i class="fas fa-gift text-orange-600 dark:text-orange-400 text-xl"></i> | |||
</div> | |||
<h3 class="text-xl font-semibold mb-2">好物推荐</h3> | |||
<p>精选适合聋哑人士的生活辅助工具和实用商品 | |||
</div> | |||
<div class="card rounded-xl p-6 hover:shadow-lg"> | |||
<div | |||
class="w-12 h-12 rounded-full bg-yellow-100 dark:bg-yellow-900 flex items-center justify-center mb-4"> | |||
<i class="fas fa-shopping-cart text-yellow-600 dark:text-yellow-400 text-xl"></i> | |||
</div> | |||
<h3 class="text-xl font-semibold mb-2">就业信息</h3> | |||
<p>获取聋哑人士最新岗位信息和在线找工作支持。 | |||
</div> | |||
</div> | |||
</section> | |||
<section class="mb-16 fade-in" id=sign-community style=animation-delay:.8s> | |||
<h2 class="text-2xl md:text-3xl font-bold mb-6 flex items-center"> | |||
<i class="fas fa-users mr-3 text-purple-600"></i> | |||
手语社区 | |||
</h2> | |||
<div class="card rounded-xl p-6 mb-6"> | |||
<div class="flex flex-col md:flex-row"> | |||
<div class="md:w-1/3 mb-4 md:mb-0 md:pr-6"> | |||
<h3 class="text-xl font-semibold mb-3">最新活动</h3> | |||
<ul class=space-y-2> | |||
<li class="flex items-center"> | |||
<i class="fas fa-calendar-check text-purple-600 dark:text-purple-400 mr-2"></i> | |||
<span>手语交流会 - 下周日</span> | |||
<li class="flex items-center"> | |||
<i class="fas fa-calendar-check text-purple-600 dark:text-purple-400 mr-2"></i> | |||
<span>手语工作坊 - 本月末</span> | |||
<li class="flex items-center"> | |||
<i class="fas fa-calendar-check text-purple-600 dark:text-purple-400 mr-2"></i> | |||
<span>手语表演 - 下月</span> | |||
</ul> | |||
</div> | |||
<div class="md:w-2/3 md:border-l md:pl-6 border-gray-200 dark:border-gray-700"> | |||
<h3 class="text-xl font-semibold mb-3">社区动态</h3> | |||
<div class=space-y-4> | |||
<div class="p-4 rounded-lg bg-gray-50 dark:bg-gray-800"> | |||
<p class=mb-2> | |||
<span class=font-medium>用户A:</span> | |||
刚学了几个新的手语词汇,感觉越来越自信了! | |||
<p class="text-sm text-gray-600 dark:text-gray-400">— 前天 | |||
</div> | |||
<div class="p-4 rounded-lg bg-gray-50 dark:bg-gray-800"> | |||
<p class=mb-2> | |||
<span class=font-medium>用户B:</span> | |||
明天的手语交流会见!期待和大家见面。 | |||
<p class="text-sm text-gray-600 dark:text-gray-400">— 昨天 | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="card rounded-xl p-6"> | |||
<h3 class="text-xl font-semibold mb-4">加入我们</h3> | |||
<p class=mb-4>成为WaveSign社区的一员,与其他手语学习者和使用者交流互动,分享经验,共同进步。 | |||
<div class="flex justify-center"> | |||
<a class="btn bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-lg font-medium" | |||
href="{% url 'community' %}" | |||
style="color:var(--text-color)">立即加入</a> | |||
</div> | |||
</div> | |||
</section> | |||
<footer class="py-8 px-4 md:px-8 text-center fade-in" style=animation-delay:1.2s> | |||
<div class="flex flex-col items-center"> | |||
<div class="mb-4 text-lg font-medium">作者: backpack</div> | |||
<p class="text-sm opacity-70">© 2025 WaveSign. | |||
</div> | |||
</footer> | |||
<script> | |||
// 幻灯片控制逻辑 | |||
let currentSlide = 0; | |||
const slideCount = 3; | |||
const sliderTrack = document.getElementById('sliderTrack'); | |||
const sliderDots = document.getElementById('sliderDots'); | |||
let autoPlayTimer; | |||
// 初始化指示器 | |||
for (let i = 0; i < slideCount; i++) { | |||
const dot = document.createElement('div'); | |||
dot.className = `slider-dot ${i === 0 ? 'active' : ''}`; | |||
dot.addEventListener('click', () => goToSlide(i)); | |||
sliderDots.appendChild(dot); | |||
} | |||
// 切换幻灯片 | |||
function goToSlide(index) { | |||
currentSlide = (index + slideCount) % slideCount; | |||
sliderTrack.style.transform = `translateX(-${currentSlide * 33.333}%)`; | |||
updateDots(); | |||
} | |||
// 更新指示器状态 | |||
function updateDots() { | |||
document.querySelectorAll('.slider-dot').forEach((dot, index) => { | |||
dot.classList.toggle('active', index === currentSlide); | |||
}); | |||
} | |||
// 下一张 | |||
function nextSlide() { | |||
goToSlide(currentSlide + 1); | |||
resetAutoPlay(); | |||
} | |||
// 上一张 | |||
function prevSlide() { | |||
goToSlide(currentSlide - 1); | |||
resetAutoPlay(); | |||
} | |||
// 自动播放控制 | |||
function startAutoPlay() { | |||
autoPlayTimer = setInterval(nextSlide, 5000); | |||
} | |||
function resetAutoPlay() { | |||
clearInterval(autoPlayTimer); | |||
startAutoPlay(); | |||
} | |||
// 悬停控制 | |||
document.querySelector('.slider-container').addEventListener('mouseenter', () => { | |||
clearInterval(autoPlayTimer); | |||
}); | |||
document.querySelector('.slider-container').addEventListener('mouseleave', startAutoPlay); | |||
// 初始化自动播放 | |||
startAutoPlay(); | |||
const themeToggle = document.getElementById(`themeToggle`); | |||
const body = document.body; | |||
if (window.matchMedia && window.matchMedia(`(prefers-color-scheme: dark)`).matches) { | |||
body.classList.remove(`light`); | |||
body.classList.add(`dark`) | |||
} | |||
; themeToggle.addEventListener(`click`, () => { | |||
let b = `light` | |||
, a = `dark`; | |||
if (body.classList.contains(a)) { | |||
body.classList.remove(a); | |||
body.classList.add(b) | |||
} else { | |||
body.classList.remove(b); | |||
body.classList.add(a) | |||
} | |||
} | |||
); | |||
// 示例登录状态检测 | |||
const authState = { | |||
isLoggedIn: false, // 实际应从cookie/localStorage获取 | |||
username: "测试用户" | |||
} | |||
const renderAuth = () => { | |||
const authHTML = authState.isLoggedIn | |||
? `<div class="flex items-center space-x-2"> | |||
<i class="fa-regular fa-user text-xl"></i> | |||
<span>${authState.username}</span> | |||
</div>` | |||
: `<a href="{% url 'login' %}" class="flex items-center space-x-2 hover:text-[var(--primary)] transition-colors"> | |||
<i class="fa-regular fa-user text-xl"></i> | |||
<span class="text-lg">登录</span> | |||
</a>`; | |||
document.querySelector('header').lastElementChild.innerHTML = authHTML; | |||
} | |||
// 初始化执行 | |||
renderAuth(); | |||
const navLinks = document.querySelectorAll(`.nav-link`); | |||
const sections = document.querySelectorAll(`section[id]`); | |||
window.addEventListener(`scroll`, () => { | |||
let b = `active`; | |||
let a = ``; | |||
sections.forEach(b => { | |||
const c = b.offsetTop; | |||
const d = b.clientHeight; | |||
if (pageYOffset >= c - 200) { | |||
a = b.getAttribute(`id`) | |||
} | |||
} | |||
); | |||
navLinks.forEach(c => { | |||
c.classList.remove(b); | |||
if (c.getAttribute(`href`).substring(1) === a) { | |||
c.classList.add(b) | |||
} | |||
} | |||
) | |||
} | |||
); | |||
document.addEventListener(`DOMContentLoaded`, () => { | |||
const a = document.querySelectorAll(`.fade-in`); | |||
const b = new IntersectionObserver(a => { | |||
a.forEach(a => { | |||
if (a.isIntersecting) { | |||
a.target.style.opacity = 1; | |||
a.target.style.transform = `translateY(0)` | |||
} | |||
} | |||
) | |||
} | |||
, { | |||
threshold: 0.1 | |||
}); | |||
a.forEach(a => { | |||
a.style.opacity = 0; | |||
a.style.transform = `translateY(20px)`; | |||
a.style.transition = `opacity 0.5s ease, transform 0.5s ease`; | |||
b.observe(a) | |||
} | |||
) | |||
} | |||
) | |||
</script> | |||
</body> | |||
</html> |
@ -0,0 +1,468 @@ | |||
{% load static %} | |||
<!doctype html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="utf-8"> | |||
<title>公共服务</title> | |||
<link rel="stylesheet" href="../static/css/LifeServing.css"> | |||
<link rel="stylesheet" href="{% static 'css/login_button.css' %}"> | |||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> | |||
<link href=https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css rel=stylesheet> | |||
<script src=https://cdn.tailwindcss.com></script> | |||
<script> | |||
tailwind.config = { | |||
darkMode: `class` | |||
} | |||
</script> | |||
<script type="text/javascript" src="http://api.map.baidu.com/api?v=1.3"></script> | |||
</head> | |||
<body> | |||
<header class="py-6 px-4 md:px-8 flex justify-between items-center"> | |||
<div class="absolute left-1/2 transform -translate-x-1/2"> | |||
<a class="text-3xl md:text-4xl font-bold fade-in">WaveSign手语通</a> | |||
<p class="text-lg md:text-xl opacity-80 fade-in mt-2">连接双手与世界,构建无声的桥梁</p> | |||
</div> | |||
<div class="flex items-center space-x-4 ml-auto"> | |||
{% if user.is_authenticated %} | |||
<div class="user-menu-box-container"> | |||
<a href="{% url 'my_page' %}" class="user-menu-box-button"> | |||
{% if user.avatar %} | |||
<img src="{{ user.avatar.url }}" alt="头像" class="user-menu-box-avatar"> | |||
{% else %} | |||
<img src="{% static 'images/default_avatar.png' %}" alt="默认头像" class="user-menu-box-avatar"> | |||
{% endif %} | |||
{{ user.username }} | |||
</a> | |||
<div class="user-menu-box-dropdown"> | |||
<a href="{% url 'my_page' %}">进入个人主页</a> | |||
<a href="{% url 'logout' %}">退出登录</a> | |||
</div> | |||
</div> | |||
{% else %} | |||
<a href="{% url 'login' %}" class="user-menu-box-login"> | |||
<i class="fas fa-user"></i> 登录 | |||
</a> | |||
{% endif %} | |||
</div> | |||
</header> | |||
<nav class="sticky top-0 bg-opacity-90 backdrop-blur-sm py-4 px-4 md:px-8 z-50 fade-in" | |||
style="background-color:var(--card-bg)"> | |||
<div class="container mx-auto flex justify-center space-x-8"> | |||
<a class="nav-link text-lg font-medium" href="{% url 'home' %}" style="color:var(--text-color)">首页</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'sl_classroom' %}" style="color:var(--text-color)">手语教室</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'life_serving' %}" style="color:var(--text-color)">生活服务</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'community' %}" style="color:var(--text-color)">手语社区</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'schedule' %}" style="color:var(--text-color)">日程管理</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'my_page' %}" style="color:var(--text-color)">我的</a> | |||
</div> | |||
</nav> | |||
<nav class="nav-container"> | |||
<ul class="nav-menu"> | |||
<li class="nav-item" onmouseover="showContent('travel-service')">🚗 出行服务</li> | |||
<li class="nav-item" onmouseover="showContent('shopping-guide')">🛍️ 好物推荐</li> | |||
<li class="nav-item" onmouseover="showContent('job-info')">💼 就业信息</li> | |||
<li class="nav-item" onmouseover="showContent('activity')">❤️ 近期活动</li> | |||
</ul> | |||
</nav> | |||
<!-- 出行服务模块 --> | |||
<div id="travel-service" class="content-container"> | |||
<!-- 地图与实时信息并排 --> | |||
<div class="traffic-wrapper"> | |||
<!-- 地图容器 --> | |||
<div class="map-container" id="container"> | |||
<iframe width="100%" height="500" frameborder="0" | |||
src="https://map.baidu.com/@12958171.72,4828364.17,18z?query=深圳市华利嘉电子市场2c086"> | |||
</iframe> | |||
<div class="traffic-legend"> | |||
<div class="legend-item"> | |||
<span class="traffic-dot traffic-smooth"></span> | |||
<span>畅通</span> | |||
</div> | |||
<div class="legend-item"> | |||
<span class="traffic-dot traffic-slow"></span> | |||
<span>缓行</span> | |||
</div> | |||
<div class="legend-item"> | |||
<span class="traffic-dot traffic-congested"></span> | |||
<span>拥堵</span> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 实时信息侧边栏 --> | |||
<div class="traffic-sidebar"> | |||
<div class="traffic-alert"> | |||
<h3>🚨 实时预警</h3> | |||
<ul class="alert-list"> | |||
<li>【18:00】地铁2号线人民广场站电梯维护</li> | |||
<li>【17:30】南京西路路段临时管制</li> | |||
</ul> | |||
</div> | |||
<div class="sign-language-tip"> | |||
<h3>✋ 手语服务</h3> | |||
<p>周边可提供手语帮助的站点:</p> | |||
<ul class="service-list"> | |||
<li>人民广场站 (1号口服务台)</li> | |||
<li>静安寺站 (无障碍服务中心)</li> | |||
</ul> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 无障碍出行推荐 --> | |||
<div class="accessibility-recommend"> | |||
<div class="recommend-card"> | |||
<div class="icon">👐</div> | |||
<h4>手语导航服务</h4> | |||
<p>在线预约手语翻译陪同出行</p> | |||
<a href="https://vip.aminer.cn/sign/"> | |||
<button class="purple-btn">立即预约</button> | |||
</a> | |||
</div> | |||
<div class="recommend-card"> | |||
<div class="icon">🦻</div> | |||
<h4>震动提醒路线</h4> | |||
<p>开启手机震动导航模式</p> | |||
<a href="https://map.baidu.com/@13529169,3653875,12z,50t"> | |||
<button class="purple-btn">开启导航</button></a> | |||
</div> | |||
<div class="recommend-card"> | |||
<div class="icon">📸</div> | |||
<h4>AR实景指引</h4> | |||
<p>摄像头识别无障碍设施位置</p> | |||
<a href="https://map.baidu.com/@13529169,3653875,12z,50t"> | |||
<button class="purple-btn">启动AR</button></a> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 好物推荐模块 --> | |||
<div id="shopping-guide" class="content-container"> | |||
<!-- 分类导航 --> | |||
<div class="category-nav"> | |||
<button class="category-btn active" data-category="all">全部</button> | |||
<button class="category-btn" data-category="daily">生活日用</button> | |||
<button class="category-btn" data-category="tech">科技助残</button> | |||
<button class="category-btn" data-category="safety">安全防护</button> | |||
</div> | |||
<!-- 商品展示区 --> | |||
<div class="goods-grid"> | |||
<!-- 商品卡片1 --> | |||
<div class="goods-card" data-category="tech"> | |||
<div class="goods-img" style="background-image: url('../static/images/item1.png')"> | |||
<button class="favorite-btn">❤️ 收藏</button> | |||
</div> | |||
<div class="goods-info"> | |||
<h3>智能震动闹钟</h3> | |||
<p class="goods-desc">枕头震动唤醒,静音不扰人</p> | |||
<div class="price-tag"> | |||
<span class="current-price">¥268</span> | |||
<span class="original-price">¥399</span> | |||
</div> | |||
<a | |||
href="https://item.taobao.com/item.htm?spm=a21n57.sem.item.9.5af23903hZxXlt&priceTId=2147801b17432965612948760e16f3&utparam=%7B%22aplus_abtest%22%3A%228b1b1e20617466057956ef371f7c333d%22%7D&id=750422232833&ns=1&xxc=ad_ztc&pisk=gAcmD-4Iyxyf5pK8ebNXpMznXQ9-kSN_xcCTX5EwUur5H5pjWPDoRcqv5KNxElmrqo5vlqnu7c0sHEw9cNbjZDfOMn9j71VT_HKp9B3fl5NwvvitPNSbSy526rPaaSaOkprrCB3jll_cbeHy9GADhvOVQloaa8zT-5z43lRyrP43b5r4QgWz8uPa_oz4UuzTotzabo8l4yUN_PyaQuyzlP14_5owaar775zqb5kGvKrj_bly4aYLQNnJRba0nku40q3sanyAhqEoCAczo4ctuO1N_b40nf9w-is8HAuQdj39s_Nx-YPzSfTFIl2nQjFrg3RUFRkZpRDR0EnEj4o_Mb8N3kkxD7lqKN5ZrSqZxbyFm3qEG4lQa8de-4lSDqGoWNRayDErljPMTeN0goPuPfK5glDrQjeb1i-gfb0rgAjr_TWFAqC_zFhPCOw4PzqpMbNBVGI2zF8kr98_3zabvUYlCOw4PzqprUXe1-z7lkC.."> | |||
<button class="buy-btn">立即购买</button> | |||
</a> | |||
</div> | |||
</div> | |||
<!-- 商品卡片2 --> | |||
<div class="goods-card" data-category="safety"> | |||
<div class="goods-img" style="background-image: url('../static/images/item2.png')"> | |||
<button class="favorite-btn">❤️ 收藏</button> | |||
</div> | |||
<div class="goods-info"> | |||
<h3>闪光门铃</h3> | |||
<p class="goods-desc">灯光+震动双重提醒</p> | |||
<div class="price-tag"> | |||
<span class="current-price">¥158</span> | |||
</div> | |||
<a | |||
href="https://detail.tmall.com/item.htm?spm=a21n57.sem.item.59.5af23903hZxXlt&priceTId=2147801b17432968509466789e16f3&utparam=%7B%22aplus_abtest%22%3A%22fa6c66f4cf0640a81bd9874522aa55e0%22%7D&id=814651206485&ns=1&xxc=ad_ztc&pisk=goiIcli0Z6fCVwqRFyvaCI4PFNEWNd-2dTw-nYIFekEd2LGr_0z8aec7PblgyDrELfN-gfNd4UVFVuMSZblSEp28wbh7a28w0vD3qudqNn-qKZcGf_lCvWe9e8eYpdzduqhjAud2g3_NByAU2XlV53YOW-VTp8ULpdZTE8jL2zeKBdw8U6QRvbp6C8ebeMUdJGUTn8qRwyFRWGezU7Q8wgI9C8VO9uE-wlp_EAL21RYQ9v9LMKPfga_jT5sR2cw9ZRMBsiwEVJG3gvnB1C9udyNxp5OmjZazWY4SqTj7zv3mZ-hd9QNofAnKkjdN8W3bFxuSML73Qzi3f7npmtNtAYi_-V5dJ-ZQOrnLk39jHz3sfkopZZDZCWUbbV8MTzrIOqV0JFAiNANEwDaOOCPruAoL5jdNfb4SyDrtvCKf4QS4G0rcPOacVRN2Cd_lr5XeAXoDy4kQJR2Z0d91Oa4LIRN2Cd_lryegQrJ6C6_l."> | |||
<button class="buy-btn">立即购买</button> | |||
</a> | |||
</div> | |||
</div> | |||
<!-- 更多商品卡片... --> | |||
<div class="goods-card" data-category="daily"> | |||
<div class="goods-img" style="background-image: url('../static/images/item3.png')"> | |||
<button class="favorite-btn">❤️ 收藏</button> | |||
</div> | |||
<div class="goods-info"> | |||
<h3>无线紧急呼叫器</h3> | |||
<p class="goods-desc">千呼万唤,不如一按</p> | |||
<div class="price-tag"> | |||
<span class="current-price">¥59</span> | |||
</div> | |||
<a | |||
href="https://detail.tmall.com/item.htm?spm=a21n57.sem.item.97.5af23903hZxXlt&priceTId=2147801b17432969192931909e16f3&utparam=%7B%22aplus_abtest%22%3A%223604af41da8b23cbc9775683b3377884%22%7D&id=664135081185&ns=1&xxc=ad_ztc&pisk=gWUIcr4mEpvCybgJNDfZ1tmyNh3SJ17VOQG8i7Lew23py_wzQzo-UM2SV8yi2yuUTRM83RMpzgDeP4N7E8y7Z6c-e8eSUkSNuWVnr4B4FZ74tn2M5LyIpDnOwbhme1op0oebR4BV3atw6DXEyJy2fNs9XjDt9bnK91gtZXtKy0hL61G-apKJJ8C11bhjwenpvFntib0J2bH-WAhqwLhKvULO1bD9v438eVCsZAIFCfjIpW1KHskX3NRLQATJyPGOEmNB0EGUfXNshWUCa_5oODMY9A60SnirX7m7rQYS4WE0EjeppTMu55ULD-BwYvEjNSr7H_-n_0an5YUdoIMTR7asxlJpvjgIdmUKDa1bM0E_52zdEnVa1vnj7lSGL0uQdoDmvGXgF5MUeyi9dOkz05zKf-Bw58m72yuTJOQf4g8qhzuDVCiDPfMV11tkqAAFRJzc2uPIvfcau116d3mKsfMV11tkqDhi_m511ptl."> | |||
<button class="buy-btn">立即购买</button> | |||
</a> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 就业信息模块 --> | |||
<div id="job-info" class="content-container"> | |||
<!-- 搜索筛选区 --> | |||
<div class="job-filter"> | |||
<div class="search-box"> | |||
<input type="text" placeholder="搜索职位或公司..." class="search-input"> | |||
<a href="https://www.cdpee.org.cn/job/"><button class="search-btn">🔍 搜索</button></a> | |||
</div> | |||
</div> | |||
<!-- 职位列表 --> | |||
<div class="job-list"> | |||
<!-- 职位卡片1 --> | |||
<div class="job-card"> | |||
<div class="job-header"> | |||
<h3>前端开发工程师</h3> | |||
<span class="salary">¥15-25K</span> | |||
</div> | |||
<div class="job-meta"> | |||
<span class="company">ⓘ 无声科技</span> | |||
<span class="location">📍 北京</span> | |||
<span class="type">🏷️ 全职</span> | |||
</div> | |||
<div class="job-tags"> | |||
<span class="tag purple">手语面试</span> | |||
<span class="tag purple">远程办公</span> | |||
<span class="tag purple">弹性工时</span> | |||
</div> | |||
<div class="job-desc"> | |||
<p>岗位要求:</p> | |||
<ul> | |||
<li>精通HTML/CSS/JavaScript</li> | |||
<li>熟悉无障碍开发标准</li> | |||
<li>支持文字/视频沟通</li> | |||
</ul> | |||
</div> | |||
<div class="job-actions"> | |||
<a href="https://www.cdpee.org.cn/job/"> | |||
<button class="apply-btn">立即申请</button></a> | |||
<button class="save-btn">收藏职位</button> | |||
</div> | |||
</div> | |||
<!-- 职位卡片2 --> | |||
<div class="job-card highlighted"> | |||
<div class="job-header"> | |||
<h3>手语客服专员</h3> | |||
<span class="salary">¥8-12K</span> | |||
</div> | |||
<div class="job-meta"> | |||
<span class="company">ⓘ 暖心服务</span> | |||
<span class="location">📍 上海</span> | |||
<span class="type">🏷️ 兼职</span> | |||
</div> | |||
<div class="job-tags"> | |||
<span class="tag purple">视频面试</span> | |||
<span class="tag purple">岗前培训</span> | |||
</div> | |||
<div class="job-desc"> | |||
<p>岗位亮点:</p> | |||
<ul> | |||
<li>提供专业手语培训</li> | |||
<li>弹性工作时段</li> | |||
<li>在线工单系统</li> | |||
</ul> | |||
</div> | |||
<div class="job-actions"> | |||
<a href="https://www.cdpee.org.cn/job/"> | |||
<button class="apply-btn">立即申请</button></a> | |||
<button class="save-btn">收藏职位</button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div id="activity" class="content-container"> | |||
<!-- 原有的活动列表 --> | |||
<div class="news-list"> | |||
<article class="news-item"> | |||
<div class="news-media"> | |||
<img src="../static/images/profile/family.jpg" class="news-image" alt=""> | |||
<div class="news-meta"> | |||
<time class="news-date">2025/02/14</time> | |||
<span class="news-category">亲子家庭</span> | |||
</div> | |||
</div> | |||
<div class="news-content"> | |||
<a href="http://iot.china.com.cn/content/2025-01/03/content_43004726.html" | |||
style="text-decoration: none;"> | |||
<h2 class="news-title">亲子同乐,爱在手语间</h2> | |||
<p class="news-desc">于龙东社区219号举办聋哑人家庭参与亲子游戏、手语互动等活动,增进家庭成员间的沟通与情感。</p> | |||
</a> | |||
</div> | |||
</article> | |||
<article class="news-item"> | |||
<div class="news-media"> | |||
<img src="../static/images/profile/game.jpg" class="news-image" alt=""> | |||
<div class="news-meta"> | |||
<time class="news-date">2025/01/15</time> | |||
<span class="news-category">文化娱乐</span> | |||
</div> | |||
</div> | |||
<div class="news-content"> | |||
<a href="https://www.hangzhou2022.cn/paragames/xw/ycydt/202308/t20230814_69562.shtml" | |||
style="text-decoration: none;"> | |||
<h2 class="news-title">电竞无界,无声争锋</h2> | |||
<p class="news-desc">本平台将举办聋哑人《英雄联盟》电子竞技比赛,欢迎报名参加!</p> | |||
</a> | |||
</div> | |||
</article> | |||
<article class="news-item"> | |||
<div class="news-media"> | |||
<img src="../static/images/profile/travel.jpg" class="news-image" alt=""> | |||
<div class="news-meta"> | |||
<time class="news-date">2025/01/01</time> | |||
<span class="news-category">休闲娱乐</span> | |||
</div> | |||
</div> | |||
<div class="news-content"> | |||
<a href="https://www.sohu.com/a/427336909_669645" style="text-decoration: none;"> | |||
<h2 class="news-title">走遍世界,无声同行</h2> | |||
<p class="news-desc">走遍世界新一期旅行活动又来啦!这一次又将去哪里开启探索呢?</p> | |||
</a> | |||
</div> | |||
</article> | |||
<article class="news-item"> | |||
<div class="news-media"> | |||
<img src="../static/images/profile/baking.jpg" class="news-image" alt=""> | |||
<div class="news-meta"> | |||
<time class="news-date">2025/03/05</time> | |||
<span class="news-category">技能培训</span> | |||
</div> | |||
</div> | |||
<div class="news-content"> | |||
<a href="http://www.cjr.org.cn/info/notice/content/post_1148355.html" | |||
style="text-decoration: none;"> | |||
<h2 class="news-title">甜蜜烘焙,无声创造</h2> | |||
<p class="news-desc">无言公社将免费开设烘焙课程,教授聋哑人制作蛋糕、饼干等,心动就快来报名吧!</p> | |||
</a> | |||
</div> | |||
</article> | |||
<article class="news-item"> | |||
<div class="news-media"> | |||
<img src="images/profile/tree.jpeg" class="news-image" alt=""> | |||
<div class="news-meta"> | |||
<time class="news-date">2025/03/12</time> | |||
<span class="news-category">公益志愿</span> | |||
</div> | |||
</div> | |||
<div class="news-content"> | |||
<h2 class="news-title">绿色行动,无声守护</h2> | |||
<p class="news-desc">植树节特别活动:快来一起种棵树吧~</p> | |||
</div> | |||
</article> | |||
<article class="news-item"> | |||
<div class="news-media"></div> | |||
<div class="news-content"> | |||
<h2>更多精彩活动,持续更新中……</h2> | |||
</div> | |||
</article> | |||
</div> | |||
</div> | |||
<hr class="dashed"> | |||
<footer class="py-8 px-4 md:px-8 text-center fade-in" style='animation-delay:1.2s'> | |||
<div class="flex flex-col items-center"> | |||
<div class="mb-4 text-lg font-medium">作者: backpack</div> | |||
<p class="text-sm opacity-70">© 2025 WaveSign. | |||
</div> | |||
</footer> | |||
<script> | |||
function showContent(contentId) { | |||
// 隐藏所有内容 | |||
document.querySelectorAll('.content-container').forEach(item => { | |||
item.style.display = 'none'; | |||
}); | |||
// 显示指定内容 | |||
document.getElementById(contentId).style.display = 'block'; | |||
} | |||
function initMap() { | |||
const map = new AMap.Map('container', { | |||
zoom: 13, | |||
center: [121.4737, 31.2304] | |||
}); | |||
// 添加交通态势图层 | |||
AMap.plugin('AMap.TileLayer.Traffic', () => { | |||
map.add(new AMap.TileLayer.Traffic()); | |||
}); | |||
// 添加无障碍设施标记 | |||
const markers = [ | |||
{ position: [121.4752, 31.2315], title: '手语服务台' }, | |||
{ position: [121.4478, 31.2231], title: '无障碍电梯' } | |||
]; | |||
markers.forEach(m => { | |||
new AMap.Marker({ | |||
map: map, | |||
position: m.position, | |||
content: `<div class="access-marker">${m.title}</div>` | |||
}); | |||
}); | |||
} | |||
// 分类筛选功能 | |||
document.querySelectorAll('.category-btn').forEach(btn => { | |||
btn.addEventListener('click', function () { | |||
// 移除所有激活状态 | |||
document.querySelectorAll('.category-btn').forEach(b => b.classList.remove('active')); | |||
this.classList.add('active'); | |||
// 获取分类 | |||
const category = this.dataset.category; | |||
const goods = document.querySelectorAll('.goods-card'); | |||
goods.forEach(item => { | |||
if (category === 'all' || item.dataset.category === category) { | |||
item.style.display = 'block'; | |||
} else { | |||
item.style.display = 'none'; | |||
} | |||
}); | |||
}); | |||
}); | |||
// 搜索功能 | |||
document.querySelector('.search-btn').addEventListener('click', () => { | |||
const keyword = document.querySelector('.search-input').value.toLowerCase(); | |||
document.querySelectorAll('.job-card').forEach(card => { | |||
const text = card.textContent.toLowerCase(); | |||
card.style.display = text.includes(keyword) ? 'block' : 'none'; | |||
}); | |||
}); | |||
</script> | |||
</body> | |||
</html> |
@ -0,0 +1,270 @@ | |||
{% load static %} | |||
<!DOCTYPE html> | |||
<html lang="zh-CN"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
<title>手语通WaveSign - 登录</title> | |||
<link href="https://fonts.googleapis.com/css2?family=Pacifico&family=Poppins:wght@400;600&display=swap" | |||
rel="stylesheet"> | |||
<style> | |||
▪ { | |||
margin: 0; | |||
padding: 0; | |||
box-sizing: border-box; | |||
} | |||
body { | |||
min-height: 100vh; | |||
background: linear-gradient(180deg, #d3c8e8 0%, #b762bf 100%); | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
padding: 0 5%; | |||
position: relative; | |||
overflow: hidden; | |||
} | |||
/* 增强品牌标题 */ | |||
.brand-title { | |||
position: absolute; | |||
top: 3%; | |||
left: 5%; | |||
font-family: 'Pacifico', cursive; | |||
font-size: 4.8rem; | |||
/* 放大标题 */ | |||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); | |||
z-index: 2; | |||
} | |||
.brand-title span:first-child { | |||
color: #8a4fff; | |||
} | |||
.brand-title span:last-child { | |||
color: #ffffff; | |||
text-shadow: | |||
-1px -1px 0 #8a4fff, | |||
1px -1px 0 #cb6dea, | |||
-1px 1px 0 #811e93, | |||
1px 1px 0 #77639e; | |||
} | |||
/* 新增透视效果图片容器 */ | |||
.left-image { | |||
flex: 1; | |||
max-width: 800px; | |||
padding-right: 60px; | |||
margin-top: 80px; | |||
transform: perspective(1500px) rotateY(-15deg); | |||
transform-origin: left center; | |||
} | |||
.left-image img { | |||
width: 90%; | |||
height: auto; | |||
filter: drop-shadow(0 0 30px rgba(138, 79, 255, 0.3)); | |||
transform: scale(1.1); | |||
} | |||
/* 增强登录表单 */ | |||
.login-container { | |||
background: rgba(255, 255, 255, 0.97); | |||
padding: 40px; | |||
border-radius: 20px; | |||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15); | |||
width: 400px; | |||
margin-right: 5%; | |||
backdrop-filter: blur(5px); | |||
} | |||
h1 { | |||
color: #666; | |||
margin-bottom: 30px; | |||
text-align: center; | |||
font-family: 'Poppins', sans-serif; | |||
font-weight: 600; | |||
font-size: 2rem; | |||
/* 放大表单标题 */ | |||
} | |||
/* 新增表单样式 */ | |||
.form-group { | |||
margin-bottom: 20px; | |||
} | |||
input[type="text"], | |||
input[type="password"] { | |||
width: 90%; | |||
padding: 12px 20px; | |||
border: 2px solid #e0e0e0; | |||
border-radius: 8px; | |||
font-size: 16px; | |||
transition: all 0.3s; | |||
} | |||
input:focus { | |||
border-color: #bf6bd0; | |||
box-shadow: 0 0 8px rgba(138, 79, 255, 0.2); | |||
} | |||
.remember-me { | |||
display: flex; | |||
align-items: center; | |||
margin: 15px 0; | |||
color: #666; | |||
} | |||
button { | |||
width: 100%; | |||
padding: 14px; | |||
background: linear-gradient(135deg, #b952d5, #b84ebe); | |||
color: rgb(100, 65, 95); | |||
border: none; | |||
border-radius: 8px; | |||
font-size: 16px; | |||
cursor: pointer; | |||
transition: transform 0.2s; | |||
} | |||
button:hover { | |||
transform: translateY(-2px); | |||
} | |||
.register-section { | |||
margin-top: 25px; | |||
text-align: center; | |||
padding-top: 20px; | |||
border-top: 1px solid #eee; | |||
} | |||
.register-text { | |||
color: #666; | |||
font-size: 16px; | |||
margin-right: 8px; | |||
} | |||
.register-link { | |||
color: #8a4fff; | |||
text-decoration: none; | |||
font-weight: 600; | |||
background: linear-gradient(120deg, #8a4fff, #b762bf); | |||
background-clip: text; | |||
-webkit-background-clip: text; | |||
-webkit-text-fill-color: transparent; | |||
transition: all 0.3s; | |||
font-size: 16px; | |||
} | |||
.register-link:hover { | |||
text-shadow: 0 2px 5px rgba(138, 79, 255, 0.3); | |||
transform: translateY(-1px); | |||
} | |||
@media (max-width: 768px) { | |||
.brand-title { | |||
font-size: 2.5rem; | |||
top: 3%; | |||
} | |||
.left-image { | |||
transform: none; | |||
padding-right: 20px; | |||
} | |||
.left-image img { | |||
width: 100%; | |||
} | |||
} | |||
.message-container { | |||
margin-bottom: 20px; | |||
} | |||
.message { | |||
padding: 12px 20px; | |||
border-radius: 8px; | |||
font-family: 'Poppins', sans-serif; | |||
font-size: 14px; | |||
margin-bottom: 10px; | |||
text-align: center; | |||
animation: fade-in 0.4s ease-out; | |||
} | |||
.message.error { | |||
background-color: #ffe5e5; | |||
color: #d60000; | |||
border: 1px solid #f5c2c7; | |||
} | |||
.message.success { | |||
background-color: #e9f6ea; | |||
color: #3c763d; | |||
border: 1px solid #b4e2be; | |||
} | |||
@keyframes fade-in { | |||
from { | |||
opacity: 0; | |||
transform: translateY(-5px); | |||
} | |||
to { | |||
opacity: 1; | |||
transform: translateY(0); | |||
} | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div class="brand-title"> | |||
<span>Sign</span><span>Link</span> | |||
</div> | |||
<div class="left-image"> | |||
<img src="{% static 'images/登录1.jpg' %}" alt="图片"> | |||
</div> | |||
<div class="login-container"> | |||
<h1>欢迎登录 WaveSign</h1> | |||
{% if messages %} | |||
<div class="message-container"> | |||
{% for message in messages %} | |||
<div class="message {{ message.tags }}"> | |||
{{ message }} | |||
</div> | |||
{% endfor %} | |||
</div> | |||
{% endif %} | |||
<form method="post" action="{% url 'login' %}"> | |||
{% csrf_token %} | |||
<div class="form-group"> | |||
<input type="text" name="username" placeholder="请输入账号" required> | |||
</div> | |||
<div class="form-group"> | |||
<input type="password" name="password" placeholder="请输入密码" required> | |||
</div> | |||
<div class="remember-me"> | |||
<input type="checkbox" id="remember"> | |||
<label for="remember">记住密码</label> | |||
</div> | |||
<button type="submit">立即登录</button> | |||
<div class="register-section"> | |||
<span class="register-text">没有账号?</span> | |||
<a href="{% url 'register' %}" class="register-link">立即注册</a> | |||
</div> | |||
</form> | |||
</div> | |||
<script> | |||
function validateForm() { | |||
// 这里可以添加实际验证逻辑 | |||
window.location.href = "{% url 'home' %}"; | |||
return false; | |||
} | |||
</script> | |||
</body> | |||
</html> |
@ -0,0 +1,238 @@ | |||
{% load static %} | |||
<!DOCTYPE html> | |||
<html lang="zh-CN"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
<title>手语通WaveSign - 注册</title> | |||
<link href="https://fonts.googleapis.com/css2?family=Pacifico&family=Poppins:wght@400;600&display=swap" | |||
rel="stylesheet"> | |||
<style> | |||
* { | |||
margin: 0; | |||
padding: 0; | |||
box-sizing: border-box; | |||
} | |||
body { | |||
min-height: 100vh; | |||
background: linear-gradient(180deg, #d3c8e8 0%, #b762bf 100%); | |||
display: flex; | |||
justify-content: center; | |||
/* 修改为居中布局 */ | |||
align-items: center; | |||
position: relative; | |||
} | |||
.brand-title { | |||
position: absolute; | |||
top: 3%; | |||
left: 15%; | |||
transform: translateX(-50%); | |||
font-family: 'Pacifico', cursive; | |||
font-size: 4.8rem; | |||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); | |||
z-index: 2; | |||
white-space: nowrap; | |||
} | |||
.brand-title span:first-child { | |||
color: #8a4fff; | |||
} | |||
.brand-title span:last-child { | |||
color: #ffffff; | |||
text-shadow: | |||
-1px -1px 0 #8a4fff, | |||
1px -1px 0 #cb6dea, | |||
-1px 1px 0 #811e93, | |||
1px 1px 0 #77639e; | |||
} | |||
.register-container { | |||
background: rgba(255, 255, 255, 0.97); | |||
padding: 40px; | |||
border-radius: 20px; | |||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15); | |||
width: 450px; | |||
backdrop-filter: blur(5px); | |||
margin-top: 80px; | |||
/* 为品牌标题留出空间 */ | |||
} | |||
h1 { | |||
color: #666; | |||
margin-bottom: 30px; | |||
text-align: center; | |||
font-family: 'Poppins', sans-serif; | |||
font-weight: 600; | |||
font-size: 2rem; | |||
} | |||
.form-group { | |||
margin-bottom: 20px; | |||
} | |||
input[type="text"], | |||
input[type="email"], | |||
input[type="password"] { | |||
width: 100%; | |||
padding: 12px 20px; | |||
border: 2px solid #e0e0e0; | |||
border-radius: 8px; | |||
font-size: 16px; | |||
transition: all 0.3s; | |||
} | |||
input:focus { | |||
border-color: #bf6bd0; | |||
box-shadow: 0 0 8px rgba(138, 79, 255, 0.2); | |||
} | |||
.password-requirements { | |||
color: #666; | |||
font-size: 12px; | |||
margin: 10px 0; | |||
padding-left: 10px; | |||
} | |||
.terms { | |||
margin: 15px 0; | |||
font-size: 13px; | |||
color: #666; | |||
display: flex; | |||
align-items: center; | |||
} | |||
.terms input[type="checkbox"] { | |||
margin-right: 8px; | |||
} | |||
.terms a { | |||
color: #8a4fff; | |||
text-decoration: none; | |||
margin-left: 5px; | |||
} | |||
.errorlist { | |||
color: #dc3545; | |||
list-style: none; | |||
padding: 10px 0; | |||
font-size: 14px; | |||
} | |||
button[type="submit"] { | |||
width: 100%; | |||
padding: 14px; | |||
background: linear-gradient(135deg, #b952d5, #b84ebe); | |||
color: white; | |||
border: none; | |||
border-radius: 8px; | |||
font-size: 16px; | |||
cursor: pointer; | |||
transition: transform 0.2s; | |||
font-family: 'Poppins', sans-serif; | |||
} | |||
button[type="submit"]:hover { | |||
transform: translateY(-2px); | |||
} | |||
@media (max-width: 768px) { | |||
.brand-title { | |||
font-size: 2.5rem; | |||
top: 5%; | |||
} | |||
.register-container { | |||
width: 90%; | |||
margin: 80px 5% 0; | |||
padding: 25px; | |||
} | |||
h1 { | |||
font-size: 1.8rem; | |||
} | |||
} | |||
@media (max-width: 480px) { | |||
.brand-title { | |||
font-size: 2rem; | |||
} | |||
input[type="text"], | |||
input[type="email"], | |||
input[type="password"] { | |||
padding: 10px 15px; | |||
font-size: 14px; | |||
} | |||
button[type="submit"] { | |||
padding: 12px; | |||
font-size: 14px; | |||
} | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div class="brand-title"> | |||
<span>Sign</span><span>Link</span> | |||
</div> | |||
<div class="register-container"> | |||
<h1>加入 WaveSign 社区</h1> | |||
<form method="post"> | |||
{% csrf_token %} | |||
<!-- 用户名 --> | |||
<div class="form-group"> | |||
{{ form.username }} | |||
{% for error in form.username.errors %} | |||
<div class="errorlist">{{ error }}</div> | |||
{% endfor %} | |||
<div class="hint-text">(4-16位字符)</div> | |||
</div> | |||
<!-- 邮箱 --> | |||
<div class="form-group"> | |||
{{ form.email }} | |||
{% for error in form.email.errors %} | |||
<div class="errorlist">{{ error }}</div> | |||
{% endfor %} | |||
</div> | |||
<!-- 密码 --> | |||
<div class="form-group"> | |||
{{ form.password }} | |||
{% for error in form.password.errors %} | |||
<div class="errorlist">{{ error }}</div> | |||
{% endfor %} | |||
<div class="password-requirements">✓ 密码必须为至少8位的字母和数字组合</div> | |||
</div> | |||
<!-- 确认密码 --> | |||
<div class="form-group"> | |||
{{ form.password_confirm }} | |||
{% for error in form.password_confirm.errors %} | |||
<div class="errorlist">{{ error }}</div> | |||
{% endfor %} | |||
</div> | |||
<!-- 同意条款 --> | |||
<div class="terms"> | |||
{{ form.agreed_terms }} | |||
<label for="{{ form.agreed_terms.id_for_label }}">我已阅读并同意 <a href="#">服务条款</a></label> | |||
{% for error in form.agreed_terms.errors %} | |||
<div class="errorlist">{{ error }}</div> | |||
{% endfor %} | |||
</div> | |||
<button type="submit">创建账号</button> | |||
</form> | |||
</div> | |||
</body> | |||
</html> |
@ -0,0 +1,389 @@ | |||
{% load static %} | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
<title>手语教室</title> | |||
<link rel="stylesheet" href="{% static 'css/SLClassroom.css' %}"> | |||
<link rel="stylesheet" href="{% static 'css/login_button.css' %}"> | |||
<!-- 引入 Font Awesome 图标库 --> | |||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet"> | |||
<script src=https://cdn.tailwindcss.com></script> | |||
<!-- mediapipe --> | |||
<script type="module" src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |||
<script src="{% static 'js/SLClassroom.js' %}"></script> | |||
<script type="module" src="{% static 'js/upload_tasks_evaluator.js' %}"></script> | |||
</head> | |||
<body> | |||
<!-- 导航栏 --> | |||
<header class="py-6 px-4 md:px-8 flex justify-between items-center"> | |||
<div class="absolute left-1/2 transform -translate-x-1/2"> | |||
<a class="text-3xl md:text-4xl font-bold fade-in">WaveSign手语通</a> | |||
<p class="text-lg md:text-xl opacity-80 fade-in mt-2">连接双手与世界,构建无声的桥梁</p> | |||
</div> | |||
<div class="flex items-center space-x-4 ml-auto"> | |||
{% if user.is_authenticated %} | |||
<div class="user-menu-box-container"> | |||
<a href="{% url 'my_page' %}" class="user-menu-box-button"> | |||
{% if user.avatar %} | |||
<img src="{{ user.avatar.url }}" alt="头像" class="user-menu-box-avatar"> | |||
{% else %} | |||
<img src="{% static 'images/default_avatar.png' %}" alt="默认头像" class="user-menu-box-avatar"> | |||
{% endif %} | |||
{{ user.username }} | |||
</a> | |||
<div class="user-menu-box-dropdown"> | |||
<a href="{% url 'my_page' %}">进入个人主页</a> | |||
<a href="{% url 'logout' %}">退出登录</a> | |||
</div> | |||
</div> | |||
{% else %} | |||
<a href="{% url 'login' %}" class="user-menu-box-login"> | |||
<i class="fas fa-user"></i> 登录 | |||
</a> | |||
{% endif %} | |||
</div> | |||
</header> | |||
<nav class="sticky top-0 bg-opacity-90 backdrop-blur-sm py-4 px-4 md:px-8 z-50 fade-in" | |||
style=background-color:var(--card-bg)> | |||
<div class="container mx-auto flex justify-center space-x-8"> | |||
<a class="nav-link text-lg font-medium" href="{% url 'home' %}" style="color:var(--text-color)">首页</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'sl_classroom' %}" | |||
style="color:var(--text-color)">手语教室</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'life_serving' %}" | |||
style="color:var(--text-color)">生活服务</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'community' %}" | |||
style="color:var(--text-color)">手语社区</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'schedule' %}" style="color:var(--text-color)">日程管理</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'my_page' %}" style="color:var(--text-color)">我的</a> | |||
</div> | |||
</nav> | |||
<nav class="navbar"> | |||
<div class="logo"> | |||
<span class="title-highlight">🤟 手语教室</span> | |||
</div> | |||
<div class="nav-divider"></div> | |||
<div class="icon-container"> | |||
<!-- 学习进度图标 --> | |||
<div class="hover-container"> | |||
<div class="icon-circle"> | |||
<span class="navbaricon"> | |||
<i class="fas fa-chart-line text-purple-700 text-5xl hover:text-purple-500 transition-all"></i> | |||
</span> | |||
</div> | |||
<div class="hover-card"> | |||
<h3>学习进度</h3> | |||
<p>你已经完成了<span class="font-bold">75%</span>的基础手语课程!</p> | |||
<div class="progress-bar"> | |||
<div class="progress" style="width: 75%;"></div> | |||
</div> | |||
<p>继续努力,你距离下一个奖励只差一步!</p> | |||
</div> | |||
</div> | |||
<!-- 我的积分图标 --> | |||
<div class="hover-container"> | |||
<div class="icon-circle"> | |||
<span class="navbaricon"> | |||
<i class="fas fa-gem text-purple-700 text-5xl hover:text-purple-500 transition-all"></i> | |||
</span> | |||
</div> | |||
<div class="hover-card"> | |||
<h3>我的积分</h3> | |||
<p>你目前拥有 <span class="font-bold">1200</span> 积分!</p> | |||
<p>完成更多任务,获得更多积分!</p> | |||
<div class="bonus-animations"> | |||
<span>积分即将加倍!</span> | |||
<div class="gem-spin"> | |||
<i class="fas fa-gem text-yellow-500 text-4xl spin"></i> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</nav> | |||
<!-- 学习模块 --> | |||
<div class="content"> | |||
<!-- 侧边栏按钮部分 --> | |||
<div class="tab"> | |||
<button class="tablinks" onclick="openTab(event, 'videoPractice')" id="defaultOpen"> | |||
<div class="tab-content flex items-center mb-4"> | |||
<div> | |||
<i class="fas fa-book text-purple-600 dark:text-purple-400 text-xl"></i> | |||
<h3 class="text-xl font-semibold">镜像视频练习</h3> | |||
</div> | |||
</div> | |||
</button> | |||
<button class="tablinks" onclick="openTab(event, 'signLanguageMap')"> | |||
<div class="tab-content flex items-center mb-4"> | |||
<div> | |||
<i class="fas fa-book text-purple-600 dark:text-purple-400 text-xl"></i> | |||
<h3 class="text-xl font-semibold">基础手语课程</h3> | |||
</div> | |||
</div> | |||
</button> | |||
<button class="tablinks" onclick="openTab(event, 'videoLessons')"> | |||
<div class="tab-content flex items-center mb-4"> | |||
<div> | |||
<i class="fas fa-video text-purple-600 dark:text-purple-400 text-xl"></i> | |||
<h3 class="text-xl font-semibold">视频教学</h3> | |||
</div> | |||
</div> | |||
</button> | |||
<button class="tablinks" onclick="openTab(event, 'practiceSection')"> | |||
<div class="tab-content flex items-center mb-4"> | |||
<div> | |||
<i class="fas fa-comments text-purple-600 dark:text-purple-400 text-xl"></i> | |||
<h3 class="text-xl font-semibold">互动练习</h3> | |||
</div> | |||
</div> | |||
</button> | |||
</div> | |||
<!-- 选项卡内容部分 --> | |||
<div class="tabcontent" id="videoPractice"> | |||
<h3 class="text-3xl font-bold mb-6 text-center">📹 上传手语视频进行匹配评分</h3> | |||
<div class="max-w-4xl mx-auto space-y-6 bg-white p-6 rounded-xl shadow-lg"> | |||
<!-- 手势选择下拉 --> | |||
<div class="my-4 flex items-center gap-4"> | |||
<label for="gestureSelect" class="font-medium">选择标准手势:</label> | |||
<select id="gestureSelect" class="px-3 py-1 border rounded text-sm"> | |||
<option value="你好">你好</option> | |||
<option value="早上好">早上好</option> | |||
<option value="谢谢">谢谢</option> | |||
<!-- 你可以继续添加其他手势选项 --> | |||
</select> | |||
</div> | |||
<!-- ✅ 标准参考区 --> | |||
<div class="flex flex-wrap md:flex-nowrap gap-6 my-6"> | |||
<!-- 标准 Canvas 动画 --> | |||
<div> | |||
<h3 class="text-lg font-bold mb-2">🎯 标准手势预览</h3> | |||
<canvas id="standardCanvas" width="320" height="320" class="border rounded shadow"></canvas> | |||
<button id="replayBtn" class="mt-2 bg-blue-500 text-white px-4 py-1 rounded">▶️ 重播手势动画</button> | |||
</div> | |||
<!-- 标准视频窗口 --> | |||
<div class="flex-1"> | |||
<h3 class="text-lg font-bold mb-2">🎥 标准示范视频</h3> | |||
<video id="standardVideo" class="w-full rounded shadow" controls></video> | |||
</div> | |||
</div> | |||
<!-- ✅ 模式选择卡片 --> | |||
<div class="flex justify-center gap-6 mb-6" id="modeCards"> | |||
<div id="card-video" | |||
class="mode-card border rounded-lg p-4 shadow cursor-pointer bg-white text-center transition hover:ring-2 hover:ring-blue-500"> | |||
<div class="text-2xl">📤</div> | |||
<div class="mt-2 font-semibold text-gray-800">上传视频评分</div> | |||
</div> | |||
<div id="card-live" | |||
class="mode-card border rounded-lg p-4 shadow cursor-pointer bg-white text-center transition hover:ring-2 hover:ring-blue-500"> | |||
<div class="text-2xl">📷</div> | |||
<div class="mt-2 font-semibold text-gray-800">实时摄像头评分</div> | |||
</div> | |||
</div> | |||
<!-- ✅ 实时摄像头控件 --> | |||
<div id="liveSection" class="hidden"> | |||
<div class="my-6"> | |||
<h3 class="text-lg font-bold mb-2">📷 实时摄像头预览</h3> | |||
<video id="liveVideo" autoplay muted playsinline style="transform: scaleX(-1);" | |||
class="rounded-xl shadow w-full max-w-md"></video> | |||
<button id="startLiveBtn" class="mt-2 bg-green-500 text-white px-4 py-2 rounded">开始实时匹配</button> | |||
<button id="stopLiveBtn" | |||
class="mt-2 bg-gray-400 text-white px-4 py-2 rounded hidden">停止</button> | |||
<div id="liveFeedback" class="mt-2 text-sm font-medium"></div> | |||
</div> | |||
</div> | |||
<!-- ✅ 视频上传控件 --> | |||
<div id="uploadSection"> | |||
<div class="my-4"> | |||
<label class="block mb-2 text-sm font-medium text-gray-700">📤 上传手语视频</label> | |||
<input type="file" id="videoUpload" accept="video/mp4" | |||
class="block w-full text-sm text-gray-600 border rounded p-2 shadow" /> | |||
</div> | |||
<!-- ✅ 匹配反馈区域 --> | |||
<div id="feedback" | |||
class="text-center text-sm font-medium text-gray-700 bg-gray-100 py-2 px-4 rounded-full w-fit mx-auto shadow"> | |||
请上传视频后等待评分... | |||
</div> | |||
<!-- ✅ 视频播放和隐藏分析用容器 --> | |||
<video id="uploadedVideo" class="w-full bg-gray-200 rounded-xl mt-4" controls></video> | |||
<canvas id="hiddenCanvas" width="640" height="480" class="hidden"></canvas> | |||
<!-- ✅ 高得分帧展示卡片 --> | |||
<div class="mt-8"> | |||
<h3 class="text-lg font-bold mb-2 text-center">✨ 匹配最佳的用户帧展示</h3> | |||
<div id="topFramesContainer" class="flex flex-wrap justify-center gap-6"></div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="tabcontent" id="signLanguageMap"> | |||
<h3>🌍 探索手语世界</h3> | |||
<p>完成每个关卡,逐步解锁手语技能!</p> | |||
<div class="map-container"> | |||
<!-- 已解锁关卡 --> | |||
<div class="map-point unlocked" onclick="openLesson(1)"> | |||
<span class="emoji">👋</span> | |||
<span class="lesson-name">问候语</span> | |||
</div> | |||
<div class="progress-line unlocked-line"></div> | |||
<div class="map-point unlocked" onclick="openLesson(2)"> | |||
<span class="emoji">✋</span> | |||
<span class="lesson-name">日常表达</span> | |||
</div> | |||
<div class="progress-line locked-line"></div> | |||
<!-- 未解锁关卡 --> | |||
<div class="map-point locked"> | |||
<span class="emoji">❓</span> | |||
<span class="lesson-name">家庭成员</span> | |||
</div> | |||
<div class="progress-line locked-line"></div> | |||
<div class="map-point locked"> | |||
<span class="emoji">❓</span> | |||
<span class="lesson-name">食物饮料</span> | |||
</div> | |||
<div class="progress-line locked-line"></div> | |||
<div class="map-point locked"> | |||
<span class="emoji">❓</span> | |||
<span class="lesson-name">日常对话</span> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="tabcontent" id="videoLessons"> | |||
<h3>📚 手语课程视频</h3> | |||
<p>欢迎来到手语教学课堂!点击视频进行学习,完成后可解锁新的内容。</p> | |||
<div class="lesson-container"> | |||
<div class="lesson-card completed"> | |||
<div class="lesson-header"> | |||
<span class="lesson-title">第一课:你好</span> | |||
<i class="fas fa-check-circle completed-icon"></i> | |||
</div> | |||
<iframe src="https://player.bilibili.com/player.html?bvid=BV1LW411s7gR&page=1" width="100%" | |||
height="315" frameborder="0" allowfullscreen></iframe> | |||
<div class="lesson-progress"> | |||
<div class="progress-bar" style="width: 100%;"></div> | |||
</div> | |||
</div> | |||
<div class="lesson-card in-progress"> | |||
<div class="lesson-header"> | |||
<span class="lesson-title">第二课:谢谢</span> | |||
<i class="fas fa-hourglass-half progress-icon"></i> | |||
</div> | |||
<iframe src="https://player.bilibili.com/player.html?bvid=BV1LW411s7gR&page=1" width="100%" | |||
height="315" frameborder="0" allowfullscreen></iframe> | |||
<div class="lesson-progress"> | |||
<div class="progress-bar" style="width: 50%;"></div> | |||
</div> | |||
</div> | |||
<div class="lesson-card locked"> | |||
<div class="lesson-header"> | |||
<span class="lesson-title">第三课:再见</span> | |||
<i class="fas fa-lock lock-icon"></i> | |||
</div> | |||
<p class="locked-message">完成前面课程即可解锁!</p> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="tabcontent" id="practiceSection"> | |||
<h3>✋ 互动练习</h3> | |||
<p>点击卡片查看手语的正确含义!</p> | |||
<div class="practice-container"> | |||
<div class="practice-card" onclick="flipCard(this)"> | |||
<div class="card-front"> | |||
<img src="{% static 'images/sign_you.png' %}" alt="手语示例"> | |||
<span>这个手势的含义是?</span> | |||
</div> | |||
<div class="card-back"> | |||
<span>👉 你</span> | |||
</div> | |||
</div> | |||
<div class="practice-card" onclick="flipCard(this)"> | |||
<div class="card-front"> | |||
<img src="{% static 'images/sign_good.png'%}" alt="手语示例"> | |||
<span>这个手势的含义是?</span> | |||
</div> | |||
<div class="card-back"> | |||
<span>✨ 好</span> | |||
</div> | |||
</div> | |||
<div class="practice-card" onclick="flipCard(this)"> | |||
<div class="card-front"> | |||
<img src="{% static 'images/sign_thank_you.png'%}" alt="手语示例"> | |||
<span>这个手势的含义是?</span> | |||
</div> | |||
<div class="card-back"> | |||
<span>🙏 谢谢!</span> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<footer class="py-8 px-4 md:px-8 text-center fade-in" style=animation-delay:1.2s> | |||
<div class="flex flex-col items-center"> | |||
<div class="mb-4 text-lg font-medium">作者: backpack</div> | |||
<p class="text-sm opacity-70">© 2025 WaveSign. | |||
</div> | |||
</footer> | |||
</div> | |||
</body> | |||
<script src="{% static 'js/SLClassroom.js' %}"></script> | |||
</body> |
@ -0,0 +1,983 @@ | |||
{% load static %} | |||
<!DOCTYPE html> | |||
<html lang="zh-CN"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
<title>WaveSign手语通 - 日程管理</title> | |||
<link rel="stylesheet" href="{% static 'css/login_button.css' %}"> | |||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css"> | |||
<script src="https://cdn.tailwindcss.com"></script> | |||
<script> | |||
// 配置Tailwind自定义颜色 | |||
tailwind.config = { | |||
theme: { | |||
extend: { | |||
colors: { | |||
primary: '#8d6a95', | |||
secondary: '#cebbf3', | |||
dark: '#1a1a1a', | |||
light: '#f9f9f9', | |||
}, | |||
} | |||
} | |||
} | |||
</script> | |||
<style type="text/tailwindcss"> | |||
:root { | |||
--primary: #8d6a95; | |||
--primary-light: #cebbf3; | |||
--bg-color: #f9f9f9; | |||
--card-bg: #fff; | |||
--text-color: hsl(301, 89%, 21%); | |||
--border-color: #e5e5e5; | |||
} | |||
body { | |||
background-color: var(--bg-color); | |||
color: var(--text-color); | |||
font-family: 'Noto Sans SC', sans-serif; | |||
transition: background-color .3s, color .3s; | |||
padding: 2rem 5%; | |||
max-width: 1440px; | |||
margin: 0 auto; | |||
box-sizing: border-box; | |||
} | |||
@media (min-width: 1600px) { | |||
body { | |||
padding: 2rem 8%; | |||
} | |||
} | |||
@media (max-width: 768px) { | |||
body { | |||
padding: 1.5rem; | |||
} | |||
} | |||
.main-container { | |||
padding: 0 2rem; | |||
margin: 0 auto; | |||
} | |||
.card { | |||
background-color: var(--card-bg); | |||
border: 1px solid var(--border-color); | |||
transition: transform .3s, box-shadow .3s, border-color .3s; | |||
margin: 1rem 0; | |||
padding: 1.5rem; | |||
} | |||
.card:hover { | |||
border-color: var(--primary); | |||
transform: translateY(-5px); | |||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); | |||
} | |||
.btn { | |||
transition: transform .2s, background-color .2s; | |||
} | |||
.btn:hover { | |||
transform: scale(1.05); | |||
} | |||
.fade-in { | |||
animation: .8s ease-in fadeIn; | |||
} | |||
@keyframes fadeIn { | |||
0% { | |||
opacity: 0; | |||
transform: translateY(20px); | |||
} | |||
to { | |||
opacity: 1; | |||
transform: translateY(0); | |||
} | |||
} | |||
.nav-link { | |||
position: relative; | |||
} | |||
.nav-link:after { | |||
content: ""; | |||
background-color: var(--primary); | |||
width: 0; | |||
height: 2px; | |||
transition: width .3s; | |||
position: absolute; | |||
bottom: -2px; | |||
left: 0; | |||
} | |||
.nav-link:hover:after, | |||
.nav-link.active:after { | |||
width: 100%; | |||
} | |||
/* 日历专用样式 */ | |||
.calendar-day { | |||
min-height: 100px; | |||
border: 1px solid var(--border-color); | |||
border-radius: 0.25rem; | |||
padding: 0.5rem; | |||
transition: all 0.2s ease; | |||
} | |||
.calendar-day:hover { | |||
background-color: rgba(206, 187, 243, 0.1); | |||
} | |||
.day-header { | |||
background-color: var(--card-bg); | |||
font-weight: bold; | |||
text-align: center; | |||
padding: 0.5rem; | |||
border-radius: 0.25rem; | |||
} | |||
.day-number { | |||
font-size: 1.2rem; | |||
font-weight: 600; | |||
color: var(--primary); | |||
} | |||
.event-marker { | |||
position: absolute; | |||
bottom: 0.5rem; | |||
left: 0.5rem; | |||
right: 0.5rem; | |||
display: flex; | |||
flex-wrap: wrap; | |||
gap: 0.25rem; | |||
} | |||
.event-dot { | |||
width: 0.5rem; | |||
height: 0.5rem; | |||
border-radius: 9999px; | |||
background-color: var(--primary); | |||
} | |||
</style> | |||
</head> | |||
<body > | |||
<header class="py-6 px-4 md:px-8 flex justify-between items-center"> | |||
<div class="absolute left-1/2 transform -translate-x-1/2"> | |||
<a class="text-3xl md:text-4xl font-bold fade-in">WaveSign手语通</a> | |||
<p class="text-lg md:text-xl opacity-80 fade-in mt-2">连接双手与世界,构建无声的桥梁</p> | |||
</div> | |||
<div class="flex items-center space-x-4 ml-auto"> | |||
<div class="flex items-center space-x-4 ml-auto"> | |||
{% if user.is_authenticated %} | |||
<div class="user-menu-box-container"> | |||
<a href="{% url 'my_page' %}" class="user-menu-box-button"> | |||
{% if user.avatar %} | |||
<img src="{{ user.avatar.url }}" alt="头像" class="user-menu-box-avatar"> | |||
{% else %} | |||
<img src="{% static 'images/default_avatar.png' %}" alt="默认头像" class="user-menu-box-avatar"> | |||
{% endif %} | |||
{{ user.username }} | |||
</a> | |||
<div class="user-menu-box-dropdown"> | |||
<a href="{% url 'my_page' %}">进入个人主页</a> | |||
<a href="{% url 'logout' %}">退出登录</a> | |||
</div> | |||
</div> | |||
{% else %} | |||
<a href="{% url 'login' %}" class="user-menu-box-login"> | |||
<i class="fas fa-user"></i> 登录 | |||
</a> | |||
{% endif %} | |||
</div> | |||
</header> | |||
<nav class="sticky top-0 bg-opacity-90 backdrop-blur-sm py-4 px-4 md:px-8 z-50 fade-in" style="background-color:var(--card-bg)"> | |||
<div class="container mx-auto flex justify-center space-x-8"> | |||
<a class="nav-link text-lg font-medium" href="{% url 'home' %}" style="color:var(--text-color)">首页</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'sl_classroom' %}" style="color:var(--text-color)">手语教室</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'life_serving' %}" style="color:var(--text-color)">生活服务</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'community' %}" style="color:var(--text-color)">手语社区</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'schedule' %}" style="color:var(--text-color)">日程管理</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'my_page' %}" style="color:var(--text-color)">我的</a> | |||
</div> | |||
</nav> | |||
<!-- 主内容区 --> | |||
<main class="container mx-auto px-4 py-8"> | |||
<div class="mb-8 fade-in"> | |||
<h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-primary flex items-center"> | |||
<i class="fa fa-calendar-check-o mr-3"></i>日程管理 | |||
</h2> | |||
</div> | |||
<!-- 日历容器 --> | |||
<div class="card rounded-xl hover:shadow-lg mb-8"> | |||
<div class="flex justify-between items-center mb-6"> | |||
<h3 class="text-xl font-bold text-primary flex items-center"> | |||
<i class="fa fa-calendar mr-2"></i>我的日历 | |||
</h3> | |||
<div class="flex space-x-2"> | |||
<button onclick="prevMonth()" class="p-2 rounded-full hover:bg-primary-light/50 transition-colors"> | |||
<i class="fa fa-chevron-left text-primary"></i> | |||
</button> | |||
<button onclick="goToToday()" class="px-4 py-2 bg-primary text-white rounded-full hover:bg-primary-light transition-colors"> | |||
今天 | |||
</button> | |||
<button onclick="nextMonth()" class="p-2 rounded-full hover:bg-primary-light/50 transition-colors"> | |||
<i class="fa fa-chevron-right text-primary"></i> | |||
</button> | |||
</div> | |||
</div> | |||
<div class="mb-4"> | |||
<h4 id="currentMonthDisplay" class="text-2xl font-bold text-center text-primary"></h4> | |||
</div> | |||
<div id="calendar" class="mb-8"></div> | |||
</div> | |||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6"> | |||
<!-- 待办事项区域 --> | |||
<div class="lg:col-span-5 card rounded-xl hover:shadow-lg"> | |||
<div class="flex justify-between items-center mb-6"> | |||
<h3 class="text-xl font-bold text-primary flex items-center"> | |||
<i class="fa fa-tasks mr-2"></i>待办清单 | |||
</h3> | |||
<div class="relative"> | |||
<span id="taskCount" class="bg-primary text-white text-xs rounded-full px-2 py-1">0</span> | |||
</div> | |||
</div> | |||
<div class="mb-4"> | |||
<div class="flex space-x-2"> | |||
<input type="text" id="newTaskInput" placeholder="添加新任务..." | |||
class="flex-1 px-4 py-2 border border-light rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 bg-transparent text-black"> | |||
<button id="addTaskBtn" class="bg-primary text-black px-4 py-2 rounded-lg hover:bg-primary-light transition-colors"> | |||
<i class="fa fa-plus"></i> | |||
</button> | |||
</div> | |||
</div> | |||
<div id="taskList" class="space-y-2 max-h-[500px] overflow-y-auto pr-2"> | |||
<!-- 待办事项将通过JavaScript动态生成 --> | |||
<div class="text-center py-8 text-gray-400"> | |||
<i class="fa fa-check-circle text-4xl mb-3"></i> | |||
<p>暂无待办事项</p> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 日程表单区域 --> | |||
<div class="lg:col-span-7 card rounded-xl hover:shadow-lg"> | |||
<h3 class="text-xl font-bold text-primary flex items-center mb-6"> | |||
<i class="fa fa-plus-circle mr-2"></i>添加日程 | |||
</h3> | |||
<form id="eventForm" class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |||
<div class="form-group"> | |||
<label for="eventTitle" class="block text-sm font-medium text-gray-700 mb-1">事件名称</label> | |||
<input type="text" id="eventTitle" class="w-full px-4 py-2 border border-light rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 bg-transparent text-black" placeholder="输入事件名称"> | |||
</div> | |||
<div class="form-group"> | |||
<label for="eventDate" class="block text-sm font-medium text-gray-700 mb-1">日期</label> | |||
<input type="date" id="eventDate" class="w-full px-4 py-2 border border-light rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 bg-transparent text-black"> | |||
</div> | |||
<div class="form-group"> | |||
<label for="eventTime" class="block text-sm font-medium text-gray-700 mb-1">时间</label> | |||
<input type="time" id="eventTime" class="w-full px-4 py-2 border border-light rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 bg-transparent text-black"> | |||
</div> | |||
<div class="form-group md:col-span-3"> | |||
<label for="eventDescription" class="block text-sm font-medium text-gray-700 mb-1">描述</label> | |||
<textarea id="eventDescription" rows="3" class="w-full px-4 py-2 border border-light rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 bg-transparent text-black" placeholder="输入事件描述(可选)"></textarea> | |||
</div> | |||
<div class="form-group md:col-span-3 flex justify-end"> | |||
<button type="button" id="submitEventBtn" class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-primary-light transition-colors flex items-center"> | |||
<i class="fa fa-calendar-plus-o mr-2"></i>添加日程 | |||
</button> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
<!-- 事件模态框 --> | |||
<div id="eventModal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50"> | |||
<div id="eventModalContent" class="bg-white rounded-lg p-6 max-w-lg w-full mx-4"> | |||
<!-- 模态框内容将由 JavaScript 动态填充 --> | |||
</div> | |||
</div> | |||
<!-- 添加事件按钮 --> | |||
<button onclick="showAddEventModal()" class="fixed bottom-8 right-8 bg-primary text-white p-4 rounded-full shadow-lg hover:bg-primary-dark transition-colors"> | |||
<i class="fa fa-plus"></i> | |||
</button> | |||
<!-- 添加事件模态框 --> | |||
<div id="addEventModal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50"> | |||
<div class="bg-white rounded-lg p-6 max-w-lg w-full mx-4"> | |||
<h3 class="text-xl font-bold mb-4">添加新事件</h3> | |||
<form id="addEventForm" onsubmit="addEvent(); return false;"> | |||
<div class="mb-4"> | |||
<label class="block text-gray-700 text-sm font-bold mb-2" for="eventTitle"> | |||
事件名称 | |||
</label> | |||
<input type="text" id="eventTitle" name="title" required | |||
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"> | |||
</div> | |||
<div class="mb-4"> | |||
<label class="block text-gray-700 text-sm font-bold mb-2" for="eventDate"> | |||
日期 | |||
</label> | |||
<input type="date" id="eventDate" name="date" required | |||
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"> | |||
</div> | |||
<div class="mb-4"> | |||
<label class="block text-gray-700 text-sm font-bold mb-2" for="eventTime"> | |||
时间 | |||
</label> | |||
<input type="time" id="eventTime" name="time" required | |||
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"> | |||
</div> | |||
<div class="mb-4"> | |||
<label class="block text-gray-700 text-sm font-bold mb-2" for="eventDescription"> | |||
描述 | |||
</label> | |||
<textarea id="eventDescription" name="description" | |||
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"></textarea> | |||
</div> | |||
<div class="flex justify-end space-x-2"> | |||
<button type="button" onclick="closeAddEventModal()" | |||
class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">取消</button> | |||
<button type="submit" | |||
class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-dark">保存</button> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
</main> | |||
<footer class="py-8 px-4 md:px-8 text-center fade-in"> | |||
<div class="flex flex-col items-center"> | |||
<div class="mb-4 text-lg font-medium">作者: backpack</div> | |||
<p class="text-sm opacity-70">© 2025 WaveSign.</p> | |||
</div> | |||
</footer> | |||
<script> | |||
// 获取CSRF Token | |||
function getCookie(name) { | |||
let cookieValue = null; | |||
if (document.cookie && document.cookie !== '') { | |||
const cookies = document.cookie.split(';'); | |||
for (let i = 0; i < cookies.length; i++) { | |||
const cookie = cookies[i].trim(); | |||
if (cookie.substring(0, name.length + 1) === (name + '=')) { | |||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); | |||
break; | |||
} | |||
} | |||
} | |||
return cookieValue; | |||
} | |||
// 显示添加事件模态框 | |||
function showAddEventModal() { | |||
const modal = document.getElementById('addEventModal'); | |||
modal.classList.remove('hidden'); | |||
} | |||
// 关闭添加事件模态框 | |||
function closeAddEventModal() { | |||
const modal = document.getElementById('addEventModal'); | |||
modal.classList.add('hidden'); | |||
} | |||
// 显示通知 | |||
function showNotification(message) { | |||
const notification = document.createElement('div'); | |||
notification.className = 'fixed bottom-4 right-4 bg-primary text-white px-4 py-2 rounded-lg shadow-lg z-50 fade-in'; | |||
notification.textContent = message; | |||
document.body.appendChild(notification); | |||
setTimeout(() => { | |||
notification.style.opacity = '0'; | |||
notification.style.transform = 'translateY(10px)'; | |||
notification.style.transition = 'opacity 0.3s, transform 0.3s'; | |||
setTimeout(() => { | |||
notification.remove(); | |||
}, 300); | |||
}, 3000); | |||
} | |||
// 格式化日期为 YYYY-MM-DD | |||
function formatDate(date) { | |||
const year = date.getFullYear(); | |||
const month = String(date.getMonth() + 1).padStart(2, '0'); | |||
const day = String(date.getDate()).padStart(2, '0'); | |||
return `${year}-${month}-${day}`; | |||
} | |||
// 当前日期 | |||
const today = new Date(); | |||
let currentDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()); | |||
// 获取月数据 | |||
function fetchMonthData(date) { | |||
const year = date.getFullYear(); | |||
const month = date.getMonth(); | |||
const firstDay = new Date(year, month, 1); | |||
const lastDay = new Date(year, month + 1, 0); | |||
fetch(`/Schedule/events/?start_date=${formatDate(firstDay)}&end_date=${formatDate(lastDay)}`, { | |||
method: 'GET', | |||
headers: { | |||
'Content-Type': 'application/json', | |||
'X-CSRFToken': getCookie('csrftoken') | |||
}, | |||
credentials: 'include' | |||
}) | |||
.then(response => { | |||
if (response.status === 401) { | |||
throw new Error('请先登录'); | |||
} | |||
if (!response.ok) { | |||
throw new Error(`HTTP error! status: ${response.status}`); | |||
} | |||
return response.json(); | |||
}) | |||
.then(data => { | |||
// 检查数据格式 | |||
if (!data || typeof data !== 'object') { | |||
throw new Error('Invalid data format received from server'); | |||
} | |||
// 确保 data 是数组 | |||
const eventsArray = Array.isArray(data) ? data : []; | |||
// 将事件数据按日期分组 | |||
const eventsByDate = {}; | |||
eventsArray.forEach(event => { | |||
if (event && event.date) { | |||
const date = event.date; | |||
if (!eventsByDate[date]) { | |||
eventsByDate[date] = []; | |||
} | |||
eventsByDate[date].push(event); | |||
} | |||
}); | |||
// 渲染日历 | |||
renderCalendar(eventsByDate); | |||
}) | |||
.catch(error => { | |||
console.error('Error fetching month data:', error); | |||
const calendar = document.getElementById('calendar'); | |||
if (calendar) { | |||
if (error.message === '请先登录') { | |||
calendar.innerHTML = ` | |||
<div class="col-span-7 text-center py-10 text-red-500"> | |||
<i class="fa fa-exclamation-triangle text-2xl mb-2"></i> | |||
<p>请先登录</p> | |||
<p class="text-sm mt-2">您需要登录才能查看日程</p> | |||
<a href="{% url 'login' %}" class="inline-block mt-4 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-light transition-colors"> | |||
去登录 | |||
</a> | |||
</div> | |||
`; | |||
} else { | |||
calendar.innerHTML = ` | |||
<div class="col-span-7 text-center py-10 text-red-500"> | |||
<i class="fa fa-exclamation-triangle text-2xl mb-2"></i> | |||
<p>加载日历数据失败</p> | |||
<p class="text-sm mt-2">${error.message}</p> | |||
<p class="text-sm mt-2">请检查后端服务是否正常运行</p> | |||
</div> | |||
`; | |||
} | |||
} | |||
}); | |||
} | |||
// 渲染日历 | |||
function renderCalendar(eventsByDate = {}) { | |||
const calendar = document.getElementById('calendar'); | |||
if (!calendar) { | |||
console.error('Calendar container not found'); | |||
return; | |||
} | |||
calendar.innerHTML = ''; | |||
// 更新月份显示 | |||
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']; | |||
document.getElementById('currentMonthDisplay').textContent = | |||
`${currentDate.getFullYear()}年${monthNames[currentDate.getMonth()]}`; | |||
// 添加星期头部 | |||
const weekDays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']; | |||
const weekHeader = document.createElement('div'); | |||
weekHeader.className = 'grid grid-cols-7 gap-2 mb-2'; | |||
weekDays.forEach(day => { | |||
const dayHeader = document.createElement('div'); | |||
dayHeader.className = 'day-header text-center py-2'; | |||
if (day === '周六' || day === '周日') { | |||
dayHeader.classList.add('text-amber-600'); | |||
} | |||
dayHeader.textContent = day; | |||
weekHeader.appendChild(dayHeader); | |||
}); | |||
calendar.appendChild(weekHeader); | |||
// 计算当月第一天是星期几 (0-6, 0是星期日) | |||
const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1); | |||
const lastDay = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0); | |||
const startingDay = firstDay.getDay() || 7; // 调整为星期一为第一天 | |||
const totalDays = lastDay.getDate(); | |||
// 创建日历网格 | |||
const calendarGrid = document.createElement('div'); | |||
calendarGrid.className = 'grid grid-cols-7 gap-2'; | |||
// 添加上个月的占位日期 | |||
for (let i = 1; i < startingDay; i++) { | |||
const emptyDay = document.createElement('div'); | |||
emptyDay.className = 'calendar-day text-gray-300'; | |||
calendarGrid.appendChild(emptyDay); | |||
} | |||
// 添加当月的日期 | |||
for (let day = 1; day <= totalDays; day++) { | |||
const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); | |||
const dateStr = formatDate(date); | |||
const dayCell = document.createElement('div'); | |||
dayCell.className = 'calendar-day relative'; | |||
// 检查是否是今天 | |||
const isToday = ( | |||
day === today.getDate() && | |||
currentDate.getMonth() === today.getMonth() && | |||
currentDate.getFullYear() === today.getFullYear() | |||
); | |||
if (isToday) { | |||
dayCell.classList.add('bg-primary/10', 'border-primary'); | |||
} | |||
// 添加日期 | |||
const dateHeader = document.createElement('div'); | |||
dateHeader.className = 'day-number mb-1'; | |||
dateHeader.textContent = day; | |||
dayCell.appendChild(dateHeader); | |||
// 添加事件 | |||
if (eventsByDate[dateStr]) { | |||
const eventsContainer = document.createElement('div'); | |||
eventsContainer.className = 'event-list space-y-1'; | |||
eventsByDate[dateStr].forEach(event => { | |||
const eventElement = document.createElement('div'); | |||
eventElement.className = 'event-item p-1 text-sm rounded cursor-pointer hover:bg-gray-100 truncate'; | |||
eventElement.textContent = event.title; | |||
eventElement.title = event.title; // 添加悬停提示 | |||
eventElement.onclick = () => showEventDetails(event); | |||
eventsContainer.appendChild(eventElement); | |||
}); | |||
dayCell.appendChild(eventsContainer); | |||
} | |||
calendarGrid.appendChild(dayCell); | |||
} | |||
calendar.appendChild(calendarGrid); | |||
} | |||
// 上个月 | |||
function prevMonth() { | |||
currentDate.setMonth(currentDate.getMonth() - 1); | |||
fetchMonthData(currentDate); | |||
} | |||
// 下个月 | |||
function nextMonth() { | |||
currentDate.setMonth(currentDate.getMonth() + 1); | |||
fetchMonthData(currentDate); | |||
} | |||
// 回到今天 | |||
function goToToday() { | |||
currentDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()); | |||
fetchMonthData(currentDate); | |||
} | |||
// 显示事件详情 | |||
function showEventDetails(event) { | |||
const modal = document.getElementById('eventModal'); | |||
const modalContent = document.getElementById('eventModalContent'); | |||
if (!modal || !modalContent) { | |||
console.error('Modal elements not found'); | |||
return; | |||
} | |||
modalContent.innerHTML = ` | |||
<h3 class="text-xl font-bold mb-4">${event.title}</h3> | |||
<p class="mb-2">时间:${event.date} ${event.time}</p> | |||
<p class="mb-4">${event.description || '无描述'}</p> | |||
<div class="flex justify-end space-x-2"> | |||
<button onclick="editEvent(${event.id})" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">编辑</button> | |||
<button onclick="deleteEvent(${event.id})" class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600">删除</button> | |||
<button onclick="closeEventModal()" class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">关闭</button> | |||
</div> | |||
`; | |||
modal.classList.remove('hidden'); | |||
} | |||
// 关闭事件详情模态框 | |||
function closeEventModal() { | |||
const modal = document.getElementById('eventModal'); | |||
if (modal) { | |||
modal.classList.add('hidden'); | |||
} | |||
} | |||
// 添加新事件 | |||
function addEvent() { | |||
const titleInput = document.getElementById('eventTitle'); | |||
const dateInput = document.getElementById('eventDate'); | |||
const timeInput = document.getElementById('eventTime'); | |||
const descriptionInput = document.getElementById('eventDescription'); | |||
const title = titleInput.value.trim(); | |||
const date = dateInput.value; | |||
const time = timeInput.value; | |||
const description = descriptionInput.value.trim(); | |||
if (!title || !date || !time) { | |||
alert('请填写事件名称、日期和时间'); | |||
return; | |||
} | |||
fetch('/Schedule/events/', { | |||
method: 'POST', | |||
headers: { | |||
'Content-Type': 'application/json', | |||
'X-CSRFToken': getCookie('csrftoken') | |||
}, | |||
body: JSON.stringify({ | |||
title: title, | |||
date: date, | |||
time: time, | |||
description: description | |||
}) | |||
}) | |||
.then(response => { | |||
if (!response.ok) { | |||
throw new Error('添加事件失败: ' + response.status); | |||
} | |||
return response.json(); | |||
}) | |||
.then(data => { | |||
// 刷新事件列表 | |||
fetchMonthData(currentDate); | |||
// 清空表单 | |||
titleInput.value = ''; | |||
dateInput.value = ''; | |||
timeInput.value = ''; | |||
descriptionInput.value = ''; | |||
// 关闭模态框 | |||
closeAddEventModal(); | |||
// 显示成功提示 | |||
showNotification('日程添加成功'); | |||
}) | |||
.catch(error => { | |||
console.error('添加事件失败:', error); | |||
alert('添加事件失败: ' + error.message); | |||
}); | |||
} | |||
// 删除事件 | |||
function deleteEvent(eventId) { | |||
if (confirm('确定要删除这个事件吗?')) { | |||
fetch(`/Schedule/events/${eventId}/`, { | |||
method: 'DELETE', | |||
headers: { | |||
'X-CSRFToken': getCookie('csrftoken') | |||
} | |||
}) | |||
.then(response => { | |||
if (!response.ok) { | |||
throw new Error('删除事件失败'); | |||
} | |||
closeEventModal(); | |||
fetchMonthData(currentDate); | |||
showNotification('事件已删除'); | |||
}) | |||
.catch(error => { | |||
console.error('Error deleting event:', error); | |||
alert('删除事件失败'); | |||
}); | |||
} | |||
} | |||
// 初始化 | |||
document.addEventListener('DOMContentLoaded', () => { | |||
fetchMonthData(currentDate); | |||
fetchAndRenderTasks(); | |||
// 绑定事件监听 | |||
document.getElementById('addTaskBtn').addEventListener('click', function() { | |||
const input = document.getElementById('newTaskInput'); | |||
const text = input.value.trim(); | |||
if (!text) { | |||
alert('请输入任务内容'); | |||
return; | |||
} | |||
// 如果没有选择日期,默认使用今天 | |||
const eventDate = document.getElementById('eventDate').value; | |||
const date = eventDate || formatDate(today); | |||
// 先创建事件,然后添加任务 | |||
fetch('/Schedule/events/', { | |||
method: 'POST', | |||
headers: { | |||
'Content-Type': 'application/json', | |||
'X-CSRFToken': getCookie('csrftoken') | |||
}, | |||
credentials: 'include', | |||
body: JSON.stringify({ | |||
title: '待办事项', | |||
date: date, | |||
time: '00:00', | |||
description: '自动创建的待办事项' | |||
}) | |||
}) | |||
.then(response => { | |||
if (!response.ok) { | |||
throw new Error('创建事件失败: ' + response.status); | |||
} | |||
return response.json(); | |||
}) | |||
.then(eventData => { | |||
// 创建任务 | |||
return fetch('/Schedule/tasks/', { | |||
method: 'POST', | |||
headers: { | |||
'Content-Type': 'application/json', | |||
'X-CSRFToken': getCookie('csrftoken') | |||
}, | |||
credentials: 'include', | |||
body: JSON.stringify({ | |||
text: text, | |||
event: eventData.id | |||
}) | |||
}); | |||
}) | |||
.then(response => { | |||
if (!response.ok) { | |||
throw new Error('添加任务失败: ' + response.status); | |||
} | |||
return response.json(); | |||
}) | |||
.then(() => { | |||
// 清空输入框 | |||
input.value = ''; | |||
// 刷新任务列表 | |||
fetchAndRenderTasks(); | |||
// 刷新日历 | |||
fetchMonthData(currentDate); | |||
showNotification('任务添加成功'); | |||
}) | |||
.catch(error => { | |||
console.error('Error:', error); | |||
alert('操作失败: ' + error.message); | |||
}); | |||
}); | |||
document.getElementById('newTaskInput').addEventListener('keypress', function(e) { | |||
if (e.key === 'Enter') { | |||
document.getElementById('addTaskBtn').click(); | |||
} | |||
}); | |||
document.getElementById('submitEventBtn').addEventListener('click', addEvent); | |||
}); | |||
// 从API获取并渲染任务 | |||
function fetchAndRenderTasks(date) { | |||
const targetDate = date || formatDate(today); | |||
fetch(`/Schedule/tasks/?date=${targetDate}`, { | |||
method: 'GET', | |||
headers: { | |||
'Content-Type': 'application/json', | |||
'X-CSRFToken': getCookie('csrftoken') | |||
}, | |||
credentials: 'include' | |||
}) | |||
.then(response => { | |||
if (response.status === 401) { | |||
throw new Error('请先登录'); | |||
} | |||
if (!response.ok) { | |||
throw new Error('获取任务数据失败: ' + response.status); | |||
} | |||
return response.json(); | |||
}) | |||
.then(data => { | |||
window.tasks = data; // 保存到全局变量 | |||
renderTaskList(); | |||
}) | |||
.catch(error => { | |||
console.error('Error fetching tasks:', error); | |||
const taskList = document.getElementById('taskList'); | |||
if (error.message === '请先登录') { | |||
taskList.innerHTML = ` | |||
<div class="text-center py-8 text-red-500"> | |||
<i class="fa fa-exclamation-triangle text-4xl mb-3"></i> | |||
<p>请先登录</p> | |||
<p class="text-sm mt-2">您需要登录才能查看待办事项</p> | |||
<a href="{% url 'login' %}" class="inline-block mt-4 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-light transition-colors"> | |||
去登录 | |||
</a> | |||
</div> | |||
`; | |||
} else { | |||
taskList.innerHTML = ` | |||
<div class="text-center py-8 text-red-500"> | |||
<i class="fa fa-exclamation-triangle text-4xl mb-3"></i> | |||
<p>加载任务数据失败</p> | |||
<p class="text-sm mt-2">${error.message}</p> | |||
</div> | |||
`; | |||
} | |||
}); | |||
} | |||
// 渲染任务列表 | |||
function renderTaskList() { | |||
const taskList = document.getElementById('taskList'); | |||
const taskCount = document.getElementById('taskCount'); | |||
// 更新任务计数 | |||
const incompleteTasks = window.tasks.filter(task => !task.completed); | |||
taskCount.textContent = incompleteTasks.length; | |||
// 清空任务列表 | |||
taskList.innerHTML = ''; | |||
// 如果没有任务,显示空状态 | |||
if (window.tasks.length === 0) { | |||
taskList.innerHTML = ` | |||
<div class="text-center py-8 text-gray-400"> | |||
<i class="fa fa-check-circle text-4xl mb-3"></i> | |||
<p>暂无待办事项</p> | |||
</div> | |||
`; | |||
return; | |||
} | |||
// 渲染每个任务 | |||
window.tasks.forEach(task => { | |||
const taskElement = document.createElement('div'); | |||
taskElement.className = `flex items-center p-3 rounded-lg border border-light ${task.completed ? 'bg-gray-50' : 'bg-white'} transition-all`; | |||
taskElement.innerHTML = ` | |||
<div class="flex items-center"> | |||
<input type="checkbox" id="task-${task.id}" class="mr-3 h-5 w-5 text-primary rounded border-gray-300 focus:ring-primary" ${task.completed ? 'checked' : ''}> | |||
<label for="task-${task.id}" class="flex-1 ${task.completed ? 'text-gray-500 line-through' : ''}">${task.text}</label> | |||
</div> | |||
<button class="delete-task-btn text-gray-400 hover:text-red-500 transition-colors ml-2" data-id="${task.id}"> | |||
<i class="fa fa-trash"></i> | |||
</button> | |||
`; | |||
// 添加事件监听 | |||
taskElement.querySelector(`#task-${task.id}`).addEventListener('change', function() { | |||
toggleTask(task.id); | |||
}); | |||
taskElement.querySelector('.delete-task-btn').addEventListener('click', function() { | |||
deleteTask(task.id); | |||
}); | |||
taskList.appendChild(taskElement); | |||
}); | |||
} | |||
// 切换任务完成状态 | |||
function toggleTask(id) { | |||
fetch(`/Schedule/tasks/${id}/toggle/`, { | |||
method: 'POST', | |||
headers: { | |||
'Content-Type': 'application/json', | |||
'X-CSRFToken': getCookie('csrftoken') | |||
}, | |||
credentials: 'include' | |||
}) | |||
.then(response => { | |||
if (!response.ok) { | |||
throw new Error('更新任务状态失败: ' + response.status); | |||
} | |||
return response.json(); | |||
}) | |||
.then(data => { | |||
// 更新本地任务状态 | |||
const taskIndex = window.tasks.findIndex(task => task.id === id); | |||
if (taskIndex !== -1) { | |||
window.tasks[taskIndex].completed = data.completed; | |||
renderTaskList(); | |||
} | |||
}) | |||
.catch(error => { | |||
console.error('Error toggling task:', error); | |||
alert('更新任务状态失败: ' + error.message); | |||
}); | |||
} | |||
// 删除任务 | |||
function deleteTask(id) { | |||
if (confirm('确定要删除这个任务吗?')) { | |||
fetch(`/Schedule/tasks/${id}/delete/`, { | |||
method: 'DELETE', | |||
headers: { | |||
'Content-Type': 'application/json', | |||
'X-CSRFToken': getCookie('csrftoken') | |||
}, | |||
credentials: 'include' | |||
}) | |||
.then(response => { | |||
if (!response.ok) { | |||
throw new Error('删除任务失败: ' + response.status); | |||
} | |||
// 刷新任务列表 | |||
fetchAndRenderTasks(); | |||
// 刷新事件列表 | |||
fetchMonthData(currentDate); | |||
showNotification('任务已删除'); | |||
}) | |||
.catch(error => { | |||
console.error('Error deleting task:', error); | |||
alert('删除任务失败: ' + error.message); | |||
}); | |||
} | |||
} | |||
</script> | |||
</body> | |||
</html> | |||
@ -0,0 +1,469 @@ | |||
<!doctypehtml> | |||
<html lang=zh-CN> | |||
<meta charset=UTF-8> | |||
<meta content=width= device-width,initial-scale=1.0 name=viewport> | |||
<title>聊天页面</title> | |||
<script src=https://cdn.tailwindcss.com/3.4.16></script> | |||
<script> | |||
tailwind.config = { | |||
darkMode: `class` | |||
} | |||
</script> | |||
<link href=https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css rel=stylesheet> | |||
<style> | |||
@import "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap";:root { | |||
--primary-purple: #6a1b9a; | |||
--secondary-purple: #9c27b0; | |||
--light-purple: #d1c4e9; | |||
--dark-purple: #4a148c; | |||
--background: #f3e5f5; | |||
--text-primary: #333; | |||
--text-secondary: #666; | |||
--card-bg: #fff; | |||
--card-border: #eee | |||
} | |||
.dark { | |||
--primary-purple: #9c27b0; | |||
--secondary-purple: #6a1b9a; | |||
--light-purple: #4a148c; | |||
--dark-purple: #6a1b9a; | |||
--background: #121212; | |||
--text-primary: #fff; | |||
--text-secondary: #ccc; | |||
--card-bg: #1e1e1e; | |||
--card-border: #2a2a2a | |||
} | |||
body { | |||
background-color: var(--background); | |||
color: var(--text-primary); | |||
font-family: Noto Sans SC,sans-serif; | |||
transition: all .3s | |||
} | |||
.chat-container { | |||
height: 100vh; | |||
display: flex | |||
} | |||
.contacts-sidebar { | |||
background-color: var(--primary-purple); | |||
color: #fff; | |||
width: 250px; | |||
transition: all .3s | |||
} | |||
.chat-main { | |||
flex: 1; | |||
overflow-y: auto | |||
} | |||
.profile-sidebar { | |||
background-color: var(--card-bg); | |||
border-left: 1px solid var(--card-border); | |||
width: 250px | |||
} | |||
.message { | |||
border-radius: 8px; | |||
margin-bottom: 15px; | |||
padding: 12px; | |||
transition: all .3s | |||
} | |||
.message.sent { | |||
background-color: var(--light-purple); | |||
align-self: flex-end | |||
} | |||
.message.received { | |||
background-color: #f5f5f5; | |||
align-self: flex-start | |||
} | |||
.message img,.message video { | |||
border-radius: 8px; | |||
max-width: 100%; | |||
height: auto | |||
} | |||
.emoji-picker { | |||
background-color: var(--card-bg); | |||
border-radius: 8px; | |||
box-shadow: 0 4px 6px #0000001a; | |||
max-height: 200px; | |||
padding: 10px; | |||
overflow-y: auto | |||
} | |||
.emoji-category { | |||
margin-bottom: 10px | |||
} | |||
.emoji-category-title { | |||
color: var(--text-secondary); | |||
margin-bottom: 5px; | |||
font-size: 14px | |||
} | |||
.emoji-grid { | |||
grid-template-columns: repeat(7,1fr); | |||
gap: 5px; | |||
display: grid | |||
} | |||
.emoji-item { | |||
cursor: pointer; | |||
font-size: 20px; | |||
transition: transform .2s | |||
} | |||
.emoji-item:hover { | |||
transform: scale(1.2) | |||
} | |||
.upload-button { | |||
cursor: pointer; | |||
background-color: var(--light-purple); | |||
color: var(--primary-purple); | |||
border-radius: 50%; | |||
justify-content: center; | |||
align-items: center; | |||
width: 36px; | |||
height: 36px; | |||
margin-right: 10px; | |||
font-size: 18px; | |||
transition: all .3s; | |||
display: flex | |||
} | |||
.upload-button:hover { | |||
background-color: var(--secondary-purple); | |||
color: #fff | |||
} | |||
.contact { | |||
border-radius: 8px; | |||
padding: 12px; | |||
transition: all .3s | |||
} | |||
.contact:hover { | |||
background-color: var(--secondary-purple) | |||
} | |||
.card { | |||
background-color: var(--card-bg); | |||
border: 1px solid var(--card-border); | |||
border-radius: 8px; | |||
margin-bottom: 15px; | |||
padding: 15px; | |||
transition: all .3s | |||
} | |||
.card:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 6px #0000001a | |||
} | |||
.btn { | |||
background-color: var(--primary-purple); | |||
color: #fff; | |||
border-radius: 4px; | |||
padding: 8px 16px; | |||
font-weight: 500; | |||
transition: all .3s | |||
} | |||
.btn:hover { | |||
background-color: var(--secondary-purple); | |||
transform: scale(1.05) | |||
} | |||
.fade-in { | |||
animation: .5s ease-in fadeIn | |||
} | |||
@keyframes fadeIn { | |||
0% { | |||
opacity: 0; | |||
transform: translateY(10px) | |||
} | |||
to { | |||
opacity: 1; | |||
transform: translateY(0) | |||
} | |||
} | |||
::-webkit-scrollbar { | |||
width: 8px | |||
} | |||
::-webkit-scrollbar-track { | |||
background: var(--background) | |||
} | |||
::-webkit-scrollbar-thumb { | |||
background: var(--secondary-purple); | |||
border-radius: 4px | |||
} | |||
::-webkit-scrollbar-thumb:hover { | |||
background: var(--primary-purple) | |||
} | |||
</style> | |||
<body class=min-h-screen> | |||
<header class="py-6 px-4 md:px-8 text-center"> | |||
<a class="text-3xl md:text-4xl font-bold mb-4 fade-in" href=主页面.html>WaveSign手语通</a> | |||
<p class="text-lg md:text-xl opacity-80 fade-in mt-4">连接双手与世界,构建无声的桥梁 | |||
</header> | |||
<div class="min-h-screen flex flex-col"> | |||
<nav class="bg-white dark:bg-gray-800 shadow-md sticky top-0 z-50"> | |||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |||
<div class="flex justify-center h-16"> | |||
<div class="flex items-center"> | |||
<div class="container mx-auto flex justify-center space-x-8"> | |||
<a class="nav-link text-lg font-medium" href=SLClassroom.html style=color:var(--text-color)>手语教室</a> | |||
<a class="nav-link text-lg font-medium" href=LifeServing.html style=color:var(--text-color)>生活服务</a> | |||
<a class="nav-link text-lg font-medium" href=Community.html style=color:var(--text-color)>手语社区</a> | |||
<a class="nav-link text-lg font-medium" href=聊天页面.html style=color:var(--text-color)>聊天</a> | |||
<a class="nav-link text-lg font-medium" href=myPage.html style=color:var(--text-color)>我的</a> | |||
<button class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none" id=theme-toggle> | |||
<i class="fas fa-moon dark:hidden"></i> | |||
<i class="fas fa-sun hidden dark:block text-yellow-300"></i> | |||
</button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</nav> | |||
<div> | |||
<div class="chat-container flex"> | |||
<div class="contacts-sidebar shadow-md"> | |||
<h2 class="text-xl font-bold p-4 border-b border-opacity-30 border-white">联系人</h2> | |||
<ul class="p-2 space-y-2"> | |||
<li class="contact flex items-center p-2 cursor-pointer" onclick=switchChat(1)> | |||
<img alt="张军的头像" class="w-10 h-10 rounded-full mr-3" src=images\OIP-C.jpg> | |||
<span>张军</span> | |||
<li class="contact flex items-center p-2 cursor-pointer" onclick=switchChat(2)> | |||
<img alt="王暖暖的头像" class="w-10 h-10 rounded-full mr-3" src=images\聊天2.jpg> | |||
<span>王暖暖</span> | |||
</ul> | |||
</div> | |||
<div class="chat-main p-4"> | |||
<div class="chat-header bg-opacity-20 bg-white p-3 rounded-lg mb-4"> | |||
<div class="flex items-center"> | |||
<img alt=聊天对象头像 class="w-12 h-12 rounded-full mr-3" src=images\OIP-C.jpg id=chatAvatar> | |||
<div> | |||
<h3 class="font-bold text-lg" id=chatName>张军</h3> | |||
<p class="text-sm opacity-75" id=chatStatus>在线 | |||
</div> | |||
</div> | |||
</div> | |||
<div class="messages p-3" id=messages> | |||
<div class="message received"> | |||
<p>你好!今天天气不错啊。 | |||
</div> | |||
<div class="message sent"> | |||
<p>是啊,准备出去走走。 | |||
</div> | |||
<div class="message received"> | |||
<p>有什么计划吗? | |||
</div> | |||
<div class="message sent"> | |||
<p>打算去公园散步,拍点照片。 | |||
</div> | |||
<div class="message received"> | |||
<p>好的,我也去公园看看。 | |||
</div> | |||
</div> | |||
<div class="input-area flex items-center mt-4"> | |||
<input class="flex-1 px-4 py-2 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-primary-purple" placeholder=输入消息... id=input type=text> | |||
<label class=upload-button for=imageUpload> | |||
<i class="fas fa-image"></i> | |||
</label> | |||
<input accept="image/*" id=imageUpload style=display:none type=file> | |||
<label class=upload-button for=videoUpload> | |||
<i class="fas fa-video"></i> | |||
</label> | |||
<input accept="video/*" id=videoUpload style=display:none type=file> | |||
<button class=btn onclick=sendMessage()> | |||
<i class="fas fa-paper-plane mr-1"></i> | |||
发送 | |||
</button> | |||
<button class="btn ml-2" onclick=showEmojis()> | |||
<i class="far fa-smile mr-1"></i> | |||
</button> | |||
</div> | |||
</div> | |||
<div class="profile-sidebar shadow-md"> | |||
<div class="flex justify-center mb-4"> | |||
<img alt=个人资料头像 class="w-20 h-20 rounded-full border-4 border-white" src=images\OIP-C.jpg id=profileAvatar> | |||
</div> | |||
<h3 class="text-center text-xl font-bold mb-2" id=profileName>张军</h3> | |||
<div class="card p-3 mb-4"> | |||
<p class="text-sm mb-1"> | |||
<i class="fas fa-venus-mars mr-2 text-primary-purple"></i> | |||
性别:<span id=profileGender>男</span> | |||
<p class="text-sm mb-1"> | |||
<i class="fas fa-map-marker-alt mr-2 text-primary-purple"></i> | |||
属地:<span id=profileLocation>北京</span> | |||
<p class="text-sm mb-1"> | |||
<i class="fas fa-quote-left mr-2 text-primary-purple"></i> | |||
个性签名:<span id=profileBio>Hello World!</span> | |||
</div> | |||
<div class="emoji-picker fixed bottom-16 right-16 z-50" id=emojiPicker> | |||
<div class=emoji-category> | |||
<div class=emoji-category-title>表情符号</div> | |||
<div class=emoji-grid> | |||
<span class=emoji-item>😊</span> | |||
<span class=emoji-item>😂</span> | |||
<span class=emoji-item>🤔</span> | |||
<span class=emoji-item>😢</span> | |||
<span class=emoji-item>😠</span> | |||
<span class=emoji-item>😲</span> | |||
<span class=emoji-item>😄</span> | |||
<span class=emoji-item>😊</span> | |||
<span class=emoji-item>😂</span> | |||
<span class=emoji-item>🤔</span> | |||
<span class=emoji-item>😢</span> | |||
<span class=emoji-item>😠</span> | |||
<span class=emoji-item>😲</span> | |||
<span class=emoji-item>😄</span> | |||
<span class=emoji-item>😊</span> | |||
<span class=emoji-item>😂</span> | |||
<span class=emoji-item>🤔</span> | |||
<span class=emoji-item>😢</span> | |||
<span class=emoji-item>😠</span> | |||
<span class=emoji-item>😲</span> | |||
<span class=emoji-item>😄</span> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<script> | |||
var contacts = [{ | |||
id: 1, | |||
name: `张军`, | |||
avatar: `images/OIP-C.jpg`, | |||
gender: `男`, | |||
location: `北京`, | |||
bio: `Hello World!` | |||
}, { | |||
id: 2, | |||
name: `王暖暖`, | |||
avatar: `images/聊天2.jpg`, | |||
gender: `女`, | |||
location: `上海`, | |||
bio: `Nice to meet you!` | |||
}]; | |||
var currentContactId = 1; | |||
var switchChat = (a => { | |||
currentContactId = a; | |||
document.getElementById(`messages`).innerHTML = ``; | |||
const b = contacts.find(c => c.id === a); | |||
document.getElementById(`chatAvatar`).src = b.avatar; | |||
document.getElementById(`chatName`).innerText = b.name; | |||
document.getElementById(`profileAvatar`).src = b.avatar; | |||
document.getElementById(`profileName`).innerText = b.name; | |||
document.getElementById(`profileGender`).innerText = ` ${b.gender}`; | |||
document.getElementById(`profileLocation`).innerText = ` ${b.location}`; | |||
document.getElementById(`profileBio`).innerText = ` ${b.bio}` | |||
} | |||
); | |||
var sendMessage = ( () => { | |||
const a = document.getElementById(`input`).value.trim(); | |||
if (a && currentContactId) { | |||
const b = document.createElement(`div`); | |||
b.className = `message sent`; | |||
b.innerHTML = a; | |||
document.getElementById(`messages`).appendChild(b); | |||
document.getElementById(`input`).value = ``; | |||
document.getElementById(`messages`).scrollTop = document.getElementById(`messages`).scrollHeight | |||
} | |||
} | |||
); | |||
var uploadImage = (a => { | |||
const b = a.target.files[0]; | |||
if (b) { | |||
const c = new FileReader(); | |||
c.onload = (a => { | |||
const d = a.target.result; | |||
sendImage(d) | |||
} | |||
); | |||
c.readAsDataURL(b) | |||
} | |||
} | |||
); | |||
var uploadVideo = (a => { | |||
const b = a.target.files[0]; | |||
if (b) { | |||
const c = new FileReader(); | |||
c.onload = (a => { | |||
const d = a.target.result; | |||
sendVideo(d) | |||
} | |||
); | |||
c.readAsDataURL(b) | |||
} | |||
} | |||
); | |||
var sendImage = (a => { | |||
if (currentContactId) { | |||
const b = document.createElement(`div`); | |||
b.className = `message sent`; | |||
const c = document.createElement(`img`); | |||
c.src = a; | |||
c.style.maxWidth = `200px`; | |||
c.style.borderRadius = `8px`; | |||
b.appendChild(c); | |||
document.getElementById(`messages`).appendChild(b); | |||
document.getElementById(`messages`).scrollTop = document.getElementById(`messages`).scrollHeight | |||
} | |||
} | |||
); | |||
var sendVideo = (a => { | |||
if (currentContactId) { | |||
const b = document.createElement(`div`); | |||
b.className = `message sent`; | |||
const c = document.createElement(`video`); | |||
c.src = a; | |||
c.controls = !0; | |||
c.style.maxWidth = `200px`; | |||
c.style.borderRadius = `8px`; | |||
b.appendChild(c); | |||
document.getElementById(`messages`).appendChild(b); | |||
document.getElementById(`messages`).scrollTop = document.getElementById(`messages`).scrollHeight | |||
} | |||
} | |||
); | |||
var showEmojis = ( () => { | |||
const a = document.getElementById(`emojiPicker`); | |||
a.style.display = a.style.display === `none` ? `block` : `none` | |||
} | |||
); | |||
document.querySelectorAll(`.emoji-item`).forEach(a => { | |||
a.addEventListener(`click`, () => { | |||
const b = a.textContent; | |||
document.getElementById(`input`).value += b; | |||
document.getElementById(`emojiPicker`).style.display = `none` | |||
} | |||
) | |||
} | |||
); | |||
document.addEventListener(`DOMContentLoaded`, ( () => { | |||
const a = document.querySelectorAll(`.message`); | |||
a.forEach( (a, b) => { | |||
setTimeout( () => { | |||
a.classList.add(`fade-in`) | |||
} | |||
, b * 100) | |||
} | |||
) | |||
} | |||
)) | |||
</script> |
@ -0,0 +1,442 @@ | |||
{% load static %} | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
<title>我的</title> | |||
<link rel="stylesheet" href="{% static 'css/myPage.css' %}"> | |||
<link rel="stylesheet" href="{% static 'css/login_button.css' %}"> | |||
<script src="{% static 'js/myPage.js' %}"></script> | |||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet"> | |||
<script src=https://cdn.tailwindcss.com></script> | |||
</head> | |||
<body style="background-image: url('{{ profile.page_background.url }}'); background-size: cover;"> | |||
<!-- 导航栏 --> | |||
<header class="py-6 px-4 md:px-8 flex justify-between items-center bg-white bg-opacity-70"> | |||
<div class="absolute left-1/2 transform -translate-x-1/2"> | |||
<a class="text-3xl md:text-4xl font-bold fade-in">WaveSign手语通</a> | |||
<p class="text-lg md:text-xl opacity-80 fade-in mt-2">连接双手与世界,构建无声的桥梁</p> | |||
</div> | |||
<div class="flex items-center space-x-4 ml-auto"> | |||
{% if user.is_authenticated %} | |||
<div class="user-menu-box-container"> | |||
<a href="{% url 'my_page' %}" class="user-menu-box-button"> | |||
{% if user.avatar %} | |||
<img src="{{ user.avatar.url }}" alt="头像" class="user-menu-box-avatar"> | |||
{% else %} | |||
<img src="{% static 'images/default_avatar.png' %}" alt="默认头像" class="user-menu-box-avatar"> | |||
{% endif %} | |||
{{ user.username }} | |||
</a> | |||
<div class="user-menu-box-dropdown"> | |||
<a href="{% url 'my_page' %}">进入个人主页</a> | |||
<a href="{% url 'logout' %}">退出登录</a> | |||
</div> | |||
</div> | |||
{% else %} | |||
<a href="{% url 'login' %}" class="user-menu-box-login"> | |||
<i class="fas fa-user"></i> 登录 | |||
</a> | |||
{% endif %} | |||
</div> | |||
</header> | |||
<nav class="sticky top-0 bg-white bg-opacity-90 backdrop-blur-sm py-4 px-4 md:px-8 z-50 fade-in" | |||
style="border-bottom: 1px solid var(--border-color);"> | |||
<div class="container mx-auto flex justify-center space-x-8"> | |||
<a class="nav-link text-lg font-medium" href="{% url 'home' %}" style="color:var(--text-color)">首页</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'sl_classroom' %}" | |||
style="color:var(--text-color)">手语教室</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'life_serving' %}" | |||
style="color:var(--text-color)">生活服务</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'community' %}" | |||
style="color:var(--text-color)">手语社区</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'schedule' %}" style="color:var(--text-color)">日程管理</a> | |||
<a class="nav-link text-lg font-medium" href="{% url 'my_page' %}" style="color:var(--text-color)">我的</a> | |||
</div> | |||
</nav> | |||
<form method="POST" enctype="multipart/form-data"> | |||
{% csrf_token %} | |||
<!-- 页面背景上传控件 --> | |||
<div class="bg-upload-container"> | |||
<label for="bgInput" class="bg-upload-label">更换背景</label> | |||
<input type="file" name="page_background" id="bgInput" class="bg-upload" accept="image/*" | |||
onchange="changePageBg(event)"> | |||
</div> | |||
<div class="profile-page"> | |||
<!-- 个人信息区域 --> | |||
<div class="profile-page"> | |||
<div class="profile-header"> | |||
<div class="profile-container"> | |||
<!-- 个人资料背景 --> | |||
<div class="profile-banner" id="profileBanner" | |||
style="background-image: url('{{ profile.personal_background.url }}');"> | |||
<input type="file" name="personal_background" accept="image/*" hidden id="uploadBanner" | |||
onchange="changeBanner(event)"> | |||
<button type="button" class="change-banner-btn" | |||
onclick="document.getElementById('uploadBanner').click()">更换个人资料背景</button> | |||
</div> | |||
<!-- 头像 --> | |||
<div class="profile-avatar-container"> | |||
<input type="file" name="avatar" accept="image/*" hidden id="uploadAvatar" | |||
onchange="changeAvatar(event)"> | |||
<img src="{% if user.avatar %}{{ user.avatar.url }}{% else %}{% static 'images/default_avatar.png' %}{% endif %}" | |||
class="profile-avatar" id="profileAvatar" | |||
onclick="document.getElementById('uploadAvatar').click()"> | |||
</div> | |||
<!-- 用户资料 --> | |||
<div class="profile-content"> | |||
<h2 class="profile-name">{{ user.username }}</h2> | |||
<p class="profile-bio">{{ profile.bio }}</p> | |||
<!-- 编辑弹窗 --> | |||
<div class="modal-overlay" id="modalOverlay"></div> | |||
<div class="profile-modal" id="profileModal"> | |||
<span class="close-modal" onclick="closeProfileEdit()">×</span> | |||
<h3 style="text-align:center;">编辑个人资料</h3> | |||
<label>用户名:</label> | |||
<input type="text" name="username" value="{{ user.username }}"> | |||
<label>个性签名:</label> | |||
<textarea name="bio">{{ profile.bio }}</textarea> | |||
<button type="submit" class="update-profile-btn">更新资料</button> | |||
</div> | |||
<div class="profile-update-notice" id="profileUpdateNotice">个人资料已更新!</div> | |||
<button class="edit-profile-btn" type="button" onclick="openProfileEdit()">编辑资料</button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</form> | |||
<!-- 分栏布局 --> | |||
<div class="main-content"> | |||
<!-- 左栏:关注列表 --> | |||
<div class="left-column"> | |||
<h3>我关注的人</h3> | |||
<hr class="divider"> | |||
<div class="friend-list-container"> | |||
{% for follow in follows %} | |||
<div class="friend-item"> | |||
<img class="w-10 h-10 rounded-full object-cover" | |||
src="{{ follow.followed.avatar.url|default:'/static/images/default_avatar.png' }}" alt="头像" | |||
class="user-avatar" /> | |||
<span>{{ follow.followed.username }}</span> | |||
</div> | |||
{% empty %} | |||
<p class="text-gray-400">你还没有关注任何人</p> | |||
{% endfor %} | |||
</div> | |||
</div> | |||
<!-- 中栏:帖子内容 --> | |||
<div class="middle-column"> | |||
<div class="tab"> | |||
<button class="tablinks" onclick="openTab(event, 'myPosts')" id="defaultOpen"> | |||
<i class="fas fa-home"></i> 我的帖子 | |||
</button> | |||
<button class="tablinks" onclick="openTab(event, 'myFavorites')"> | |||
<i class="fas fa-heart"></i> 我的收藏 | |||
</button> | |||
<button class="tablinks" onclick="openTab(event, 'myLikes')"> | |||
<i class="fas fa-thumbs-up"></i> 我的赞 | |||
</button> | |||
</div> | |||
<!-- 我的帖子 --> | |||
<div id="myPosts" class="tabcontent"> | |||
{% for post in my_posts %} | |||
<div class="relative post-card shadow-md rounded-lg p-4 mb-6 bg-white dark:bg-gray-800"> | |||
<!-- 用户信息区 --> | |||
<div class="post-header flex items-center space-x-3 mb-2"> | |||
{% if post.user.avatar %} | |||
<img class="w-10 h-10 rounded-full object-cover" src="{{ post.user.avatar.url }}" alt="用户头像"> | |||
{% else %} | |||
<img class="w-10 h-10 rounded-full object-cover" src="{% static 'images/default_avatar.png' %}" | |||
alt="默认头像"> | |||
{% endif %} | |||
<div> | |||
<h3 class="font-semibold text-gray-900 dark:text-gray-100">{{ post.user.username }}</h3> | |||
<p class="text-sm text-gray-500">{{ post.created_at|timesince }}前</p> | |||
</div> | |||
<div class="absolute top-4 right-4 z-10"> | |||
<button onclick="toggleOptionsMenuDelete(this)" | |||
class="text-gray-500 hover:text-gray-800 dark:hover:text-white"> | |||
<i class="fas fa-ellipsis-h"></i> | |||
</button> | |||
<div | |||
class="options-menu hidden absolute right-0 mt-2 w-32 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-lg"> | |||
<form method="post" action="{% url 'delete_post' post.id %}" | |||
onsubmit="return confirm('确定要删除这条帖子吗?');"> | |||
{% csrf_token %} | |||
<button type="submit" | |||
class="block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-gray-100 dark:hover:bg-gray-600"> | |||
删除帖子 | |||
</button> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 帖子内容 --> | |||
<div class="post-content text-gray-800 dark:text-gray-200 mb-2"> | |||
<p>{{ post.content }}</p> | |||
</div> | |||
<!-- 媒体展示 --> | |||
<div class="post-media space-y-2"> | |||
{% if post.image %} | |||
<img src="{{ post.image.url }}" alt="帖子图片" class="w-full rounded-lg"> | |||
{% endif %} | |||
{% if post.video %} | |||
<video controls class="w-1/2 h-auto rounded-lg"> | |||
<source src="{{ post.video.url }}" type="video/mp4"> | |||
</video> | |||
{% endif %} | |||
</div> | |||
<!-- 简洁信息展示区 --> | |||
<div class="post-actions flex space-x-4 text-sm text-gray-600 mt-4"> | |||
<div><i class="fa-regular fa-heart mr-1"></i>{{ post.like_count }}</div> | |||
<div><i class="fa-regular fa-comment-dots mr-1"></i>{{ post.comment_count }}</div> | |||
<div><i class="fa-regular fa-bookmark mr-1"></i>{{ post.favorite_count }}</div> | |||
</div> | |||
<!-- 评论展示(最多展示前几条) --> | |||
{% if post.comment_set.all %} | |||
<div class="post-comments mt-4 space-y-2 text-sm text-gray-700 dark:text-gray-300"> | |||
{% for comment in post.comment_set.all|slice:":3" %} | |||
<div><strong>{{ comment.user.username }}</strong>: {{ comment.content }}</div> | |||
{% endfor %} | |||
{% if post.comment_set.count > 3 %} | |||
<div class="text-gray-400 italic">……更多评论</div> | |||
{% endif %} | |||
</div> | |||
{% endif %} | |||
</div> | |||
{% empty %} | |||
<p class="text-gray-500 text-center mt-6">暂无内容。</p> | |||
{% endfor %} | |||
</div> | |||
<!-- 我的收藏 --> | |||
<div id="myFavorites" class="tabcontent" style="display: none;"> | |||
{% for post in favorited_posts %} | |||
<div class="post-card shadow-md rounded-lg p-4 mb-6 bg-white dark:bg-gray-800"> | |||
<!-- 用户信息区 --> | |||
<div class="post-header flex items-center space-x-3 mb-2"> | |||
{% if post.user.avatar %} | |||
<img class="w-10 h-10 rounded-full object-cover" src="{{ post.user.avatar.url }}" alt="用户头像"> | |||
{% else %} | |||
<img class="w-10 h-10 rounded-full object-cover" src="{% static 'images/default_avatar.png' %}" | |||
alt="默认头像"> | |||
{% endif %} | |||
<div> | |||
<h3 class="font-semibold text-gray-900 dark:text-gray-100">{{ post.user.username }}</h3> | |||
<p class="text-sm text-gray-500">{{ post.created_at|timesince }}前</p> | |||
</div> | |||
</div> | |||
<!-- 帖子内容 --> | |||
<div class="post-content text-gray-800 dark:text-gray-200 mb-2"> | |||
<p>{{ post.content }}</p> | |||
</div> | |||
<!-- 媒体展示 --> | |||
<div class="post-media space-y-2"> | |||
{% if post.image %} | |||
<img src="{{ post.image.url }}" alt="帖子图片" class="w-full rounded-lg"> | |||
{% endif %} | |||
{% if post.video %} | |||
<video controls class="w-1/2 h-auto rounded-lg"> | |||
<source src="{{ post.video.url }}" type="video/mp4"> | |||
</video> | |||
{% endif %} | |||
</div> | |||
<!-- 简洁信息展示区 --> | |||
<div class="post-actions flex space-x-4 text-sm text-gray-600 mt-4"> | |||
<div><i class="fa-regular fa-heart mr-1"></i>{{ post.like_count }}</div> | |||
<div><i class="fa-regular fa-comment-dots mr-1"></i>{{ post.comment_count }}</div> | |||
<div><i class="fa-regular fa-bookmark mr-1"></i>{{ post.favorite_count }}</div> | |||
</div> | |||
<!-- 评论展示(最多展示前几条) --> | |||
{% if post.comment_set.all %} | |||
<div class="post-comments mt-4 space-y-2 text-sm text-gray-700 dark:text-gray-300"> | |||
{% for comment in post.comment_set.all|slice:":3" %} | |||
<div><strong>{{ comment.user.username }}</strong>: {{ comment.content }}</div> | |||
{% endfor %} | |||
{% if post.comment_set.count > 3 %} | |||
<div class="text-gray-400 italic">……更多评论</div> | |||
{% endif %} | |||
</div> | |||
{% endif %} | |||
</div> | |||
{% empty %} | |||
<p class="text-gray-500 text-center mt-6">暂无内容。</p> | |||
{% endfor %} | |||
</div> | |||
<!-- 我的点赞 --> | |||
<div id="myLikes" class="tabcontent" style="display: none;"> | |||
{% for post in liked_posts %} | |||
<div class="post-card shadow-md rounded-lg p-4 mb-6 bg-white dark:bg-gray-800"> | |||
<!-- 用户信息区 --> | |||
<div class="post-header flex items-center space-x-3 mb-2"> | |||
{% if post.user.avatar %} | |||
<img class="w-10 h-10 rounded-full object-cover" src="{{ post.user.avatar.url }}" alt="用户头像"> | |||
{% else %} | |||
<img class="w-10 h-10 rounded-full object-cover" src="{% static 'images/default_avatar.png' %}" | |||
alt="默认头像"> | |||
{% endif %} | |||
<div> | |||
<h3 class="font-semibold text-gray-900 dark:text-gray-100">{{ post.user.username }}</h3> | |||
<p class="text-sm text-gray-500">{{ post.created_at|timesince }}前</p> | |||
</div> | |||
</div> | |||
<!-- 帖子内容 --> | |||
<div class="post-content text-gray-800 dark:text-gray-200 mb-2"> | |||
<p>{{ post.content }}</p> | |||
</div> | |||
<!-- 媒体展示 --> | |||
<div class="post-media space-y-2"> | |||
{% if post.image %} | |||
<img src="{{ post.image.url }}" alt="帖子图片" class="w-full rounded-lg"> | |||
{% endif %} | |||
{% if post.video %} | |||
<video controls class="w-1/2 h-auto rounded-lg"> | |||
<source src="{{ post.video.url }}" type="video/mp4"> | |||
</video> | |||
{% endif %} | |||
</div> | |||
<!-- 简洁信息展示区 --> | |||
<div class="post-actions flex space-x-4 text-sm text-gray-600 mt-4"> | |||
<div><i class="fa-regular fa-heart mr-1"></i>{{ post.like_count }}</div> | |||
<div><i class="fa-regular fa-comment-dots mr-1"></i>{{ post.comment_count }}</div> | |||
<div><i class="fa-regular fa-bookmark mr-1"></i>{{ post.favorite_count }}</div> | |||
</div> | |||
<!-- 评论展示(最多展示前几条) --> | |||
{% if post.comment_set.all %} | |||
<div class="post-comments mt-4 space-y-2 text-sm text-gray-700 dark:text-gray-300"> | |||
{% for comment in post.comment_set.all|slice:":3" %} | |||
<div><strong>{{ comment.user.username }}</strong>: {{ comment.content }}</div> | |||
{% endfor %} | |||
{% if post.comment_set.count > 3 %} | |||
<div class="text-gray-400 italic">……更多评论</div> | |||
{% endif %} | |||
</div> | |||
{% endif %} | |||
</div> | |||
{% empty %} | |||
<p class="text-gray-500 text-center mt-6">暂无内容。</p> | |||
{% endfor %} | |||
</div> | |||
</div> | |||
<!-- 右栏:互动通知区 --> | |||
<div class="right-column"> | |||
<!-- 📅 即将到来的日程 --> | |||
<div class="notification-card mt-4"> | |||
<h4>📅 即将到来的日程</h4> | |||
<ul> | |||
{% for event in upcoming_events %} | |||
<li class="text-sm mb-2"> | |||
<i class="fas fa-calendar-alt text-purple-500 mr-1"></i> | |||
<strong>{{ event.title }}</strong><br> | |||
<span class="text-gray-500 text-xs">{{ event.datetime|date:"Y年m月d日 H:i" }}</span> | |||
</li> | |||
{% endfor %} | |||
{% if not upcoming_events %} | |||
<li class="text-gray-500">暂无即将到来的日程</li> | |||
{% endif %} | |||
</ul> | |||
<div class="text-right mt-2"> | |||
<a href="{% url 'schedule' %}" class="text-sm text-primary hover:underline">查看全部日程 →</a> | |||
</div> | |||
</div> | |||
<!-- ✅ 我的待办事项 --> | |||
<div class="notification-card mt-4"> | |||
<h4>✅ 我的待办事项</h4> | |||
<ul> | |||
{% for task in recent_tasks %} | |||
<li class="text-sm mb-2"> | |||
<i class="fas fa-check-circle text-green-500 mr-1"></i> | |||
{{ task.text }} | |||
<span class="text-gray-400 text-xs block">添加于 {{ task.created_at|date:"m月d日 H:i" }}</span> | |||
</li> | |||
{% endfor %} | |||
{% if not recent_tasks %} | |||
<li class="text-gray-500">暂无未完成的任务</li> | |||
{% endif %} | |||
</ul> | |||
<div class="text-right mt-2"> | |||
<a href="{% url 'schedule' %}" class="text-sm text-primary hover:underline">前往日程管理 →</a> | |||
</div> | |||
</div> | |||
<div class="notification-card notifications"> | |||
<h4>🔔 互动通知</h4> | |||
<ul> | |||
{% for like in recent_likes %} | |||
<li> | |||
<i class="fas fa-heart text-red-500"></i> | |||
<strong>{{ like.user.username }}</strong> 点赞了你的帖子: | |||
<span>{{ like.post.content|truncatechars:12 }}</span> | |||
</li> | |||
{% endfor %} | |||
{% for comment in recent_comments %} | |||
<li> | |||
<i class="fas fa-comment-dots text-blue-500"></i> | |||
<strong>{{ comment.user.username }}</strong> 评论了你的帖子: | |||
<span>{{ comment.content|truncatechars:12 }}</span> | |||
</li> | |||
{% endfor %} | |||
{% if not recent_likes and not recent_comments %} | |||
<li class="text-gray-500">暂无互动</li> | |||
{% endif %} | |||
</ul> | |||
</div> | |||
</div> | |||
</div> | |||
<footer class="py-8 px-4 md:px-8 text-center fade-in" style=animation-delay:1.2s> | |||
<div class="flex flex-col items-center"> | |||
<div class="mb-4 text-lg font-medium">作者: backpack</div> | |||
<p class="text-sm opacity-70">© 2025 WaveSign. | |||
</div> | |||
</footer> | |||
</body> | |||
</html> |