This commit is contained in:
biss committed 2026-08-11 21:14:12 +08:00
1 parent 02fcf9ae8f
commit e5afe60a11
8 files changed
+256 -65

No files matched your search

@@ -4,6 +4,7 @@ using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth; using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Excel; using Jiaowu.Api.Infrastructure.Excel;
using Jiaowu.Api.Infrastructure.Persistence; using Jiaowu.Api.Infrastructure.Persistence;
using Jiaowu.Api.Infrastructure.OfficialDocuments;
using Jiaowu.Api.Infrastructure.Timetables; using Jiaowu.Api.Infrastructure.Timetables;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -17,7 +18,8 @@ namespace Jiaowu.Api.Controllers;
public sealed class TimetableManagementController( public sealed class TimetableManagementController(
AppDbContext db, AppDbContext db,
TimetableDataService timetableDataService, TimetableDataService timetableDataService,
ICurrentUserDataScope currentUserDataScope) : ControllerBase ICurrentUserDataScope currentUserDataScope,
OfficialDocumentOptions officialDocumentOptions) : ControllerBase
{ {
private const string ManagementRoles = private const string ManagementRoles =
SystemRoles.SuperAdmin + "," + SystemRoles.SuperAdmin + "," +
@@ -239,7 +241,7 @@ public sealed class TimetableManagementController(
} }
var fileName = $"课表批量导出-{FileName(timetables[0].Term.Name)}.pdf"; var fileName = $"课表批量导出-{FileName(timetables[0].Term.Name)}.pdf";
return File(TimetablePdfExporter.Create(timetables), "application/pdf", fileName); return File(TimetablePdfExporter.Create(timetables, officialDocumentOptions.FontPath), "application/pdf", fileName);
} }
[HttpPost("display-links/export.xlsx")] [HttpPost("display-links/export.xlsx")]
@@ -297,7 +299,7 @@ public sealed class TimetableManagementController(
private ActionResult PdfFile(TimetableData timetable) => private ActionResult PdfFile(TimetableData timetable) =>
File( File(
TimetablePdfExporter.Create(timetable), TimetablePdfExporter.Create(timetable, officialDocumentOptions.FontPath),
"application/pdf", "application/pdf",
$"{FileName(timetable.Subject.Name)}-{FileName(timetable.Term.Name)}-课表.pdf"); $"{FileName(timetable.Subject.Name)}-{FileName(timetable.Term.Name)}-课表.pdf");
} }
@@ -5,6 +5,7 @@ using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Caching; using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Excel; using Jiaowu.Api.Infrastructure.Excel;
using Jiaowu.Api.Infrastructure.Persistence; using Jiaowu.Api.Infrastructure.Persistence;
using Jiaowu.Api.Infrastructure.OfficialDocuments;
using Jiaowu.Api.Infrastructure.Timetables; using Jiaowu.Api.Infrastructure.Timetables;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -18,7 +19,8 @@ public sealed class TimetablesController(
AppDbContext db, AppDbContext db,
TimetableDataService timetableDataService, TimetableDataService timetableDataService,
PersonalCalendarService personalCalendarService, PersonalCalendarService personalCalendarService,
IAppCache cache) : ControllerBase IAppCache cache,
OfficialDocumentOptions officialDocumentOptions) : ControllerBase
{ {
[HttpGet("options")] [HttpGet("options")]
[AllowAnonymous] [AllowAnonymous]
@@ -517,7 +519,7 @@ public sealed class TimetablesController(
{ {
var fileName = var fileName =
$"{SafeFileName(result.Subject.Name)}-{SafeFileName(result.Term.Name)}-课表.pdf"; $"{SafeFileName(result.Subject.Name)}-{SafeFileName(result.Term.Name)}-课表.pdf";
return File(TimetablePdfExporter.Create(result), "application/pdf", fileName); return File(TimetablePdfExporter.Create(result, officialDocumentOptions.FontPath), "application/pdf", fileName);
} }
private static string SafeFileName(string value) private static string SafeFileName(string value)
@@ -1,6 +1,7 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using QRCoder; using QRCoder;
using SkiaSharp; using SkiaSharp;
using Jiaowu.Api.Infrastructure.Pdf;
namespace Jiaowu.Api.Infrastructure.OfficialDocuments; namespace Jiaowu.Api.Infrastructure.OfficialDocuments;
@@ -28,7 +29,7 @@ public sealed class OfficialDocumentPdfGenerator(
OfficialDocumentSnapshot snapshot, OfficialDocumentSnapshot snapshot,
string verificationUrl) string verificationUrl)
{ {
using var typeface = ResolveTypeface(); using var typeface = PdfTypefaceResolver.ResolveChineseTypeface(options.FontPath);
using var stream = new MemoryStream(); using var stream = new MemoryStream();
using (var document = SKDocument.CreatePdf(stream)) using (var document = SKDocument.CreatePdf(stream))
{ {
@@ -382,28 +383,5 @@ public sealed class OfficialDocumentPdfGenerator(
return value + "…"; return value + "…";
} }
private SKTypeface ResolveTypeface()
{
var candidates = new[]
{
options.FontPath,
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "simhei.ttf"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "msyh.ttc"),
"/usr/share/fonts/noto/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc"
};
foreach (var path in candidates.Where(x => !string.IsNullOrWhiteSpace(x)))
{
if (!File.Exists(path)) continue;
var typeface = SKTypeface.FromFile(path);
if (typeface is not null) return typeface;
}
var fallback = SKTypeface.FromFamilyName("Microsoft YaHei") ??
SKTypeface.FromFamilyName("Noto Sans CJK SC");
return fallback ?? throw new InvalidOperationException(
"未找到可用于官方 PDF 的中文字体。请配置 OfficialDocuments:FontPath。");
}
private static string FormatDecimal(decimal value) => value.ToString("0.##"); private static string FormatDecimal(decimal value) => value.ToString("0.##");
} }
Loaded 3 of 8 files, more files were not shown because too many files have changed in this diff. Show more