optimize: api resp size.
Some checks failed
Build and Push Docker Image / buildx (push) Has been cancelled
Some checks failed
Build and Push Docker Image / buildx (push) Has been cancelled
This commit is contained in:
@@ -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 }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user