This commit is contained in:
2026-07-29 10:40:52 +08:00
parent b7ad3fda49
commit 7c89d13f81
12 changed files with 654 additions and 1 deletions
@@ -0,0 +1,91 @@
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>OCR 导入 - 录播查询</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="{{ mix('/css/app.css') }}" rel="stylesheet"/>
</head>
<body class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-100">
@include("common.header")
<main class="flex-grow container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="max-w-3xl mx-auto">
<div class="md:flex md:items-center md:justify-between mb-8">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
OCR 结果导入
</h1>
<a href="{{ url()->previous() }}" class="mt-4 md:mt-0 inline-flex items-center text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<svg class="h-4 w-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
返回
</a>
</div>
@if(session("import_results"))
<div class="mb-6 bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
<ul class="divide-y divide-gray-200 dark:divide-gray-700">
@foreach(session("import_results") as $result)
<li class="px-6 py-3 flex items-start text-sm">
@if($result["ok"])
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
@else
<svg class="h-5 w-5 text-red-500 mr-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
@endif
<span class="text-gray-900 dark:text-gray-100 font-mono mr-2">{{ $result["file"] }}</span>
<span class="text-gray-600 dark:text-gray-400">{{ $result["message"] }}</span>
</li>
@endforeach
</ul>
</div>
@endif
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
<form class="p-6 space-y-6" action="" method="post" enctype="multipart/form-data">
@csrf
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div class="sm:col-span-2">
<label for="video_bvid" class="block text-sm font-medium text-gray-700 dark:text-gray-300">BVID(留空按文件名自动匹配)</label>
<input type="text" name="video_bvid" id="video_bvid" value="{{ old('video_bvid') }}"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm py-2">
</div>
<div>
<label for="part_num" class="block text-sm font-medium text-gray-700 dark:text-gray-300">分P号</label>
<input type="number" name="part_num" id="part_num" min="1" value="{{ old('part_num') }}"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm py-2">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">OCR 结果文件 (支持批量)</label>
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 dark:border-gray-600 border-dashed rounded-md hover:border-indigo-500 transition-colors">
<div class="space-y-1 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div class="flex text-sm text-gray-600 dark:text-gray-400">
<label for="file-upload" class="relative cursor-pointer bg-white dark:bg-gray-800 rounded-md font-medium text-indigo-600 dark:text-indigo-400 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
<span>上传文件</span>
<input id="file-upload" name="file[]" multiple type="file" accept=".jsonl" class="sr-only" onchange="document.getElementById('file-count').innerText = this.files.length + ' 个文件已选择'">
</label>
<p class="pl-1">或拖拽文件到这里</p>
</div>
<p class="text-xs text-gray-500 dark:text-gray-500">result_*.jsonl 格式;同一分P重复导入会整体覆盖</p>
<p class="text-sm text-indigo-600 font-medium" id="file-count"></p>
</div>
</div>
</div>
@include("common.form_error")
<div class="flex items-center justify-end pt-4 border-t border-gray-200 dark:border-gray-700">
<button type="submit" class="inline-flex justify-center py-2 px-6 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
开始导入
</button>
</div>
</form>
</div>
</div>
</main>
@include("common.footer")
</body>
</html>
@@ -47,6 +47,10 @@
节目开始位置
@endif
</x-links.video_link>
@if($video_pivot->start_ocr)
<canvas class="mt-1 w-full max-w-[180px] bg-black rounded" data-ocr-frame="{{ json_encode($video_pivot->start_ocr, JSON_UNESCAPED_UNICODE) }}"></canvas>
<div class="text-xs text-gray-500">OCR @ {{ gmdate("H:i:s", (int)$video_pivot->start_ocr["ts"]) }}</div>
@endif
</td>
<td class="border align-bottom">
<x-links.video_link :bvid="$video_pivot->video_bvid" :part="$video_pivot->stop_part" :time="$video_pivot->stop_time">
@@ -57,11 +61,17 @@
节目结束位置
@endif
</x-links.video_link>
@if($video_pivot->stop_ocr)
<canvas class="mt-1 w-full max-w-[180px] bg-black rounded" data-ocr-frame="{{ json_encode($video_pivot->stop_ocr, JSON_UNESCAPED_UNICODE) }}"></canvas>
<div class="text-xs text-gray-500">OCR @ {{ gmdate("H:i:s", (int)$video_pivot->stop_ocr["ts"]) }}</div>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
<script src="{{ mix('/js/component/ocr_canvas.js') }}"></script>
<script>document.addEventListener("DOMContentLoaded", function () { OcrCanvas.initSnapshots(); });</script>
@include("common.footer")
</body>
</html>
+31
View File
@@ -19,6 +19,9 @@
</a>
</div>
<div class="flex flex-col lg:flex-row gap-8">
<div class="flex-grow min-w-0">
<!-- Desktop Table -->
<div class="hidden lg:block bg-white dark:bg-gray-800 shadow overflow-hidden rounded-lg mb-8">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
@@ -154,11 +157,39 @@
导入直播弹幕
</a>
@endif
<a href="{{ url(route("ocr.construct.import.page", ["video_bvid"=>$video->bvid])) }}" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
导入OCR结果
</a>
</div>
@endauth
</div>
@if($ocr_urls->isNotEmpty())
<div class="w-full lg:w-72 flex-shrink-0" id="ocr-panel" data-urls="{{ $ocr_urls->toJson() }}">
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-4 lg:sticky lg:top-20">
<h2 class="text-lg font-bold text-gray-900 dark:text-white mb-3">右侧栏 OCR</h2>
@if($ocr_urls->count() > 1)
<div class="flex flex-wrap gap-2 mb-3">
@foreach($ocr_urls as $part_num => $ocr_url)
<button type="button" data-ocr-part="{{ $part_num }}" class="px-2 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:border-indigo-500 transition-colors">P{{ $part_num }}</button>
@endforeach
</div>
@endif
<canvas class="w-full bg-black rounded"></canvas>
<input type="range" min="0" max="0" step="1" value="0" class="w-full mt-3 accent-indigo-600">
<div data-ocr-time class="text-xs text-gray-500 dark:text-gray-400 mt-1"></div>
<div data-ocr-status class="text-xs text-gray-400 dark:text-gray-500 mt-1" style="display:none"></div>
</div>
</div>
@endif
</div>
</div>
</main>
@if($ocr_urls->isNotEmpty())
<script src="{{ mix('/js/component/ocr_canvas.js') }}"></script>
<script>document.addEventListener("DOMContentLoaded", function () { OcrCanvas.initPlayer(document.getElementById("ocr-panel")); });</script>
@endif
@include("common.footer")
</body>
</html>