Initialize Fonrey Django multi-tenant project skeleton

Set up the required directory layout, app scaffolding, core settings, templates, static assets, and Docker/Tailwind tooling to establish a standardized development baseline.
This commit is contained in:
2026-04-26 17:12:09 +08:00
commit 4aba6dfa77
170 changed files with 1220 additions and 0 deletions

21
templates/base.html Normal file
View File

@@ -0,0 +1,21 @@
{% load static %}
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Fonrey{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/output.css' %}">
{% if use_flatpickr %}
<link rel="stylesheet" href="{% static 'vendor/flatpickr.min.css' %}">
{% endif %}
{% block extra_head %}{% endblock %}
</head>
<body class="{% block body_class %}{% endblock %}">
{% block content %}{% endblock %}
<script src="{% static 'vendor/htmx.min.js' %}"></script>
<script defer src="{% static 'vendor/alpine.min.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,4 @@
<div class="text-center py-12" hx-get="/client/" hx-trigger="load" hx-target="#empty-action" hx-swap="innerHTML">
<p class="text-neutral-500">暂无数据</p>
<div id="empty-action"></div>
</div>

View File

@@ -0,0 +1,5 @@
<div class="fixed inset-0 z-60 hidden items-center justify-center bg-black/40" id="modal-root" hx-get="/org/" hx-trigger="revealed" hx-target="#modal-body" hx-swap="innerHTML">
<div class="w-full max-w-lg bg-white rounded-lg shadow p-4">
<div id="modal-body">加载中...</div>
</div>
</div>

View File

@@ -0,0 +1,4 @@
<div class="flex items-center justify-between" hx-get="?page=2" hx-trigger="click from:#next-page" hx-target="#table-wrap" hx-swap="outerHTML">
<p class="text-sm text-neutral-600">第 1 页,共 1 页</p>
<button id="next-page" type="button" class="px-3 py-1.5 rounded border border-neutral-300">下一页</button>
</div>

View File

@@ -0,0 +1,7 @@
<nav class="px-2" hx-get="/permission/" hx-trigger="load" hx-target="#sidebar-async" hx-swap="innerHTML">
<ul class="space-y-1">
<li><a href="#" class="block px-3 py-2 rounded hover:bg-neutral-100">总览</a></li>
<li><a href="#" class="block px-3 py-2 rounded hover:bg-neutral-100">我的任务</a></li>
</ul>
<div id="sidebar-async"></div>
</nav>

View File

@@ -0,0 +1,3 @@
<div class="rounded-lg bg-white border border-neutral-200 shadow-xs px-4 py-3 text-sm" role="status">
<p>{{ message|default:"操作完成" }}</p>
</div>

View File

@@ -0,0 +1,4 @@
<div class="mb-4 flex items-center justify-between" hx-get="/setting/" hx-trigger="revealed" hx-target="#topbar-extra" hx-swap="innerHTML">
<h1 class="text-xl font-semibold">工作台</h1>
<div id="topbar-extra"></div>
</div>

View File

@@ -0,0 +1,3 @@
{% extends "base.html" %}
{% block title %}403{% endblock %}
{% block content %}<div class="p-8">403 Forbidden</div>{% endblock %}

View File

@@ -0,0 +1,3 @@
{% extends "base.html" %}
{% block title %}404{% endblock %}
{% block content %}<div class="p-8">404 Not Found</div>{% endblock %}

View File

@@ -0,0 +1,3 @@
{% extends "base.html" %}
{% block title %}500{% endblock %}
{% block content %}<div class="p-8">500 Server Error</div>{% endblock %}

View File

@@ -0,0 +1,69 @@
{% extends "base.html" %}
{% block body_class %}bg-neutral-50 text-neutral-900{% endblock %}
{% block content %}
<div x-data="{ sidebarOpen: $persist(true).as('fonrey.sidebar.open') }" class="min-h-screen">
<header class="sticky top-0 z-20 h-14 bg-primary-800 text-white flex items-center px-4 gap-4">
<div class="w-[150px] font-semibold">Fonrey</div>
<nav class="flex-1 flex items-center gap-2 overflow-x-auto">
<a href="#" class="px-3 py-2 rounded bg-primary-700">主页</a>
<a href="#" class="px-3 py-2 rounded hover:bg-primary-700">房源</a>
<a href="#" class="px-3 py-2 rounded hover:bg-primary-700">客源</a>
<a href="#" class="px-3 py-2 rounded hover:bg-primary-700">营销</a>
<a href="#" class="px-3 py-2 rounded hover:bg-primary-700">交易</a>
<a href="#" class="px-3 py-2 rounded hover:bg-primary-700">数据</a>
<a href="#" class="px-3 py-2 rounded hover:bg-primary-700">人事</a>
<a href="#" class="px-3 py-2 rounded hover:bg-primary-700">系统</a>
<input class="ml-2 h-9 rounded px-3 text-neutral-900 w-64" placeholder="全局搜索">
</nav>
<div class="flex items-center gap-3">
<button type="button" class="p-2 rounded hover:bg-primary-700">🔔</button>
<button type="button" class="p-2 rounded hover:bg-primary-700">⚙️</button>
<button type="button" class="h-8 w-8 rounded-full bg-primary-600">U</button>
</div>
</header>
<aside :class="sidebarOpen ? 'w-60' : 'w-16'" class="fixed top-14 left-0 bottom-0 z-20 bg-white border-r border-neutral-200 transition-all">
<div class="p-3">
<button type="button" class="text-sm text-primary-600" @click="sidebarOpen = !sidebarOpen">切换侧栏</button>
</div>
{% include "components/sidebar.html" %}
</aside>
<main :class="sidebarOpen ? 'ml-60' : 'ml-16'" class="px-6 py-4 transition-all">
{% include "components/topbar.html" %}
<section hx-get="/property/" hx-trigger="load" hx-target="#page-async" hx-swap="innerHTML">
<div id="page-async">
{% block app_content %}{% endblock %}
</div>
</section>
</main>
<div id="toast-container" class="fixed bottom-4 right-4 z-70 space-y-2"></div>
</div>
<div id="desktop-only-gate" class="hidden fixed inset-0 z-70 bg-neutral-900/80 text-white items-center justify-center p-6">
<div class="max-w-lg text-center text-lg font-medium">Fonrey 当前仅支持桌面端≥1280px请在电脑上访问</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
(function () {
function toggleGate() {
var gate = document.getElementById('desktop-only-gate');
if (!gate) return;
if (window.innerWidth < 1280) {
gate.classList.remove('hidden');
gate.classList.add('flex');
} else {
gate.classList.add('hidden');
gate.classList.remove('flex');
}
}
toggleGate();
window.addEventListener('resize', toggleGate);
})();
</script>
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends "base.html" %}
{% block body_class %}min-h-screen bg-neutral-50{% endblock %}
{% block content %}
<div class="min-h-screen flex items-center justify-center px-4">
<div class="w-full max-w-md bg-white shadow-xs rounded-lg p-6" hx-get="/account/placeholder/" hx-trigger="load" hx-target="#auth-async" hx-swap="innerHTML">
<div id="auth-async">
{% block auth_content %}{% endblock %}
</div>
</div>
</div>
{% endblock %}