optimize: api resp size.
Some checks failed
Build and Push Docker Image / buildx (push) Has been cancelled

This commit is contained in:
2025-11-11 17:06:17 +08:00
parent 96042461f9
commit 68d13112ec
4 changed files with 158 additions and 109 deletions

View File

@@ -53,14 +53,22 @@ export async function fetchCurrentUser(server, token) {
return request(server, '/api/auth/me', { token }); return request(server, '/api/auth/me', { token });
} }
export async function fetchCalendar(server, token) { export async function fetchCalendar(server, token, year, month) {
return request(server, '/api/calendar', { token }); const params = new URLSearchParams();
if (year !== undefined && month !== undefined) {
params.append('year', year);
params.append('month', month);
}
const queryString = params.toString();
const path = queryString ? `/api/calendar?${queryString}` : '/api/calendar';
return request(server, path, { token });
} }
export async function saveCalendar(server, token, markedDates) {
export async function saveCalendar(server, token, markedDates, deletedDates) {
return request(server, '/api/calendar', { return request(server, '/api/calendar', {
method: 'POST', method: 'POST',
token, token,
body: { markedDates } body: { markedDates, deletedDates }
}); });
} }

View File

@@ -26,6 +26,8 @@ export default class CalendarView {
this.prevBtn.addEventListener('click', () => { this.prevBtn.addEventListener('click', () => {
state.currentDate.setMonth(state.currentDate.getMonth() - 1); state.currentDate.setMonth(state.currentDate.getMonth() - 1);
this.renderCalendar(); this.renderCalendar();
// Load data for the new month
this.loadData();
}); });
} }
@@ -33,6 +35,8 @@ export default class CalendarView {
this.nextBtn.addEventListener('click', () => { this.nextBtn.addEventListener('click', () => {
state.currentDate.setMonth(state.currentDate.getMonth() + 1); state.currentDate.setMonth(state.currentDate.getMonth() + 1);
this.renderCalendar(); this.renderCalendar();
// Load data for the new month
this.loadData();
}); });
} }
@@ -40,6 +44,8 @@ export default class CalendarView {
this.todayBtn.addEventListener('click', () => { this.todayBtn.addEventListener('click', () => {
state.currentDate = new Date(); state.currentDate = new Date();
this.renderCalendar(); this.renderCalendar();
// Load data for the current month
this.loadData();
}); });
} }
} }
@@ -51,21 +57,12 @@ export default class CalendarView {
try { try {
showToast('正在加载...', 'info'); showToast('正在加载...', 'info');
const result = await fetchCalendar(state.currentServer, state.token); // Pass current year and month to only fetch data for the displayed month
// Convert the new data structure to the old one for backward compatibility const year = state.currentDate.getFullYear();
const markedDates = {}; const month = state.currentDate.getMonth() + 1; // getMonth() returns 0-11, need 1-12
for (const [date, data] of Object.entries(result.markedDates || {})) { const result = await fetchCalendar(state.currentServer, state.token, year, month);
if (typeof data === 'string') { // Merge with existing data
// Old format - just status Object.assign(state.markedDates, result.markedDates || {});
markedDates[date] = data;
} else if (data && typeof data === 'object') {
// New format - object with status and updatedAt
markedDates[date] = data.status;
}
}
state.markedDates = markedDates;
// Store the full data including timestamps
state.markedDatesWithTimestamps = result.markedDates || {};
this.renderCalendar(); this.renderCalendar();
showToast('数据加载成功', 'success'); showToast('数据加载成功', 'success');
} catch (error) { } catch (error) {
@@ -82,7 +79,7 @@ export default class CalendarView {
clearData() { clearData() {
state.markedDates = {}; state.markedDates = {};
state.markedDatesWithTimestamps = {}; state.deletedDates = [];
this.renderCalendar(); this.renderCalendar();
} }
@@ -169,7 +166,7 @@ export default class CalendarView {
if (timestamp) { if (timestamp) {
// Store the ISO timestamp // Store the ISO timestamp
dayEl.dataset.modificationTime = timestamp; dayEl.dataset.modificationTime = timestamp;
// Add hover events for custom tooltip // Add hover events for custom tooltip
dayEl.addEventListener('mouseenter', (e) => { dayEl.addEventListener('mouseenter', (e) => {
const isoTime = e.currentTarget.dataset.modificationTime; const isoTime = e.currentTarget.dataset.modificationTime;
@@ -185,7 +182,7 @@ export default class CalendarView {
}); });
this.showTooltip(e, `修改时间: ${localTime}`); this.showTooltip(e, `修改时间: ${localTime}`);
}); });
dayEl.addEventListener('mouseleave', () => { dayEl.addEventListener('mouseleave', () => {
this.hideTooltip(); this.hideTooltip();
}); });
@@ -209,7 +206,7 @@ export default class CalendarView {
tip.parentNode.removeChild(tip); tip.parentNode.removeChild(tip);
} }
}); });
// Create new tooltip element with unique ID // Create new tooltip element with unique ID
const tooltip = document.createElement('div'); const tooltip = document.createElement('div');
const uniqueId = 'tooltip-' + Date.now(); const uniqueId = 'tooltip-' + Date.now();
@@ -217,19 +214,19 @@ export default class CalendarView {
tooltip.className = 'tooltip'; tooltip.className = 'tooltip';
tooltip.textContent = content; tooltip.textContent = content;
document.body.appendChild(tooltip); document.body.appendChild(tooltip);
// Position tooltip relative to the target element // Position tooltip relative to the target element
const rect = event.currentTarget.getBoundingClientRect(); const rect = event.currentTarget.getBoundingClientRect();
tooltip.style.left = (rect.left + rect.width / 2) + 'px'; tooltip.style.left = (rect.left + rect.width / 2) + 'px';
tooltip.style.top = (rect.top - 10) + 'px'; tooltip.style.top = (rect.top - 10) + 'px';
tooltip.style.transform = 'translate(-50%, -100%)'; tooltip.style.transform = 'translate(-50%, -100%)';
// Show tooltip with a slight delay to ensure position is set // Show tooltip with a slight delay to ensure position is set
requestAnimationFrame(() => { requestAnimationFrame(() => {
tooltip.classList.add('show'); tooltip.classList.add('show');
}); });
} }
hideTooltip() { hideTooltip() {
const tooltips = document.querySelectorAll('.tooltip'); const tooltips = document.querySelectorAll('.tooltip');
tooltips.forEach(tooltip => { tooltips.forEach(tooltip => {
@@ -307,7 +304,8 @@ export default class CalendarView {
} }
const dateKey = this.formatDate(date); const dateKey = this.formatDate(date);
const currentStatus = state.markedDates[dateKey] || 'none'; const dateData = state.markedDates[dateKey];
const currentStatus = (dateData && dateData.status) || 'none';
let nextStatus; let nextStatus;
switch (currentStatus) { switch (currentStatus) {
@@ -325,11 +323,21 @@ export default class CalendarView {
if (nextStatus === 'none') { if (nextStatus === 'none') {
delete state.markedDates[dateKey]; delete state.markedDates[dateKey];
// Track this deletion
if (!state.deletedDates.includes(dateKey)) {
state.deletedDates.push(dateKey);
}
// Clear pending tooltip for removed marks // Clear pending tooltip for removed marks
this.pendingTooltipDate = null; this.pendingTooltipDate = null;
this.hideTooltip(); this.hideTooltip();
} else { } else {
state.markedDates[dateKey] = nextStatus; // Store status, timestamp will be updated by server on save
state.markedDates[dateKey] = { status: nextStatus };
// Remove from deleted dates if it was there
const deleteIndex = state.deletedDates.indexOf(dateKey);
if (deleteIndex !== -1) {
state.deletedDates.splice(deleteIndex, 1);
}
// Store the date for tooltip after save completes // Store the date for tooltip after save completes
this.pendingTooltipDate = dateKey; this.pendingTooltipDate = dateKey;
} }
@@ -353,7 +361,17 @@ export default class CalendarView {
async persistMarkedDates() { async persistMarkedDates() {
state.saveTimeoutId = null; state.saveTimeoutId = null;
try { try {
await saveCalendar(state.currentServer, state.token, state.markedDates); // Convert state format { date: { status, updatedAt } } to API format { date: status }
const markedDatesForAPI = {};
for (const [date, data] of Object.entries(state.markedDates)) {
if (data && typeof data === 'object' && data.status) {
markedDatesForAPI[date] = data.status;
}
}
await saveCalendar(state.currentServer, state.token, markedDatesForAPI, state.deletedDates);
// Clear the deleted dates array after successful save
state.deletedDates = [];
showToast('数据已保存', 'success'); showToast('数据已保存', 'success');
// Reload data to get updated timestamps // Reload data to get updated timestamps
await this.loadData(); await this.loadData();
@@ -384,12 +402,16 @@ export default class CalendarView {
getDateStatus(date) { getDateStatus(date) {
const dateKey = this.formatDate(date); const dateKey = this.formatDate(date);
return state.markedDates[dateKey] || 'none'; const dateData = state.markedDates[dateKey];
if (dateData && typeof dateData === 'object') {
return dateData.status || 'none';
}
return 'none';
} }
getDateTimestamp(date) { getDateTimestamp(date) {
const dateKey = this.formatDate(date); const dateKey = this.formatDate(date);
const dateData = state.markedDatesWithTimestamps[dateKey]; const dateData = state.markedDates[dateKey];
if (dateData && typeof dateData === 'object' && dateData.updatedAt) { if (dateData && typeof dateData === 'object' && dateData.updatedAt) {
return dateData.updatedAt; return dateData.updatedAt;
} }

View File

@@ -1,7 +1,7 @@
const state = { const state = {
currentDate: new Date(), currentDate: new Date(),
markedDates: {}, markedDates: {}, // Now stores objects with { status, updatedAt }
markedDatesWithTimestamps: {}, deletedDates: [], // Track dates that have been explicitly deleted
currentServer: '', currentServer: '',
token: '', token: '',
user: null, user: null,

View File

@@ -5,31 +5,58 @@ const router = express.Router();
router.get('/', (req, res) => { router.get('/', (req, res) => {
const userId = req.user.userId; const userId = req.user.userId;
const { year, month } = req.query;
db.all( // Build query based on whether year and month are provided
'SELECT date, status, updated_at FROM calendar_marks WHERE user_id = ?', let query = 'SELECT date, status, updated_at FROM calendar_marks WHERE user_id = ?';
[userId], const params = [userId];
(err, rows) => {
if (err) {
return res.status(500).json({ success: false, error: '获取数据失败' });
}
const markedDates = {}; // If year and month are provided, filter by that month plus adjacent months
rows.forEach((row) => { if (year && month) {
markedDates[row.date] = { const yearNum = parseInt(year, 10);
status: row.status, const monthNum = parseInt(month, 10);
updatedAt: row.updated_at
};
});
res.json({ success: true, markedDates }); // Validate year and month
if (isNaN(yearNum) || isNaN(monthNum) || monthNum < 1 || monthNum > 12) {
return res.status(400).json({ success: false, error: '无效的年份或月份' });
} }
);
// Calculate the date range: previous month to next month
// This covers all dates that might be visible in the calendar view
const prevMonth = monthNum === 1 ? 12 : monthNum - 1;
const prevYear = monthNum === 1 ? yearNum - 1 : yearNum;
const nextMonth = monthNum === 12 ? 1 : monthNum + 1;
const nextYear = monthNum === 12 ? yearNum + 1 : yearNum;
const startDate = `${prevYear}-${String(prevMonth).padStart(2, '0')}-01`;
// Get the last day of next month
const lastDayOfNextMonth = new Date(nextYear, nextMonth, 0).getDate();
const endDate = `${nextYear}-${String(nextMonth).padStart(2, '0')}-${String(lastDayOfNextMonth).padStart(2, '0')}`;
query += ' AND date >= ? AND date <= ?';
params.push(startDate, endDate);
}
db.all(query, params, (err, rows) => {
if (err) {
return res.status(500).json({ success: false, error: '获取数据失败' });
}
const markedDates = {};
rows.forEach((row) => {
markedDates[row.date] = {
status: row.status,
updatedAt: row.updated_at
};
});
res.json({ success: true, markedDates });
});
}); });
router.post('/', (req, res) => { router.post('/', (req, res) => {
const userId = req.user.userId; const userId = req.user.userId;
const { markedDates } = req.body || {}; const { markedDates, deletedDates } = req.body || {};
if (!markedDates || typeof markedDates !== 'object') { if (!markedDates || typeof markedDates !== 'object') {
return res.status(400).json({ success: false, error: '无效的数据格式' }); return res.status(400).json({ success: false, error: '无效的数据格式' });
@@ -40,75 +67,67 @@ router.post('/', (req, res) => {
return new Date().toISOString(); return new Date().toISOString();
}; };
const dbOperations = Object.entries(markedDates).map(([date, status]) => { const dbOperations = [];
return new Promise((resolve, reject) => {
// First check if this date exists and if status has changed
db.get(
'SELECT status FROM calendar_marks WHERE user_id = ? AND date = ?',
[userId, date],
(err, row) => {
if (err) {
return reject(err);
}
// Only update timestamp if status changed or it's a new entry
const statusChanged = !row || row.status !== status;
if (statusChanged) {
// Status changed - update with new timestamp
const currentTimestamp = getLocalTimestamp();
db.run(
`INSERT INTO calendar_marks (user_id, date, status, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(user_id, date) DO UPDATE SET
status = excluded.status,
updated_at = excluded.updated_at`,
[userId, date, status, currentTimestamp],
(err) => {
if (err) reject(err);
else resolve();
}
);
} else {
// Status unchanged - keep existing timestamp
db.run(
`INSERT INTO calendar_marks (user_id, date, status, updated_at)
VALUES (?, ?, ?, (SELECT updated_at FROM calendar_marks WHERE user_id = ? AND date = ?))
ON CONFLICT(user_id, date) DO UPDATE SET
status = excluded.status`,
[userId, date, status, userId, date],
(err) => {
if (err) reject(err);
else resolve();
}
);
}
}
);
});
});
const dates = Object.keys(markedDates); // Update or insert marked dates
if (dates.length > 0) { Object.entries(markedDates).forEach(([date, status]) => {
const placeholders = dates.map(() => '?').join(',');
dbOperations.push( dbOperations.push(
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
db.run( // First check if this date exists and if status has changed
`DELETE FROM calendar_marks WHERE user_id = ? AND date NOT IN (${placeholders})`, db.get(
[userId, ...dates], 'SELECT status FROM calendar_marks WHERE user_id = ? AND date = ?',
(err) => { [userId, date],
if (err) reject(err); (err, row) => {
else resolve(); if (err) {
return reject(err);
}
// Only update timestamp if status changed or it's a new entry
const statusChanged = !row || row.status !== status;
if (statusChanged) {
// Status changed - update with new timestamp
const currentTimestamp = getLocalTimestamp();
db.run(
`INSERT INTO calendar_marks (user_id, date, status, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(user_id, date) DO UPDATE SET
status = excluded.status,
updated_at = excluded.updated_at`,
[userId, date, status, currentTimestamp],
(err) => {
if (err) reject(err);
else resolve();
}
);
} else {
// Status unchanged - keep existing timestamp
db.run(
`INSERT INTO calendar_marks (user_id, date, status, updated_at)
VALUES (?, ?, ?, (SELECT updated_at FROM calendar_marks WHERE user_id = ? AND date = ?))
ON CONFLICT(user_id, date) DO UPDATE SET
status = excluded.status`,
[userId, date, status, userId, date],
(err) => {
if (err) reject(err);
else resolve();
}
);
}
} }
); );
}) })
); );
} else { });
// Delete explicitly removed dates
if (deletedDates && Array.isArray(deletedDates) && deletedDates.length > 0) {
const placeholders = deletedDates.map(() => '?').join(',');
dbOperations.push( dbOperations.push(
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
db.run( db.run(
'DELETE FROM calendar_marks WHERE user_id = ?', `DELETE FROM calendar_marks WHERE user_id = ? AND date IN (${placeholders})`,
[userId], [userId, ...deletedDates],
(err) => { (err) => {
if (err) reject(err); if (err) reject(err);
else resolve(); else resolve();