统一汇总时间展示格式
This commit is contained in:
+39
-9
@@ -1233,6 +1233,16 @@ def duration_text_from_minutes(minutes: int) -> str:
|
||||
return f"{minutes // 60}小时{minutes % 60}分"
|
||||
|
||||
|
||||
def duration_text_from_hours(hours: float) -> str:
|
||||
total_minutes = int(round(float(hours or 0) * 60))
|
||||
sign = "-" if total_minutes < 0 else ""
|
||||
return f"{sign}{duration_text_from_minutes(abs(total_minutes))}"
|
||||
|
||||
|
||||
def duration_text_map(values: dict[str, float]) -> dict[str, str]:
|
||||
return {key: duration_text_from_hours(value) for key, value in values.items()}
|
||||
|
||||
|
||||
def duration_minutes_from_time_range(time_range: str) -> int | None:
|
||||
if not time_range:
|
||||
return None
|
||||
@@ -2718,12 +2728,20 @@ def summarize_records(records: list[ClassRecord]) -> dict:
|
||||
by_student[record.student] += record.duration_hours
|
||||
by_teacher[record.teacher] += record.duration_hours
|
||||
by_subject[record.subject] += record.duration_hours
|
||||
students = dict(sorted((key, round(value, 2)) for key, value in by_student.items()))
|
||||
teachers = dict(sorted((key, round(value, 2)) for key, value in by_teacher.items()))
|
||||
subjects = dict(sorted((key, round(value, 2)) for key, value in by_subject.items()))
|
||||
total_hours = round(sum(record.duration_hours for record in records), 2)
|
||||
return {
|
||||
"count": len(records),
|
||||
"total_hours": round(sum(record.duration_hours for record in records), 2),
|
||||
"students": dict(sorted((key, round(value, 2)) for key, value in by_student.items())),
|
||||
"teachers": dict(sorted((key, round(value, 2)) for key, value in by_teacher.items())),
|
||||
"subjects": dict(sorted((key, round(value, 2)) for key, value in by_subject.items())),
|
||||
"total_hours": total_hours,
|
||||
"total_duration": duration_text_from_hours(total_hours),
|
||||
"students": students,
|
||||
"students_duration": duration_text_map(students),
|
||||
"teachers": teachers,
|
||||
"teachers_duration": duration_text_map(teachers),
|
||||
"subjects": subjects,
|
||||
"subjects_duration": duration_text_map(subjects),
|
||||
}
|
||||
|
||||
|
||||
@@ -2736,12 +2754,20 @@ def summarize_public_records(records: list[ClassRecord], teachers: list[Teacher]
|
||||
by_student[record.student] += record.duration_hours
|
||||
by_teacher[display_names.get(record.teacher, record.teacher)] += record.duration_hours
|
||||
by_subject[record.subject] += record.duration_hours
|
||||
students = dict(sorted((key, round(value, 2)) for key, value in by_student.items()))
|
||||
teachers_summary = dict(sorted((key, round(value, 2)) for key, value in by_teacher.items()))
|
||||
subjects = dict(sorted((key, round(value, 2)) for key, value in by_subject.items()))
|
||||
total_hours = round(sum(record.duration_hours for record in records), 2)
|
||||
return {
|
||||
"count": len(records),
|
||||
"total_hours": round(sum(record.duration_hours for record in records), 2),
|
||||
"students": dict(sorted((key, round(value, 2)) for key, value in by_student.items())),
|
||||
"teachers": dict(sorted((key, round(value, 2)) for key, value in by_teacher.items())),
|
||||
"subjects": dict(sorted((key, round(value, 2)) for key, value in by_subject.items())),
|
||||
"total_hours": total_hours,
|
||||
"total_duration": duration_text_from_hours(total_hours),
|
||||
"students": students,
|
||||
"students_duration": duration_text_map(students),
|
||||
"teachers": teachers_summary,
|
||||
"teachers_duration": duration_text_map(teachers_summary),
|
||||
"subjects": subjects,
|
||||
"subjects_duration": duration_text_map(subjects),
|
||||
}
|
||||
|
||||
|
||||
@@ -2798,9 +2824,13 @@ def account_to_dict(account: Account) -> dict:
|
||||
return {
|
||||
"student_id": account.student_id,
|
||||
"student": account.student,
|
||||
"payments": [{"date": payment.date, "hours": payment.hours} for payment in account.payments],
|
||||
"payments": [
|
||||
{"date": payment.date, "hours": payment.hours, "duration": duration_text_from_hours(payment.hours)}
|
||||
for payment in account.payments
|
||||
],
|
||||
"payments_count": len(account.payments),
|
||||
"remaining": account.remaining,
|
||||
"remaining_duration": duration_text_from_hours(account.remaining),
|
||||
"account_status": account.account_status,
|
||||
"note": account.note,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>课时账户查询</title>
|
||||
<link rel="stylesheet" href="/static/styles.css?v=20260611-accounts-split" />
|
||||
<link rel="stylesheet" href="/static/styles.css?v=20260616-duration-text" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
@@ -55,6 +55,6 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/static/accounts.js?v=20260611-accounts-split"></script>
|
||||
<script src="/static/accounts.js?v=20260616-duration-text"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -13,6 +13,10 @@ function fmtHours(value) {
|
||||
return `${hours}小时${minutes}分`;
|
||||
}
|
||||
|
||||
function displayHours(value, fallback) {
|
||||
return fallback || fmtHours(value);
|
||||
}
|
||||
|
||||
function fmtTime(seconds) {
|
||||
if (!seconds) return "未知";
|
||||
return new Date(seconds * 1000).toLocaleString("zh-CN", { hour12: false });
|
||||
@@ -40,7 +44,7 @@ function statusClass(status) {
|
||||
|
||||
function renderPayments(payments) {
|
||||
if (!payments.length) return "暂无";
|
||||
return payments.map((item) => `${escapeHtml(item.date)}:${fmtHours(item.hours)}`).join("<br>");
|
||||
return payments.map((item) => `${escapeHtml(item.date)}:${escapeHtml(displayHours(item.hours, item.duration))}`).join("<br>");
|
||||
}
|
||||
|
||||
async function fetchJson(url) {
|
||||
@@ -89,7 +93,7 @@ async function loadAccounts() {
|
||||
(row) => `<tr>
|
||||
<td>${escapeHtml(row.student)}<br><small>${escapeHtml(row.student_id)}</small></td>
|
||||
<td><span class="status ${statusClass(row.account_status)}">${escapeHtml(row.account_status)}</span></td>
|
||||
<td class="num">${fmtHours(row.remaining)}</td>
|
||||
<td class="num">${escapeHtml(displayHours(row.remaining, row.remaining_duration))}</td>
|
||||
<td>${renderPayments(row.payments)}</td>
|
||||
<td class="note-cell">${escapeHtml(row.note || "")}</td>
|
||||
</tr>`,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>管理后台</title>
|
||||
<link rel="stylesheet" href="/static/styles.css?v=20260615-summary-time-review" />
|
||||
<link rel="stylesheet" href="/static/styles.css?v=20260616-duration-text" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
@@ -417,6 +417,6 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/static/admin.js?v=20260616-summary-single-input"></script>
|
||||
<script src="/static/admin.js?v=20260616-duration-text"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+17
-4
@@ -112,6 +112,10 @@ function fmtHours(value) {
|
||||
return `${hours}小时${minutes}分`;
|
||||
}
|
||||
|
||||
function displayHours(value, fallback) {
|
||||
return fallback || fmtHours(value);
|
||||
}
|
||||
|
||||
function fmtTime(seconds) {
|
||||
if (!seconds) return "未知";
|
||||
return new Date(seconds * 1000).toLocaleString("zh-CN", { hour12: false });
|
||||
@@ -146,7 +150,7 @@ function taskStatusClass(status) {
|
||||
|
||||
function renderPayments(payments) {
|
||||
if (!payments.length) return "暂无";
|
||||
return payments.map((item) => `${escapeHtml(item.date)}:${fmtHours(item.hours)}`).join("<br>");
|
||||
return payments.map((item) => `${escapeHtml(item.date)}:${escapeHtml(displayHours(item.hours, item.duration))}`).join("<br>");
|
||||
}
|
||||
|
||||
async function fetchJson(url, options = {}) {
|
||||
@@ -212,7 +216,7 @@ async function loadAccounts() {
|
||||
(row) => `<tr>
|
||||
<td>${escapeHtml(row.student)}<br><small>${escapeHtml(row.student_id)}</small></td>
|
||||
<td><span class="status ${statusClass(row.account_status)}">${escapeHtml(row.account_status)}</span></td>
|
||||
<td class="num">${fmtHours(row.remaining)}</td>
|
||||
<td class="num">${escapeHtml(displayHours(row.remaining, row.remaining_duration))}</td>
|
||||
<td>${renderPayments(row.payments)}</td>
|
||||
<td class="note-cell">${escapeHtml(row.note || "")}</td>
|
||||
<td><button class="small-button account-edit" type="button" data-student-id="${escapeHtml(row.student_id)}">编辑</button></td>
|
||||
@@ -228,7 +232,16 @@ async function loadAccounts() {
|
||||
}
|
||||
|
||||
function paymentsToText(payments) {
|
||||
return payments.map((item) => `${item.date}:${item.hours}`).join("\n");
|
||||
return payments.map((item) => `${item.date}:${displayHours(item.hours, item.duration)}`).join("\n");
|
||||
}
|
||||
|
||||
function parseHoursInput(value) {
|
||||
const text = String(value || "").trim();
|
||||
const match = text.match(/^(\d+)小时(?:(\d+)分)?$/);
|
||||
if (match) return Number(match[1]) + Number(match[2] || 0) / 60;
|
||||
const minuteMatch = text.match(/^(\d+)分$/);
|
||||
if (minuteMatch) return Number(minuteMatch[1]) / 60;
|
||||
return Number(text);
|
||||
}
|
||||
|
||||
function parsePaymentsInput(value) {
|
||||
@@ -241,7 +254,7 @@ function parsePaymentsInput(value) {
|
||||
if (!date || hoursText === undefined) {
|
||||
throw new Error(`缴费记录格式错误:${item}`);
|
||||
}
|
||||
const hours = Number(hoursText);
|
||||
const hours = parseHoursInput(hoursText);
|
||||
if (!Number.isFinite(hours)) {
|
||||
throw new Error(`缴费课时格式错误:${item}`);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,10 @@ function fmtHours(value) {
|
||||
return `${hours}小时${minutes}分`;
|
||||
}
|
||||
|
||||
function displayHours(value, fallback) {
|
||||
return fallback || fmtHours(value);
|
||||
}
|
||||
|
||||
function fmtTime(seconds) {
|
||||
if (!seconds) return "未知";
|
||||
return new Date(seconds * 1000).toLocaleString("zh-CN", { hour12: false });
|
||||
@@ -227,7 +231,7 @@ function renderGroupedRecords(records) {
|
||||
<td colspan="7">
|
||||
<div class="teacher-group-title">
|
||||
<strong>${escapeHtml(group.teacher)}</strong>
|
||||
<span>${group.count} 条记录 · ${fmtHours(group.totalHours)}</span>
|
||||
<span>${group.count} 条记录 · ${displayHours(group.totalHours)}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>${sortRecordsForDisplay(group.records)
|
||||
@@ -265,7 +269,7 @@ function statusClass(status) {
|
||||
function renderPayments(payments) {
|
||||
if (!payments.length) return "暂无缴费记录";
|
||||
return payments
|
||||
.map((item) => `<span class="payment-line">${escapeHtml(item.date)}:${fmtHours(item.hours)}</span>`)
|
||||
.map((item) => `<span class="payment-line">${escapeHtml(item.date)}:${escapeHtml(displayHours(item.hours, item.duration))}</span>`)
|
||||
.join("");
|
||||
}
|
||||
|
||||
@@ -279,7 +283,7 @@ function renderInlineAccount(account) {
|
||||
<span class="status ${statusClass(account.account_status)}">${escapeHtml(account.account_status)}</span>
|
||||
</div>
|
||||
<div class="inline-account-grid">
|
||||
${metric("剩余课时", fmtHours(account.remaining))}
|
||||
${metric("剩余课时", displayHours(account.remaining, account.remaining_duration))}
|
||||
${metric("缴费次数", account.payments_count)}
|
||||
${metricHtml("缴费记录", renderPayments(account.payments))}
|
||||
${metric("备注", account.note || "无")}
|
||||
@@ -593,7 +597,7 @@ async function queryRecords(query) {
|
||||
recordMeta.innerHTML = [
|
||||
metric("识别日期", data.query.date_range),
|
||||
metric("命中记录", `${summary.count} 条`),
|
||||
metric("总课时", fmtHours(summary.total_hours)),
|
||||
metric("总课时", displayHours(summary.total_hours, summary.total_duration)),
|
||||
metric("授课老师", `${Object.keys(summary.teachers || {}).length} 位`),
|
||||
].join("");
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>新时空教务管理系统</title>
|
||||
<link rel="stylesheet" href="/static/styles.css?v=20260615-record-summary-toggle" />
|
||||
<link rel="stylesheet" href="/static/styles.css?v=20260616-duration-text" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
@@ -140,6 +140,6 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="/static/app.js?v=20260615-record-summary-toggle"></script>
|
||||
<script src="/static/app.js?v=20260616-duration-text"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user