This commit is contained in:
root
2026-06-07 23:18:53 +08:00
parent ebd3d6a317
commit 934b9a4a23
36 changed files with 2272 additions and 296 deletions

View File

@@ -2,6 +2,7 @@
import { useState, useMemo, useSyncExternalStore } from "react";
import { useTranslation } from "@/app/digital/i18n/navigation";
import { mediaUrl } from "@/app/lib/media";
import type { ResourceData } from "@/app/digital/lib/theme-data";
import DigitalThemeIcon, { type IconName } from "./DigitalThemeIcon";
@@ -98,7 +99,7 @@ export default function ResourceNavigation({ data }: ResourceNavigationProps) {
style={{ aspectRatio: "2/3" }}
>
<img
src={book.cover}
src={mediaUrl(book.cover)}
alt={book.title}
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
/>
@@ -167,7 +168,7 @@ export default function ResourceNavigation({ data }: ResourceNavigationProps) {
style={{ aspectRatio: "1/1" }}
>
<img
src={product.image}
src={mediaUrl(product.image)}
alt={product.name}
className="h-full w-full object-contain transition-transform duration-300 group-hover:scale-105"
/>

View File

@@ -1,6 +1,7 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { mediaUrl } from "@/app/lib/media";
const STORAGE_PREFIX = "lms-video-";
const SAVE_INTERVAL = 5000;
@@ -215,8 +216,8 @@ export function VideoPlayer({ src, poster, title, className = "", videoId, onCom
>
<video
ref={videoRef}
src={src}
poster={poster}
src={mediaUrl(src)}
poster={mediaUrl(poster)}
title={title}
className="aspect-video w-full object-contain"
onClick={togglePlay}

View File

@@ -1,4 +1,5 @@
import { marked } from "marked";
import { mediaUrl } from "@/app/lib/media";
export interface EbookChunk {
html: string;
@@ -10,9 +11,21 @@ export interface EbookData {
headers: string[];
}
function rewriteMediaTag(tag: string): string {
return tag.replace(/\s(src|poster)=(["'])(.*?)\2/gi, (match, attr: string, quote: string, value: string) => {
const rewritten = mediaUrl(value);
return rewritten ? ` ${attr}=${quote}${rewritten}${quote}` : match;
});
}
function rewriteEmbeddedMedia(html: string): string {
return html.replace(/<(img|video|audio|source)\b[^>]*>/gi, rewriteMediaTag);
}
export function parseEbookMarkdown(source: string): EbookData {
const html = marked.parse(source, { breaks: true, gfm: true }) as string;
const lazyHtml = html.replace(/<img /g, '<img loading="lazy" decoding="async" ');
const mediaHtml = rewriteEmbeddedMedia(html);
const lazyHtml = mediaHtml.replace(/<img /g, '<img loading="lazy" decoding="async" ');
let rawChunks = lazyHtml.split(/(?=<h1[\s>])/i).filter((c) => c.trim());
if (rawChunks.length <= 1) {

View File

@@ -6,12 +6,14 @@
import * as Minio from "minio";
import { getMinioConfig } from "./config";
import { getThemeConfig } from "@/config/digital/site.config";
import type { MinioConfig } from "./config";
const ensuredBuckets = new Set<string>();
/** 获取 MinIO 客户端实例(使用主题配置) */
export function getMinioClient(): Minio.Client {
export function getMinioClient(config?: MinioConfig): Minio.Client {
const theme = getThemeConfig();
const cfg = getMinioConfig(theme.services?.minio);
const cfg = config || getMinioConfig(theme.services?.minio);
return new Minio.Client({
endPoint: cfg.endPoint,
port: cfg.port,
@@ -23,6 +25,29 @@ export function getMinioClient(): Minio.Client {
});
}
export async function ensurePublicBucket(client: Minio.Client, bucket: string): Promise<void> {
if (ensuredBuckets.has(bucket)) return;
const exists = await client.bucketExists(bucket);
if (!exists) {
await client.makeBucket(bucket);
}
await client.setBucketPolicy(
bucket,
JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: { AWS: ["*"] },
Action: ["s3:GetObject"],
Resource: [`arn:aws:s3:::${bucket}/*`],
},
],
})
);
ensuredBuckets.add(bucket);
}
export interface UploadResult {
url: string;
objectKey: string;
@@ -46,7 +71,8 @@ export async function uploadBuffer(
throw new Error("MinIO 存储未启用");
}
const client = getMinioClient();
const client = getMinioClient(cfg);
await ensurePublicBucket(client, cfg.bucket);
await client.putObject(cfg.bucket, objectKey, buffer, buffer.length, {
"Content-Type": contentType,
});