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 });
}
export async function fetchCalendar(server, token) {
return request(server, '/api/calendar', { token });
export async function fetchCalendar(server, token, year, month) {
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', {
method: 'POST',
token,
body: { markedDates }
body: { markedDates, deletedDates }
});
}

View File

@@ -26,6 +26,8 @@ export default class CalendarView {
this.prevBtn.addEventListener('click', () => {
state.currentDate.setMonth(state.currentDate.getMonth() - 1);
this.renderCalendar();
// Load data for the new month
this.loadData();
});
}
@@ -33,6 +35,8 @@ export default class CalendarView {
this.nextBtn.addEventListener('click', () => {
state.currentDate.setMonth(state.currentDate.getMonth() + 1);
this.renderCalendar();
// Load data for the new month
this.loadData();
});
}
@@ -40,6 +44,8 @@ export default class CalendarView {
this.todayBtn.addEventListener('click', () => {
state.currentDate = new Date();
this.renderCalendar();
// Load data for the current month
this.loadData();
});
}
}
@@ -51,21 +57,12 @@ export default class CalendarView {
try {
showToast('正在加载...', 'info');
const result = await fetchCalendar(state.currentServer, state.token);
// Convert the new data structure to the old one for backward compatibility
const markedDates = {};
for (const [date, data] of Object.entries(result.markedDates || {})) {
if (typeof data === 'string') {
// Old format - just status
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 || {};
// Pass current year and month to only fetch data for the displayed month
const year = state.currentDate.getFullYear();
const month = state.currentDate.getMonth() + 1; // getMonth() returns 0-11, need 1-12
const result = await fetchCalendar(state.currentServer, state.token, year, month);
// Merge with existing data
Object.assign(state.markedDates, result.markedDates || {});
this.renderCalendar();
showToast('数据加载成功', 'success');
} catch (error) {
@@ -82,7 +79,7 @@ export default class CalendarView {
clearData() {
state.markedDates = {};
state.markedDatesWithTimestamps = {};
state.deletedDates = [];
this.renderCalendar();
}
@@ -307,7 +304,8 @@ export default class CalendarView {
}
const dateKey = this.formatDate(date);
const currentStatus = state.markedDates[dateKey] || 'none';
const dateData = state.markedDates[dateKey];
const currentStatus = (dateData && dateData.status) || 'none';
let nextStatus;
switch (currentStatus) {
@@ -325,11 +323,21 @@ export default class CalendarView {
if (nextStatus === 'none') {
delete state.markedDates[dateKey];
// Track this deletion
if (!state.deletedDates.includes(dateKey)) {
state.deletedDates.push(dateKey);
}
// Clear pending tooltip for removed marks
this.pendingTooltipDate = null;
this.hideTooltip();
} 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
this.pendingTooltipDate = dateKey;
}
@@ -353,7 +361,17 @@ export default class CalendarView {
async persistMarkedDates() {
state.saveTimeoutId = null;
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');
// Reload data to get updated timestamps
await this.loadData();
@@ -384,12 +402,16 @@ export default class CalendarView {
getDateStatus(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) {
const dateKey = this.formatDate(date);
const dateData = state.markedDatesWithTimestamps[dateKey];
const dateData = state.markedDates[dateKey];
if (dateData && typeof dateData === 'object' && dateData.updatedAt) {
return dateData.updatedAt;
}

View File

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

View File

@@ -5,31 +5,58 @@ const router = express.Router();
router.get('/', (req, res) => {
const userId = req.user.userId;
const { year, month } = req.query;
db.all(
'SELECT date, status, updated_at FROM calendar_marks WHERE user_id = ?',
[userId],
(err, rows) => {
if (err) {
return res.status(500).json({ success: false, error: '获取数据失败' });
}
// Build query based on whether year and month are provided
let query = 'SELECT date, status, updated_at FROM calendar_marks WHERE user_id = ?';
const params = [userId];
const markedDates = {};
rows.forEach((row) => {
markedDates[row.date] = {
status: row.status,
updatedAt: row.updated_at
};
});
// If year and month are provided, filter by that month plus adjacent months
if (year && month) {
const yearNum = parseInt(year, 10);
const monthNum = parseInt(month, 10);
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) => {
const userId = req.user.userId;
const { markedDates } = req.body || {};
const { markedDates, deletedDates } = req.body || {};
if (!markedDates || typeof markedDates !== 'object') {
return res.status(400).json({ success: false, error: '无效的数据格式' });
@@ -40,75 +67,67 @@ router.post('/', (req, res) => {
return new Date().toISOString();
};
const dbOperations = Object.entries(markedDates).map(([date, status]) => {
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);
}
const dbOperations = [];
// 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);
if (dates.length > 0) {
const placeholders = dates.map(() => '?').join(',');
// Update or insert marked dates
Object.entries(markedDates).forEach(([date, status]) => {
dbOperations.push(
new Promise((resolve, reject) => {
db.run(
`DELETE FROM calendar_marks WHERE user_id = ? AND date NOT IN (${placeholders})`,
[userId, ...dates],
(err) => {
if (err) reject(err);
else resolve();
// 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();
}
);
}
}
);
})
);
} else {
});
// Delete explicitly removed dates
if (deletedDates && Array.isArray(deletedDates) && deletedDates.length > 0) {
const placeholders = deletedDates.map(() => '?').join(',');
dbOperations.push(
new Promise((resolve, reject) => {
db.run(
'DELETE FROM calendar_marks WHERE user_id = ?',
[userId],
`DELETE FROM calendar_marks WHERE user_id = ? AND date IN (${placeholders})`,
[userId, ...deletedDates],
(err) => {
if (err) reject(err);
else resolve();