Skip to content

Commit ebd584c

Browse files
committed
feat: Favorite swimmer and sidebar collapse
1 parent 8e69473 commit ebd584c

File tree

6 files changed

+324
-35
lines changed

6 files changed

+324
-35
lines changed

DjangoConfigs/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
2828

2929
# SECURITY WARNING: don't run with debug turned on in production!
30-
DEBUG = False
30+
DEBUG = True
3131

3232
ALLOWED_HOSTS = ["*"]
3333
CSRF_TRUSTED_ORIGINS = []

Swim4LoveV2/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def exist_student_id(student_id):
2323
class Volunteer(models.Model):
2424
student_id = models.CharField(max_length=10, help_text="Please enter your student ID.", primary_key=True)
2525
name = models.CharField(max_length=100, help_text="Please enter your name.", unique=True)
26+
favorites = models.ManyToManyField(Swimmer, related_name='favorited_by', blank=True)
2627

2728
def __str__(self):
2829
return self.name

Swim4LoveV2/static/css/styles.css

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
html {
22
background-size: cover;
33
background: #1a73e8 url("../image/ykps_swim_pool.webp") no-repeat fixed center center;
4+
height: 100%;
5+
overflow: hidden;
46
}
57

68
html, body {
79
min-height: 100%;
810
margin: 0;
911
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
12+
overflow-y: auto;
13+
overflow-x: hidden;
14+
position: relative;
15+
height: 100%;
1016
}
1117

1218
:root {
@@ -32,25 +38,47 @@ html, body {
3238
width: 95%;
3339
max-width: 1800px;
3440
margin: 20px auto;
35-
height: calc(100vh - 40px);
41+
min-height: calc(100vh - 40px);
3642
color: var(--text-color);
43+
transition: all 0.3s ease;
44+
}
45+
46+
/* 控制面板收缩时的容器样式 */
47+
.container.panel-collapsed {
48+
grid-template-columns: 0 1fr;
3749
}
3850

3951
.left-panel {
4052
display: flex;
4153
flex-direction: column;
4254
gap: 12px;
43-
height: 100%;
55+
height: auto;
56+
transition: all 0.3s ease;
57+
overflow: visible;
58+
padding-right: 5px;
59+
margin-bottom: 20px;
60+
}
61+
62+
.panel-collapsed .left-panel {
63+
margin-left: -330px;
64+
opacity: 0;
4465
}
4566

4667
.right-panel {
47-
background: rgba(15, 30, 60, 0.75); /* 稍深一点的背景色 */
68+
background: rgba(15, 30, 60, 0.75);
4869
border-radius: var(--border-radius);
4970
box-shadow: var(--box-shadow);
5071
backdrop-filter: blur(10px);
5172
-webkit-backdrop-filter: blur(10px);
5273
overflow-y: auto;
5374
padding: 20px;
75+
margin-bottom: 20px;
76+
}
77+
78+
.panel-collapsed .right-panel {
79+
max-width: 1200px;
80+
margin: 0 auto;
81+
width: 100%;
5482
}
5583

5684
.title {
@@ -484,7 +512,7 @@ td.house-winter {
484512
}
485513

486514
/* 按钮样式 */
487-
.edit-button, .delete-button {
515+
.edit-button, .delete-button, .favorite-button {
488516
background: rgba(255, 255, 255, 0.15);
489517
backdrop-filter: blur(10px);
490518
-webkit-backdrop-filter: blur(10px);
@@ -518,6 +546,21 @@ td.house-winter {
518546
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15);
519547
}
520548

549+
/* 添加收藏按钮的金色样式 */
550+
.favorite-button.favorited {
551+
color: gold; /* 只有星星图标变成金色 */
552+
}
553+
554+
.favorite-button.favorited:hover {
555+
color: gold; /* 悬停时保持金色 */
556+
transform: translateY(-3px);
557+
}
558+
559+
.favorite-button:hover {
560+
background-color: rgba(255, 255, 255, 0.25); /* 与其他按钮保持一致的悬停背景 */
561+
transform: translateY(-3px);
562+
}
563+
521564
form {
522565
display: inline-block;
523566
}
@@ -1832,4 +1875,62 @@ input[type="checkbox"]:checked + .leaderboard-status {
18321875

18331876
.minus-button:hover:not(:disabled) {
18341877
background: #f44336;
1878+
}
1879+
1880+
/* 面板切换按钮样式 */
1881+
.panel-toggle {
1882+
position: fixed;
1883+
left: 0;
1884+
bottom: 20px;
1885+
width: 30px;
1886+
height: 36px;
1887+
background: var(--main-color);
1888+
color: white;
1889+
border: none;
1890+
border-radius: 0 8px 8px 0;
1891+
box-shadow: 2px 3px 10px rgba(0, 0, 0, 0.2);
1892+
cursor: pointer;
1893+
display: flex;
1894+
align-items: center;
1895+
justify-content: center;
1896+
font-size: 16px;
1897+
z-index: 1000;
1898+
transition: all 0.3s ease;
1899+
}
1900+
1901+
.panel-toggle:hover {
1902+
background: var(--main-color-dark);
1903+
width: 35px;
1904+
}
1905+
1906+
.panel-toggle.collapsed {
1907+
left: 0;
1908+
}
1909+
1910+
.panel-toggle.collapsed i {
1911+
transform: rotate(180deg);
1912+
}
1913+
1914+
/* 适配切换按钮响应式布局 */
1915+
@media (max-width: 768px) {
1916+
.container {
1917+
grid-template-columns: 1fr;
1918+
}
1919+
1920+
.container.panel-collapsed {
1921+
grid-template-columns: 1fr;
1922+
}
1923+
1924+
.left-panel {
1925+
margin-bottom: 20px;
1926+
}
1927+
1928+
.panel-collapsed .left-panel {
1929+
display: none;
1930+
}
1931+
1932+
.panel-toggle {
1933+
bottom: 15px;
1934+
left: 0;
1935+
}
18351936
}

Swim4LoveV2/templates/index.html

Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{% load static %}
33

44
{% block content %}
5-
<div class="container">
5+
<div class="container" id="mainContainer">
66
<!-- 左侧面板 -->
77
<div class="left-panel">
88
<!-- 用户登录信息 -->
@@ -108,7 +108,7 @@ <h2 class="app-title">Swim4Love</h2>
108108
<div class="swimmers-section">
109109
<h2>Swimmers</h2>
110110
{% if have_perm %}
111-
<button class="button" onclick="location.href='{% url 'add_swimmer' %}'; return false;">
111+
<button class="button" id="addSwimmerBtn">
112112
<i class="fa-solid fa-plus"></i> Add Swimmer
113113
</button>
114114
{% endif %}
@@ -208,6 +208,14 @@ <h2>Swimmers</h2>
208208
<button type="submit" class="delete-button"><i class="fa-regular fa-trash-can"></i>
209209
</button>
210210
</form>
211+
{% if have_perm %}
212+
<form action="{% url 'toggle_favorite' swimmer.id %}" method="post" class="favorite-form" data-swimmer-id="{{ swimmer.id }}">
213+
{% csrf_token %}
214+
<button type="button" class="favorite-button {% if swimmer in current_volunteer.favorites.all %}favorited{% endif %}">
215+
<i class="fa-solid fa-star"></i>
216+
</button>
217+
</form>
218+
{% endif %}
211219
</div>
212220
{% endif %}
213221
</td>
@@ -227,7 +235,7 @@ <h2>Swimmers</h2>
227235
<h2>Volunteers</h2>
228236
<div class="volunteer-header">
229237
{% if have_perm %}
230-
<button class="button" onclick="location.href='{% url 'add_volunteer' %}'; return false;">
238+
<button class="button" id="addVolunteerBtn">
231239
<i class="fa-solid fa-plus"></i> Add Volunteer
232240
</button>
233241
{% endif %}
@@ -249,12 +257,114 @@ <h2>Volunteers</h2>
249257
</div>
250258
</div>
251259
</div>
260+
261+
<!-- 面板切换按钮 -->
262+
<button id="panelToggleBtn" class="panel-toggle" title="切换侧栏">
263+
<i class="fa-solid fa-chevron-left"></i>
264+
</button>
252265
</div>
253-
266+
254267
<script>
255268
document.addEventListener('DOMContentLoaded', function() {
256-
// 注意:此处事件绑定已移至real_time_leaderboard.js
257-
// 为避免重复绑定,保留此注释以便后续维护
269+
// 添加游泳者和志愿者按钮事件
270+
const addSwimmerBtn = document.getElementById('addSwimmerBtn');
271+
if (addSwimmerBtn) {
272+
addSwimmerBtn.addEventListener('click', function() {
273+
location.href = "{% url 'add_swimmer' %}";
274+
return false;
275+
});
276+
}
277+
278+
const addVolunteerBtn = document.getElementById('addVolunteerBtn');
279+
if (addVolunteerBtn) {
280+
addVolunteerBtn.addEventListener('click', function() {
281+
location.href = "{% url 'add_volunteer' %}";
282+
return false;
283+
});
284+
}
285+
286+
// Handle favorite button clicks
287+
document.querySelectorAll('.favorite-button').forEach(button => {
288+
button.addEventListener('click', function() {
289+
const form = this.closest('.favorite-form');
290+
const swimmerId = form.dataset.swimmerId;
291+
const currentStatus = this.classList.contains('favorited');
292+
293+
fetch(form.action, {
294+
method: 'POST',
295+
headers: {
296+
'X-CSRFToken': form.querySelector('[name=csrfmiddlewaretoken]').value,
297+
'X-Requested-With': 'XMLHttpRequest'
298+
},
299+
credentials: 'same-origin' // 确保发送和接收cookie
300+
})
301+
.then(response => {
302+
if (!response.ok) {
303+
throw new Error(`请求失败: ${response.status}`);
304+
}
305+
return response.json();
306+
})
307+
.then(data => {
308+
if (data.success) {
309+
const newStatus = data.is_favorite;
310+
311+
// 切换收藏状态样式(根据服务器返回的状态更新)
312+
if (newStatus) {
313+
this.classList.add('favorited');
314+
} else {
315+
this.classList.remove('favorited');
316+
}
317+
318+
// 重排表格行
319+
const tbody = document.querySelector('#swimmersTable tbody');
320+
const rows = Array.from(tbody.querySelectorAll('tr'));
321+
322+
rows.sort((a, b) => {
323+
const aFavorited = a.querySelector('.favorite-button')?.classList.contains('favorited') || false;
324+
const bFavorited = b.querySelector('.favorite-button')?.classList.contains('favorited') || false;
325+
if (aFavorited === bFavorited) {
326+
return a.querySelector('td').textContent.localeCompare(b.querySelector('td').textContent);
327+
}
328+
return bFavorited - aFavorited;
329+
});
330+
rows.forEach(row => tbody.appendChild(row));
331+
}
332+
})
333+
.catch(error => {
334+
console.error('请求失败:', error);
335+
});
336+
});
337+
});
338+
339+
// 面板收缩功能
340+
const toggleBtn = document.getElementById('panelToggleBtn');
341+
const container = document.getElementById('mainContainer');
342+
343+
// 检查本地存储中的状态
344+
const isPanelCollapsed = localStorage.getItem('panelCollapsed') === 'true';
345+
if (isPanelCollapsed) {
346+
container.classList.add('panel-collapsed');
347+
toggleBtn.classList.add('collapsed');
348+
toggleBtn.querySelector('i').classList.remove('fa-chevron-left');
349+
toggleBtn.querySelector('i').classList.add('fa-chevron-right');
350+
}
351+
352+
toggleBtn.addEventListener('click', function() {
353+
container.classList.toggle('panel-collapsed');
354+
this.classList.toggle('collapsed');
355+
356+
// 切换图标
357+
const icon = this.querySelector('i');
358+
if (container.classList.contains('panel-collapsed')) {
359+
icon.classList.remove('fa-chevron-left');
360+
icon.classList.add('fa-chevron-right');
361+
localStorage.setItem('panelCollapsed', 'true');
362+
} else {
363+
icon.classList.remove('fa-chevron-right');
364+
icon.classList.add('fa-chevron-left');
365+
localStorage.setItem('panelCollapsed', 'false');
366+
}
367+
});
258368
});
259369
</script>
260370
{% endblock %}

Swim4LoveV2/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
path("logout/", views.logout_view, name="logout"),
1414
path("increment_laps/<uuid:pk>/", views.increment_laps, name="increment_laps"),
1515
path("decrement_laps/<uuid:pk>/", views.decrement_laps, name="decrement_laps"),
16+
path("toggle_favorite/<uuid:pk>/", views.toggle_favorite, name="toggle_favorite"),
1617
]

0 commit comments

Comments
 (0)