feat: add date and time sorting controls
This commit is contained in:
+99
-2
@@ -4,6 +4,8 @@ const recordForm = document.querySelector("#recordForm");
|
|||||||
const recordQuery = document.querySelector("#recordQuery");
|
const recordQuery = document.querySelector("#recordQuery");
|
||||||
const recordMeta = document.querySelector("#recordMeta");
|
const recordMeta = document.querySelector("#recordMeta");
|
||||||
const recordRows = document.querySelector("#recordRows");
|
const recordRows = document.querySelector("#recordRows");
|
||||||
|
const dateSortBtn = document.querySelector("#dateSortBtn");
|
||||||
|
const timeSortBtn = document.querySelector("#timeSortBtn");
|
||||||
const inlineAccount = document.querySelector("#inlineAccount");
|
const inlineAccount = document.querySelector("#inlineAccount");
|
||||||
const correctionToolbar = document.querySelector("#correctionToolbar");
|
const correctionToolbar = document.querySelector("#correctionToolbar");
|
||||||
const correctionStatus = document.querySelector("#correctionStatus");
|
const correctionStatus = document.querySelector("#correctionStatus");
|
||||||
@@ -23,7 +25,9 @@ const cancelCorrectionBtn = document.querySelector("#cancelCorrectionBtn");
|
|||||||
|
|
||||||
const WEEKDAYS = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"];
|
const WEEKDAYS = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"];
|
||||||
const COPY_LINE_BREAK = "\r\n";
|
const COPY_LINE_BREAK = "\r\n";
|
||||||
|
const SORT_DIRECTIONS = { asc: 1, desc: -1 };
|
||||||
let currentRecords = [];
|
let currentRecords = [];
|
||||||
|
let recordSort = { date: "asc", time: "asc" };
|
||||||
let currentRecordOrder = [];
|
let currentRecordOrder = [];
|
||||||
let correctedRecords = new Map();
|
let correctedRecords = new Map();
|
||||||
let activeCorrectionKey = "";
|
let activeCorrectionKey = "";
|
||||||
@@ -76,10 +80,97 @@ function groupRecordsByTeacher(records) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDisplayRecord(row) {
|
||||||
|
return correctedRecords.get(row._recordKey) || row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDateForSort(value) {
|
||||||
|
const text = String(value || "");
|
||||||
|
const match = text.match(/^(\d{4})\.(\d{1,2})\.(\d{1,2})$/);
|
||||||
|
if (!match) return text;
|
||||||
|
return `${match[1]}.${match[2].padStart(2, "0")}.${match[3].padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareDatesForSort(a, b) {
|
||||||
|
return normalizeDateForSort(a).localeCompare(normalizeDateForSort(b), "zh-CN", { numeric: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStartMinutes(value) {
|
||||||
|
const match = String(value || "").match(/^(\d{1,2}):(\d{2})/);
|
||||||
|
if (!match) return null;
|
||||||
|
return Number(match[1]) * 60 + Number(match[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareStartTimesForSort(a, b) {
|
||||||
|
const minutesA = parseStartMinutes(a);
|
||||||
|
const minutesB = parseStartMinutes(b);
|
||||||
|
if (minutesA !== null && minutesB !== null) return minutesA - minutesB;
|
||||||
|
if (minutesA !== null) return -1;
|
||||||
|
if (minutesB !== null) return 1;
|
||||||
|
return String(a || "").localeCompare(String(b || ""), "zh-CN", { numeric: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareRecordsForDisplay(a, b) {
|
||||||
|
const displayA = getDisplayRecord(a);
|
||||||
|
const displayB = getDisplayRecord(b);
|
||||||
|
const dateCompare = compareDatesForSort(displayA.date, displayB.date);
|
||||||
|
if (dateCompare !== 0) return dateCompare * SORT_DIRECTIONS[recordSort.date];
|
||||||
|
|
||||||
|
const timeCompare = compareStartTimesForSort(displayA.time, displayB.time);
|
||||||
|
if (timeCompare !== 0) return timeCompare * SORT_DIRECTIONS[recordSort.time];
|
||||||
|
|
||||||
|
return (a._recordIndex || 0) - (b._recordIndex || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortRecordsForDisplay(records) {
|
||||||
|
return [...records].sort(compareRecordsForDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortDirectionLabel(direction) {
|
||||||
|
return direction === "asc" ? "升序排列" : "降序排列";
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortDirectionName(direction) {
|
||||||
|
return sortDirectionLabel(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextSortDirectionName(direction) {
|
||||||
|
return direction === "asc" ? "降序排列" : "升序排列";
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRecordSortButtons() {
|
||||||
|
dateSortBtn.textContent = `日期 ${sortDirectionLabel(recordSort.date)}`;
|
||||||
|
dateSortBtn.setAttribute(
|
||||||
|
"aria-label",
|
||||||
|
`日期排序:${sortDirectionName(recordSort.date)},点击切换为${nextSortDirectionName(recordSort.date)}`,
|
||||||
|
);
|
||||||
|
timeSortBtn.textContent = `时间 ${sortDirectionLabel(recordSort.time)}`;
|
||||||
|
timeSortBtn.setAttribute(
|
||||||
|
"aria-label",
|
||||||
|
`时间排序:${sortDirectionName(recordSort.time)},点击切换为${nextSortDirectionName(recordSort.time)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetRecordSort() {
|
||||||
|
recordSort = { date: "asc", time: "asc" };
|
||||||
|
updateRecordSortButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCurrentRecords() {
|
||||||
|
if (!currentRecords.length) return;
|
||||||
|
recordRows.innerHTML = renderGroupedRecords(currentRecords);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRecordSort(field) {
|
||||||
|
recordSort = { ...recordSort, [field]: recordSort[field] === "asc" ? "desc" : "asc" };
|
||||||
|
updateRecordSortButtons();
|
||||||
|
renderCurrentRecords();
|
||||||
|
}
|
||||||
|
|
||||||
function renderRecordRow(row) {
|
function renderRecordRow(row) {
|
||||||
const key = row._recordKey;
|
const key = row._recordKey;
|
||||||
const corrected = correctedRecords.get(key);
|
const corrected = correctedRecords.get(key);
|
||||||
const displayRow = corrected || row;
|
const displayRow = getDisplayRecord(row);
|
||||||
const correctedClass = corrected ? " corrected-row" : "";
|
const correctedClass = corrected ? " corrected-row" : "";
|
||||||
const actionLabel = corrected ? "编辑" : "纠错";
|
const actionLabel = corrected ? "编辑" : "纠错";
|
||||||
const badge = corrected ? '<span class="correction-badge">已修改</span>' : "";
|
const badge = corrected ? '<span class="correction-badge">已修改</span>' : "";
|
||||||
@@ -110,7 +201,7 @@ function renderGroupedRecords(records) {
|
|||||||
<span>${group.count} 条记录 · ${fmtHours(group.totalHours)} 小时</span>
|
<span>${group.count} 条记录 · ${fmtHours(group.totalHours)} 小时</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>${group.records
|
</tr>${sortRecordsForDisplay(group.records)
|
||||||
.map((row) => {
|
.map((row) => {
|
||||||
currentRecordOrder.push(row._recordKey);
|
currentRecordOrder.push(row._recordKey);
|
||||||
return renderRecordRow(row);
|
return renderRecordRow(row);
|
||||||
@@ -375,6 +466,7 @@ async function queryRecords(query) {
|
|||||||
recordMeta.innerHTML = "";
|
recordMeta.innerHTML = "";
|
||||||
currentRecords = [];
|
currentRecords = [];
|
||||||
resetCorrections();
|
resetCorrections();
|
||||||
|
resetRecordSort();
|
||||||
clearInlineAccount();
|
clearInlineAccount();
|
||||||
try {
|
try {
|
||||||
const data = await fetchJson(`/api/records?q=${encodeURIComponent(q)}&limit=500`);
|
const data = await fetchJson(`/api/records?q=${encodeURIComponent(q)}&limit=500`);
|
||||||
@@ -397,6 +489,7 @@ async function queryRecords(query) {
|
|||||||
|
|
||||||
currentRecords = data.records.map((row, index) => ({
|
currentRecords = data.records.map((row, index) => ({
|
||||||
...row,
|
...row,
|
||||||
|
_recordIndex: index,
|
||||||
_recordKey: makeRecordKey(row, index),
|
_recordKey: makeRecordKey(row, index),
|
||||||
}));
|
}));
|
||||||
recordRows.innerHTML = renderGroupedRecords(currentRecords);
|
recordRows.innerHTML = renderGroupedRecords(currentRecords);
|
||||||
@@ -415,6 +508,9 @@ refreshBtn.addEventListener("click", () => {
|
|||||||
if (recordQuery.value.trim()) queryRecords(recordQuery.value);
|
if (recordQuery.value.trim()) queryRecords(recordQuery.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dateSortBtn.addEventListener("click", () => toggleRecordSort("date"));
|
||||||
|
timeSortBtn.addEventListener("click", () => toggleRecordSort("time"));
|
||||||
|
|
||||||
document.querySelectorAll("[data-query]").forEach((button) => {
|
document.querySelectorAll("[data-query]").forEach((button) => {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
recordQuery.value = button.dataset.query;
|
recordQuery.value = button.dataset.query;
|
||||||
@@ -455,4 +551,5 @@ document.addEventListener("keydown", (event) => {
|
|||||||
|
|
||||||
copyCorrectedBtn.addEventListener("click", copyCorrectedRecords);
|
copyCorrectedBtn.addEventListener("click", copyCorrectedRecords);
|
||||||
|
|
||||||
|
updateRecordSortButtons();
|
||||||
loadHealth();
|
loadHealth();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>新时空课程记录查询</title>
|
<title>新时空课程记录查询</title>
|
||||||
<link rel="stylesheet" href="/static/styles.css?v=20260611-weekend-shortcuts" />
|
<link rel="stylesheet" href="/static/styles.css?v=20260613-date-time-sort" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
@@ -50,8 +50,12 @@
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>日期</th>
|
<th>
|
||||||
<th>时间</th>
|
<button id="dateSortBtn" class="sort-header-button" type="button" aria-label="日期排序:升序排列,点击切换为降序排列">日期 升序排列</button>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<button id="timeSortBtn" class="sort-header-button" type="button" aria-label="时间排序:升序排列,点击切换为降序排列">时间 升序排列</button>
|
||||||
|
</th>
|
||||||
<th>学生</th>
|
<th>学生</th>
|
||||||
<th>老师</th>
|
<th>老师</th>
|
||||||
<th>科目</th>
|
<th>科目</th>
|
||||||
@@ -112,6 +116,6 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/app.js?v=20260612-relative-shortcuts"></script>
|
<script src="/static/app.js?v=20260613-sort-labels"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -358,6 +358,32 @@ th {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sort-header-button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
min-width: 64px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-header-button:hover,
|
||||||
|
.sort-header-button:focus-visible {
|
||||||
|
color: var(--accent-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-header-button:focus-visible {
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: 2px solid rgba(15, 118, 110, 0.35);
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user