You've already forked lubo_comment_query
节目建设页 OCR 侧栏点击填入
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* 建设页片段 OCR 侧栏:点击 OCR 结果填入表单(焦点跟随模式)。
|
||||
* 数据通路:服务端只下发片段元数据(data-pivots:分P范围/起止秒/CDN URL),
|
||||
* 帧级重数据由前端直接 fetch CDN 全量 JSON(依赖桶 CORS 放行本站;导入时已同步导出)。
|
||||
* 连续文本相同的帧前端去重;in_range 依据 pivot 起止秒在前端计算。
|
||||
* 模式:
|
||||
* - text(节目编辑页):点击文本行填入最近聚焦的目标字段,支持 覆盖/追加。
|
||||
* - time(关联视频编辑页):点击帧时间填入所在组(开始/结束)的 P数+时间点。
|
||||
* 面板 data 属性:data-pivots(JSON)/ data-pivot(固定 pivot id,可空)/ data-mode / data-target-labels(JSON)。
|
||||
*/
|
||||
(function () {
|
||||
// 展示用:小时不补零(与 ocr_canvas.js 一致)
|
||||
function formatHms(seconds) {
|
||||
seconds = Math.max(0, Math.floor(seconds));
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = seconds % 60;
|
||||
const pad = function (n) { return String(n).padStart(2, "0"); };
|
||||
return h + ":" + pad(m) + ":" + pad(s);
|
||||
}
|
||||
|
||||
// 填入 <input type="time"> 用:必须 HH:MM:SS 全补零,否则赋值被清空
|
||||
function formatInputHms(seconds) {
|
||||
seconds = Math.max(0, Math.floor(seconds));
|
||||
const pad = function (n) { return String(n).padStart(2, "0"); };
|
||||
return pad(Math.floor(seconds / 3600)) + ":" + pad(Math.floor((seconds % 3600) / 60)) + ":" + pad(seconds % 60);
|
||||
}
|
||||
|
||||
function el(tag, className, text) {
|
||||
const node = document.createElement(tag);
|
||||
if (className) { node.className = className; }
|
||||
if (text !== undefined) { node.textContent = text; }
|
||||
return node;
|
||||
}
|
||||
|
||||
function flash(input) {
|
||||
input.style.transition = "background-color 0.4s";
|
||||
input.style.backgroundColor = "#fef08a";
|
||||
setTimeout(function () { input.style.backgroundColor = ""; }, 400);
|
||||
}
|
||||
|
||||
// 多分P CDN 全量 JSON → 面板记录:合并、按 (part_num, ts) 排序、连续文本去重、标 in_range
|
||||
function buildRecords(pivot, payloads) {
|
||||
const recs = [];
|
||||
payloads.forEach(function (payload) {
|
||||
(payload.recs || []).forEach(function (rec) {
|
||||
const texts = (rec[1] || [])
|
||||
.map(function (line) { return (line[4] || "").trim(); })
|
||||
.filter(function (text) { return text !== ""; });
|
||||
recs.push({ part_num: payload.part, ts: rec[0], texts: texts });
|
||||
});
|
||||
});
|
||||
recs.sort(function (a, b) { return a.part_num - b.part_num || a.ts - b.ts; });
|
||||
const result = [];
|
||||
let lastText = null;
|
||||
recs.forEach(function (rec) {
|
||||
const joined = rec.texts.join("\n");
|
||||
if (joined === lastText) { return; }
|
||||
lastText = joined;
|
||||
let inRange = true;
|
||||
if (pivot.start_sec !== null && rec.part_num === pivot.start_part && rec.ts < pivot.start_sec) {
|
||||
inRange = false;
|
||||
}
|
||||
if (pivot.stop_sec !== null && rec.part_num === pivot.stop_part && rec.ts > pivot.stop_sec) {
|
||||
inRange = false;
|
||||
}
|
||||
result.push({
|
||||
part_num: rec.part_num,
|
||||
ts: rec.ts,
|
||||
time: formatHms(rec.ts),
|
||||
in_range: inRange,
|
||||
texts: rec.texts,
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function init(panel) {
|
||||
const pivots = JSON.parse(panel.dataset.pivots || "[]");
|
||||
const fixedPivot = panel.dataset.pivot || "";
|
||||
const mode = panel.dataset.mode || "text";
|
||||
const labels = JSON.parse(panel.dataset.targetLabels || "{}");
|
||||
const body = panel.querySelector("[data-ocr-fill-body]");
|
||||
const targetInputs = Array.prototype.slice.call(document.querySelectorAll("[data-ocr-target]"));
|
||||
const targetValues = [];
|
||||
targetInputs.forEach(function (input) {
|
||||
if (targetValues.indexOf(input.dataset.ocrTarget) === -1) {
|
||||
targetValues.push(input.dataset.ocrTarget);
|
||||
}
|
||||
});
|
||||
|
||||
let pivot = null; // 当前片段元数据
|
||||
let records = null; // 当前片段记录(buildRecords 结果)
|
||||
let loadSeq = 0;
|
||||
let currentTarget = targetValues[0] || null;
|
||||
let appendMode = false;
|
||||
let showOut = false;
|
||||
let outFrames = [];
|
||||
|
||||
body.innerHTML = "";
|
||||
body.className = "text-sm";
|
||||
const status = el("div", "text-xs text-gray-400 dark:text-gray-500 mb-2");
|
||||
const pivotRow = el("div", "mb-2");
|
||||
const toolbar = el("div", "flex flex-wrap items-center gap-x-3 gap-y-2 mb-3 text-xs");
|
||||
const list = el("div", "space-y-3 max-h-[70vh] overflow-y-auto pr-1");
|
||||
body.appendChild(status);
|
||||
body.appendChild(pivotRow);
|
||||
body.appendChild(toolbar);
|
||||
body.appendChild(list);
|
||||
|
||||
function showStatus(text) {
|
||||
status.textContent = text;
|
||||
status.style.display = text ? "" : "none";
|
||||
}
|
||||
|
||||
function targetLabel(value) {
|
||||
return labels[value] || value || "(无目标)";
|
||||
}
|
||||
|
||||
// ---- 目标字段(焦点跟随 + 工具条 chip 切换)----
|
||||
const chip = el("button", "px-2 py-1 rounded border border-indigo-400 text-indigo-600 dark:text-indigo-300 hover:bg-indigo-50 dark:hover:bg-indigo-900/40 transition-colors");
|
||||
chip.type = "button";
|
||||
chip.addEventListener("click", function () {
|
||||
if (targetValues.length === 0) { return; }
|
||||
const idx = targetValues.indexOf(currentTarget);
|
||||
setTarget(targetValues[(idx + 1) % targetValues.length], true);
|
||||
});
|
||||
toolbar.appendChild(el("span", "text-gray-500 dark:text-gray-400", "填入到:"));
|
||||
toolbar.appendChild(chip);
|
||||
|
||||
function setTarget(value, focusInput) {
|
||||
currentTarget = value;
|
||||
chip.textContent = targetLabel(value);
|
||||
targetInputs.forEach(function (input) {
|
||||
const active = input.dataset.ocrTarget === value;
|
||||
input.style.outline = active ? "2px solid #6366f1" : "";
|
||||
input.style.outlineOffset = active ? "1px" : "";
|
||||
if (active && focusInput) { input.focus(); }
|
||||
});
|
||||
}
|
||||
|
||||
targetInputs.forEach(function (input) {
|
||||
input.addEventListener("focusin", function () { setTarget(input.dataset.ocrTarget, false); });
|
||||
});
|
||||
|
||||
// ---- 覆盖/追加(text 模式)----
|
||||
if (mode === "text") {
|
||||
const appendLabel = el("label", "inline-flex items-center gap-1 text-gray-600 dark:text-gray-300 cursor-pointer");
|
||||
const appendBox = el("input", "rounded border-gray-300 dark:border-gray-600 text-indigo-600");
|
||||
appendBox.type = "checkbox";
|
||||
appendBox.addEventListener("change", function () { appendMode = appendBox.checked; });
|
||||
appendLabel.appendChild(appendBox);
|
||||
appendLabel.appendChild(document.createTextNode("追加"));
|
||||
toolbar.appendChild(appendLabel);
|
||||
}
|
||||
|
||||
// ---- 含片段外 ----
|
||||
const outLabel = el("label", "inline-flex items-center gap-1 text-gray-600 dark:text-gray-300 cursor-pointer");
|
||||
const outBox = el("input", "rounded border-gray-300 dark:border-gray-600 text-indigo-600");
|
||||
outBox.type = "checkbox";
|
||||
outBox.addEventListener("change", function () {
|
||||
showOut = outBox.checked;
|
||||
outFrames.forEach(function (frame) { frame.style.display = showOut ? "" : "none"; });
|
||||
});
|
||||
outLabel.appendChild(outBox);
|
||||
outLabel.appendChild(document.createTextNode("含片段外"));
|
||||
toolbar.appendChild(outLabel);
|
||||
|
||||
// ---- 填入动作 ----
|
||||
function fillText(text) {
|
||||
if (!currentTarget) { return; }
|
||||
const input = targetInputs.find(function (i) { return i.dataset.ocrTarget === currentTarget; });
|
||||
if (!input) { return; }
|
||||
if (appendMode && input.value.trim() !== "") {
|
||||
input.value = input.value.replace(/\s+$/, "") + " " + text;
|
||||
} else {
|
||||
input.value = text;
|
||||
}
|
||||
flash(input);
|
||||
}
|
||||
|
||||
function fillTime(rec) {
|
||||
const group = currentTarget || "start";
|
||||
targetInputs.forEach(function (input) {
|
||||
if (input.dataset.ocrTarget !== group) { return; }
|
||||
if (input.dataset.ocrKind === "part") {
|
||||
input.value = String(rec.part_num);
|
||||
flash(input);
|
||||
} else if (input.dataset.ocrKind === "time") {
|
||||
input.value = formatInputHms(rec.ts);
|
||||
flash(input);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---- 记录渲染 ----
|
||||
function renderRecords() {
|
||||
list.innerHTML = "";
|
||||
outFrames = [];
|
||||
if (!records || !records.length) {
|
||||
showStatus("该片段暂无 OCR 数据");
|
||||
return;
|
||||
}
|
||||
showStatus("");
|
||||
records.forEach(function (rec) {
|
||||
const frame = el("div", rec.in_range ? "" : "opacity-50");
|
||||
if (!rec.in_range) {
|
||||
outFrames.push(frame);
|
||||
if (!showOut) { frame.style.display = "none"; }
|
||||
}
|
||||
const head = el("div", "flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400");
|
||||
const clickZone = el("span", mode === "time" ? "inline-flex items-center gap-2 cursor-pointer hover:text-indigo-600 dark:hover:text-indigo-300" : "inline-flex items-center gap-2");
|
||||
clickZone.appendChild(el("span", "font-mono font-semibold", rec.time));
|
||||
clickZone.appendChild(el("span", "", "P" + rec.part_num));
|
||||
if (mode === "time") {
|
||||
clickZone.title = "点击填入" + targetLabel(currentTarget || "start");
|
||||
clickZone.addEventListener("click", function () { fillTime(rec); });
|
||||
}
|
||||
head.appendChild(clickZone);
|
||||
const link = el("a", "text-gray-400 hover:text-indigo-500", "↗");
|
||||
link.href = "https://www.bilibili.com/video/" + pivot.bvid + "?p=" + rec.part_num + "&t=" + Math.floor(rec.ts);
|
||||
link.target = "_blank";
|
||||
link.title = "打开视频核对";
|
||||
head.appendChild(link);
|
||||
frame.appendChild(head);
|
||||
const lines = el("div", "mt-0.5");
|
||||
rec.texts.forEach(function (text) {
|
||||
const line = el("div", "", text);
|
||||
if (mode === "text") {
|
||||
line.className = "px-1 -mx-1 rounded cursor-pointer hover:bg-indigo-50 dark:hover:bg-indigo-900/40 text-gray-800 dark:text-gray-200";
|
||||
line.addEventListener("click", function () {
|
||||
fillText(text);
|
||||
line.style.transition = "background-color 0.4s";
|
||||
line.style.backgroundColor = "#e0e7ff";
|
||||
setTimeout(function () { line.style.backgroundColor = ""; }, 400);
|
||||
});
|
||||
} else {
|
||||
line.className = "text-gray-600 dark:text-gray-400";
|
||||
}
|
||||
lines.appendChild(line);
|
||||
});
|
||||
frame.appendChild(lines);
|
||||
list.appendChild(frame);
|
||||
});
|
||||
}
|
||||
|
||||
// ---- 片段选择 ----
|
||||
function defaultPivot() {
|
||||
if (fixedPivot) {
|
||||
const found = pivots.find(function (p) { return String(p.id) === fixedPivot; });
|
||||
if (found) { return found; }
|
||||
}
|
||||
const withOcr = pivots.find(function (p) { return Object.keys(p.urls || {}).length > 0; });
|
||||
return withOcr || pivots[0] || null;
|
||||
}
|
||||
|
||||
function renderPivotSelect() {
|
||||
pivotRow.innerHTML = "";
|
||||
if (fixedPivot || pivots.length <= 1) { return; }
|
||||
const select = el("select", "w-full text-sm rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700");
|
||||
pivots.forEach(function (p) {
|
||||
const option = el("option", "", p.label + (Object.keys(p.urls || {}).length ? "" : "(无OCR)"));
|
||||
option.value = String(p.id);
|
||||
select.appendChild(option);
|
||||
});
|
||||
select.value = String(pivot.id);
|
||||
select.addEventListener("change", function () {
|
||||
const found = pivots.find(function (p) { return String(p.id) === select.value; });
|
||||
if (found) { load(found); }
|
||||
});
|
||||
pivotRow.appendChild(select);
|
||||
}
|
||||
|
||||
function load(nextPivot) {
|
||||
const seq = ++loadSeq;
|
||||
pivot = nextPivot;
|
||||
records = null;
|
||||
list.innerHTML = "";
|
||||
renderPivotSelect();
|
||||
const urls = Object.keys(pivot.urls || {}).map(function (partNum) { return pivot.urls[partNum]; });
|
||||
if (!urls.length) {
|
||||
showStatus("该片段暂无 OCR 数据");
|
||||
return;
|
||||
}
|
||||
showStatus("加载中…");
|
||||
Promise.all(urls.map(function (url) {
|
||||
return fetch(url).then(function (response) {
|
||||
if (!response.ok) { throw new Error("HTTP " + response.status); }
|
||||
return response.json();
|
||||
});
|
||||
})).then(function (payloads) {
|
||||
if (seq !== loadSeq) { return; }
|
||||
records = buildRecords(pivot, payloads);
|
||||
renderRecords();
|
||||
}).catch(function () {
|
||||
if (seq !== loadSeq) { return; }
|
||||
showStatus("OCR 数据加载失败(检查 CDN CORS 配置或该分P是否已导出)");
|
||||
});
|
||||
}
|
||||
|
||||
setTarget(currentTarget, false);
|
||||
if (!pivots.length) {
|
||||
showStatus("该节目暂无关联视频");
|
||||
return;
|
||||
}
|
||||
load(defaultPivot());
|
||||
}
|
||||
|
||||
window.OcrFill = {
|
||||
init: init,
|
||||
_buildRecords: buildRecords, // 暴露给离线校验
|
||||
};
|
||||
})();
|
||||
@@ -9,7 +9,9 @@
|
||||
@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">
|
||||
@php($show_ocr_panel = $program->id && !empty($ocr_pivots))
|
||||
<div class="@if($show_ocr_panel) lg:flex lg:gap-6 @else max-w-3xl mx-auto @endif">
|
||||
<div class="@if($show_ocr_panel) flex-1 min-w-0 max-w-3xl @endif">
|
||||
<div class="md:flex md:items-center md:justify-between mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
@if($program->id) 编辑节目 @else 添加节目 @endif
|
||||
@@ -27,19 +29,19 @@
|
||||
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">节目名称</label>
|
||||
<input type="text" name="name" id="name" required autocomplete="off" value="{{ old("name", $program->name) }}"
|
||||
<input type="text" name="name" id="name" required autocomplete="off" data-ocr-target="name" value="{{ old("name", $program->name) }}"
|
||||
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="difficulty" class="block text-sm font-medium text-gray-700 dark:text-gray-300">节目难度</label>
|
||||
<input type="text" name="difficulty" id="difficulty" autocomplete="off" value="{{ old("difficulty", $program->difficulty) }}"
|
||||
<input type="text" name="difficulty" id="difficulty" autocomplete="off" data-ocr-target="difficulty" value="{{ old("difficulty", $program->difficulty) }}"
|
||||
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="desc" class="block text-sm font-medium text-gray-700 dark:text-gray-300">节目要求</label>
|
||||
<input type="text" name="desc" id="desc" autocomplete="off" value="{{ old("desc", $program->desc) }}"
|
||||
<input type="text" name="desc" id="desc" autocomplete="off" data-ocr-target="desc" value="{{ old("desc", $program->desc) }}"
|
||||
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>
|
||||
|
||||
@@ -110,8 +112,25 @@
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@if($show_ocr_panel)
|
||||
<div class="w-full lg:w-96 flex-shrink-0 mt-8 lg:mt-0" id="ocr-fill-panel"
|
||||
data-mode="text"
|
||||
data-pivots='@json($ocr_pivots ?? [])'
|
||||
data-target-labels='{"name":"节目名称","difficulty":"节目难度","desc":"节目要求"}'>
|
||||
<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>
|
||||
<div data-ocr-fill-body class="text-sm text-gray-500 dark:text-gray-400">加载中…</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</main>
|
||||
@if($show_ocr_panel)
|
||||
<script src="{{ mix('/js/manifest.js') }}"></script>
|
||||
<script src="{{ mix('/js/component/ocr_fill.js') }}"></script>
|
||||
<script>document.addEventListener("DOMContentLoaded", function () { OcrFill.init(document.getElementById("ocr-fill-panel")); });</script>
|
||||
@endif
|
||||
@include("common.footer")
|
||||
</body>
|
||||
</html>
|
||||
@@ -9,7 +9,9 @@
|
||||
@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">
|
||||
@php($show_ocr_panel = (bool)$program_video->id)
|
||||
<div class="@if($show_ocr_panel) lg:flex lg:gap-6 @else max-w-3xl mx-auto @endif">
|
||||
<div class="@if($show_ocr_panel) flex-1 min-w-0 max-w-3xl @endif">
|
||||
<div class="md:flex md:items-center md:justify-between mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
关联视频修改
|
||||
@@ -39,12 +41,12 @@
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white uppercase tracking-wider">开始节点</h3>
|
||||
<div>
|
||||
<label for="start_part" class="block text-sm font-medium text-gray-700 dark:text-gray-300">P数</label>
|
||||
<input type="number" name="start_part" id="start_part" value="{{ old("start_part", $program_video->start_part) }}"
|
||||
<input type="number" name="start_part" id="start_part" data-ocr-target="start" data-ocr-kind="part" value="{{ old("start_part", $program_video->start_part) }}"
|
||||
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="start_time" class="block text-sm font-medium text-gray-700 dark:text-gray-300">时间点</label>
|
||||
<input type="time" step="1" name="start_time" id="start_time" value="{{ old("start_time", $program_video->start_time) }}"
|
||||
<input type="time" step="1" name="start_time" id="start_time" data-ocr-target="start" data-ocr-kind="time" value="{{ old("start_time", $program_video->start_time) }}"
|
||||
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>
|
||||
@@ -61,12 +63,12 @@
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white uppercase tracking-wider">结束节点</h3>
|
||||
<div>
|
||||
<label for="stop_part" class="block text-sm font-medium text-gray-700 dark:text-gray-300">P数</label>
|
||||
<input type="number" name="stop_part" id="stop_part" value="{{ old("stop_part", $program_video->stop_part) }}"
|
||||
<input type="number" name="stop_part" id="stop_part" data-ocr-target="stop" data-ocr-kind="part" value="{{ old("stop_part", $program_video->stop_part) }}"
|
||||
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="stop_time" class="block text-sm font-medium text-gray-700 dark:text-gray-300">时间点</label>
|
||||
<input type="time" step="1" name="stop_time" id="stop_time" value="{{ old("stop_time", $program_video->stop_time) }}"
|
||||
<input type="time" step="1" name="stop_time" id="stop_time" data-ocr-target="stop" data-ocr-kind="time" value="{{ old("stop_time", $program_video->stop_time) }}"
|
||||
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>
|
||||
@@ -112,8 +114,26 @@
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@if($show_ocr_panel)
|
||||
<div class="w-full lg:w-96 flex-shrink-0 mt-8 lg:mt-0" id="ocr-fill-panel"
|
||||
data-mode="time"
|
||||
data-pivot="{{ $program_video->id }}"
|
||||
data-pivots='@json($ocr_pivots ?? [])'
|
||||
data-target-labels='{"start":"开始节点","stop":"结束节点"}'>
|
||||
<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>
|
||||
<div data-ocr-fill-body class="text-sm text-gray-500 dark:text-gray-400">加载中…</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</main>
|
||||
@if($show_ocr_panel)
|
||||
<script src="{{ mix('/js/manifest.js') }}"></script>
|
||||
<script src="{{ mix('/js/component/ocr_fill.js') }}"></script>
|
||||
<script>document.addEventListener("DOMContentLoaded", function () { OcrFill.init(document.getElementById("ocr-fill-panel")); });</script>
|
||||
@endif
|
||||
@include("common.footer")
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user