cursor done.
This commit is contained in:
244
public/js/calendar.js
Normal file
244
public/js/calendar.js
Normal file
@@ -0,0 +1,244 @@
|
||||
import state from './state.js';
|
||||
import { fetchCalendar, saveCalendar, ApiError } from './api.js';
|
||||
import { showToast } from './toast.js';
|
||||
|
||||
export default class CalendarView {
|
||||
constructor({ onUnauthorized } = {}) {
|
||||
this.calendarEl = document.getElementById('calendar');
|
||||
this.monthDisplayEl = document.getElementById('currentMonth');
|
||||
this.prevBtn = document.getElementById('prevMonth');
|
||||
this.nextBtn = document.getElementById('nextMonth');
|
||||
this.todayBtn = document.getElementById('todayBtn');
|
||||
this.weekdays = ['日', '一', '二', '三', '四', '五', '六'];
|
||||
this.monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
|
||||
this.onUnauthorized = onUnauthorized;
|
||||
this.saveDelay = 500;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindNavigation();
|
||||
this.renderCalendar();
|
||||
}
|
||||
|
||||
bindNavigation() {
|
||||
if (this.prevBtn) {
|
||||
this.prevBtn.addEventListener('click', () => {
|
||||
state.currentDate.setMonth(state.currentDate.getMonth() - 1);
|
||||
this.renderCalendar();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.nextBtn) {
|
||||
this.nextBtn.addEventListener('click', () => {
|
||||
state.currentDate.setMonth(state.currentDate.getMonth() + 1);
|
||||
this.renderCalendar();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.todayBtn) {
|
||||
this.todayBtn.addEventListener('click', () => {
|
||||
state.currentDate = new Date();
|
||||
this.renderCalendar();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
if (!state.currentServer || !state.token) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
showToast('正在加载...', 'info');
|
||||
const result = await fetchCalendar(state.currentServer, state.token);
|
||||
state.markedDates = result.markedDates || {};
|
||||
this.renderCalendar();
|
||||
showToast('数据加载成功', 'success');
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && (error.status === 401 || error.status === 403)) {
|
||||
showToast('认证已过期,请重新登录', 'error');
|
||||
if (this.onUnauthorized) {
|
||||
this.onUnauthorized();
|
||||
}
|
||||
return;
|
||||
}
|
||||
showToast(error.message || '加载失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
clearData() {
|
||||
state.markedDates = {};
|
||||
this.renderCalendar();
|
||||
}
|
||||
|
||||
renderCalendar() {
|
||||
if (!this.calendarEl || !this.monthDisplayEl) return;
|
||||
|
||||
const currentDate = state.currentDate;
|
||||
const year = currentDate.getFullYear();
|
||||
const month = currentDate.getMonth();
|
||||
|
||||
this.calendarEl.innerHTML = '';
|
||||
|
||||
this.weekdays.forEach(day => {
|
||||
const weekdayEl = document.createElement('div');
|
||||
weekdayEl.className = 'weekday';
|
||||
weekdayEl.textContent = day;
|
||||
this.calendarEl.appendChild(weekdayEl);
|
||||
});
|
||||
|
||||
this.monthDisplayEl.textContent = `${year}年 ${this.monthNames[month]}`;
|
||||
|
||||
const firstDay = this.getFirstDayOfMonth(currentDate);
|
||||
const daysInMonth = this.getDaysInMonth(currentDate);
|
||||
|
||||
const prevMonthDate = new Date(year, month, 0);
|
||||
const daysInPrevMonth = prevMonthDate.getDate();
|
||||
|
||||
for (let i = firstDay - 1; i >= 0; i--) {
|
||||
const day = daysInPrevMonth - i;
|
||||
const date = new Date(year, month - 1, day);
|
||||
this.calendarEl.appendChild(this.createDayElement(date, day, true));
|
||||
}
|
||||
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(year, month, day);
|
||||
this.calendarEl.appendChild(this.createDayElement(date, day, false));
|
||||
}
|
||||
|
||||
const totalCells = this.calendarEl.children.length - this.weekdays.length;
|
||||
const remainingCells = 42 - totalCells;
|
||||
|
||||
for (let day = 1; day <= remainingCells; day++) {
|
||||
const date = new Date(year, month + 1, day);
|
||||
this.calendarEl.appendChild(this.createDayElement(date, day, true));
|
||||
}
|
||||
}
|
||||
|
||||
createDayElement(date, dayNumber, isOtherMonth) {
|
||||
const dayEl = document.createElement('div');
|
||||
dayEl.className = 'day';
|
||||
|
||||
if (isOtherMonth) {
|
||||
dayEl.classList.add('other-month');
|
||||
}
|
||||
|
||||
if (this.isToday(date)) {
|
||||
dayEl.classList.add('today');
|
||||
}
|
||||
|
||||
const status = this.getDateStatus(date);
|
||||
if (status !== 'none') {
|
||||
dayEl.classList.add(status);
|
||||
}
|
||||
|
||||
const dayNumberEl = document.createElement('div');
|
||||
dayNumberEl.className = 'day-number';
|
||||
dayNumberEl.textContent = dayNumber;
|
||||
dayEl.appendChild(dayNumberEl);
|
||||
|
||||
if (status !== 'none') {
|
||||
const markEl = document.createElement('div');
|
||||
markEl.className = 'day-mark';
|
||||
markEl.textContent = status === 'completed' ? '✓' : '○';
|
||||
dayEl.appendChild(markEl);
|
||||
}
|
||||
|
||||
dayEl.addEventListener('click', () => {
|
||||
if (!isOtherMonth) {
|
||||
this.toggleDateStatus(date);
|
||||
}
|
||||
});
|
||||
|
||||
return dayEl;
|
||||
}
|
||||
|
||||
toggleDateStatus(date) {
|
||||
if (!state.currentServer || !state.token) {
|
||||
showToast('请先选择服务器并登录', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const dateKey = this.formatDate(date);
|
||||
const currentStatus = state.markedDates[dateKey] || 'none';
|
||||
let nextStatus;
|
||||
|
||||
switch (currentStatus) {
|
||||
case 'none':
|
||||
nextStatus = 'completed';
|
||||
break;
|
||||
case 'completed':
|
||||
nextStatus = 'partial';
|
||||
break;
|
||||
case 'partial':
|
||||
default:
|
||||
nextStatus = 'none';
|
||||
break;
|
||||
}
|
||||
|
||||
if (nextStatus === 'none') {
|
||||
delete state.markedDates[dateKey];
|
||||
} else {
|
||||
state.markedDates[dateKey] = nextStatus;
|
||||
}
|
||||
|
||||
this.renderCalendar();
|
||||
this.scheduleSave();
|
||||
}
|
||||
|
||||
scheduleSave() {
|
||||
if (!state.currentServer || !state.token) return;
|
||||
|
||||
if (state.saveTimeoutId) {
|
||||
clearTimeout(state.saveTimeoutId);
|
||||
}
|
||||
|
||||
state.saveTimeoutId = setTimeout(() => {
|
||||
this.persistMarkedDates();
|
||||
}, this.saveDelay);
|
||||
}
|
||||
|
||||
async persistMarkedDates() {
|
||||
state.saveTimeoutId = null;
|
||||
try {
|
||||
await saveCalendar(state.currentServer, state.token, state.markedDates);
|
||||
showToast('数据已保存', 'success');
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && (error.status === 401 || error.status === 403)) {
|
||||
showToast('认证已过期,请重新登录', 'error');
|
||||
if (this.onUnauthorized) {
|
||||
this.onUnauthorized();
|
||||
}
|
||||
return;
|
||||
}
|
||||
showToast(error.message || '保存失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
getFirstDayOfMonth(date) {
|
||||
return new Date(date.getFullYear(), date.getMonth(), 1).getDay();
|
||||
}
|
||||
|
||||
getDaysInMonth(date) {
|
||||
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
|
||||
}
|
||||
|
||||
getDateStatus(date) {
|
||||
const dateKey = this.formatDate(date);
|
||||
return state.markedDates[dateKey] || 'none';
|
||||
}
|
||||
|
||||
isToday(date) {
|
||||
const today = new Date();
|
||||
return date.getFullYear() === today.getFullYear() &&
|
||||
date.getMonth() === today.getMonth() &&
|
||||
date.getDate() === today.getDate();
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user