49a5452141
This commit adds the first complete local-network deployment path for the project. It normalizes the runtime contract around a fixed container listener on 0.0.0.0:10000, binds the published compose port to 127.0.0.1, and keeps the image/build workflow aligned with the released container image. It also introduces an installation script, an nginx reverse-proxy template, and a safer SQLite backup flow based on sqlite3 .backup with retention and optional rclone upload support. Deployment-oriented configuration has been consolidated into .env.example, repository-local .env files are now ignored, and the deployment scripts are executable. In addition, the frontend mixed-content issue is fixed by switching the stylesheet reference to a root-relative static path, with tests updated to cover the regression. README guidance has been expanded to document the new install, nginx, backup, and restore conventions.
60 lines
2.1 KiB
HTML
60 lines
2.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{{ page_title or "搬家助手" }}</title>
|
|
<link rel="stylesheet" href="/static/style.css">
|
|
</head>
|
|
<body>
|
|
<main class="container">
|
|
<nav class="top-nav">
|
|
<a href="/boxes">箱子</a>
|
|
<a href="/search">搜索</a>
|
|
</nav>
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
<script>
|
|
document.addEventListener("click", function (event) {
|
|
const card = event.target.closest(".clickable-card[data-href]");
|
|
if (!card) return;
|
|
if (event.target.closest("a, button, form, input, textarea, select, label")) return;
|
|
|
|
window.location.href = card.dataset.href;
|
|
});
|
|
|
|
document.addEventListener("keydown", function (event) {
|
|
const card = event.target.closest(".clickable-card[data-href]");
|
|
if (!card) return;
|
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
if (event.target.closest("a, button, form, input, textarea, select, label")) return;
|
|
|
|
event.preventDefault();
|
|
window.location.href = card.dataset.href;
|
|
});
|
|
|
|
document.addEventListener("keydown", function (event) {
|
|
if (event.key !== "Enter") return;
|
|
if (event.target.tagName === "TEXTAREA") return;
|
|
if (event.target.type === "submit") return;
|
|
if (!event.target.closest("form")) return;
|
|
|
|
const focusable = Array.from(
|
|
event.target.form.querySelectorAll(
|
|
'input:not([type="hidden"]):not([type="submit"]):not([type="checkbox"]), textarea, select'
|
|
)
|
|
).filter((element) => !element.disabled);
|
|
|
|
const index = focusable.indexOf(event.target);
|
|
if (index === -1 || index === focusable.length - 1) return;
|
|
|
|
event.preventDefault();
|
|
focusable[index + 1].focus();
|
|
if (focusable[index + 1].select) {
|
|
focusable[index + 1].select();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|