From 4875a0ee77a75cd59ceb46b139183bb2179d3539 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 16 Jun 2026 13:56:30 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=B1=87=E6=80=BB=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E5=B1=95=E7=A4=BA=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app/data.py | 48 +++++++++++++++++++++++++++++------- app/app/static/accounts.html | 4 +-- app/app/static/accounts.js | 8 ++++-- app/app/static/admin.html | 4 +-- app/app/static/admin.js | 21 +++++++++++++--- app/app/static/app.js | 12 ++++++--- app/app/static/index.html | 4 +-- 7 files changed, 76 insertions(+), 25 deletions(-) diff --git a/app/app/data.py b/app/app/data.py index 62e44b4..88e22be 100644 --- a/app/app/data.py +++ b/app/app/data.py @@ -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, } diff --git a/app/app/static/accounts.html b/app/app/static/accounts.html index 5a0214c..41a268b 100644 --- a/app/app/static/accounts.html +++ b/app/app/static/accounts.html @@ -4,7 +4,7 @@ 课时账户查询 - +
@@ -55,6 +55,6 @@ - + diff --git a/app/app/static/accounts.js b/app/app/static/accounts.js index 450041d..1b72f96 100644 --- a/app/app/static/accounts.js +++ b/app/app/static/accounts.js @@ -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("
"); + return payments.map((item) => `${escapeHtml(item.date)}:${escapeHtml(displayHours(item.hours, item.duration))}`).join("
"); } async function fetchJson(url) { @@ -89,7 +93,7 @@ async function loadAccounts() { (row) => ` ${escapeHtml(row.student)}
${escapeHtml(row.student_id)} ${escapeHtml(row.account_status)} - ${fmtHours(row.remaining)} + ${escapeHtml(displayHours(row.remaining, row.remaining_duration))} ${renderPayments(row.payments)} ${escapeHtml(row.note || "")} `, diff --git a/app/app/static/admin.html b/app/app/static/admin.html index e9974ff..b8cb2c1 100644 --- a/app/app/static/admin.html +++ b/app/app/static/admin.html @@ -4,7 +4,7 @@ 管理后台 - +
@@ -417,6 +417,6 @@ - + diff --git a/app/app/static/admin.js b/app/app/static/admin.js index 80446f5..a67304f 100644 --- a/app/app/static/admin.js +++ b/app/app/static/admin.js @@ -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("
"); + return payments.map((item) => `${escapeHtml(item.date)}:${escapeHtml(displayHours(item.hours, item.duration))}`).join("
"); } async function fetchJson(url, options = {}) { @@ -212,7 +216,7 @@ async function loadAccounts() { (row) => ` ${escapeHtml(row.student)}
${escapeHtml(row.student_id)} ${escapeHtml(row.account_status)} - ${fmtHours(row.remaining)} + ${escapeHtml(displayHours(row.remaining, row.remaining_duration))} ${renderPayments(row.payments)} ${escapeHtml(row.note || "")} @@ -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}`); } diff --git a/app/app/static/app.js b/app/app/static/app.js index a06e7bb..c388b01 100644 --- a/app/app/static/app.js +++ b/app/app/static/app.js @@ -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) {
${escapeHtml(group.teacher)} - ${group.count} 条记录 · ${fmtHours(group.totalHours)} + ${group.count} 条记录 · ${displayHours(group.totalHours)}
${sortRecordsForDisplay(group.records) @@ -265,7 +269,7 @@ function statusClass(status) { function renderPayments(payments) { if (!payments.length) return "暂无缴费记录"; return payments - .map((item) => `${escapeHtml(item.date)}:${fmtHours(item.hours)}`) + .map((item) => `${escapeHtml(item.date)}:${escapeHtml(displayHours(item.hours, item.duration))}`) .join(""); } @@ -279,7 +283,7 @@ function renderInlineAccount(account) { ${escapeHtml(account.account_status)} - +