From c90b10cd5a980b51c3a6b1885512381d247d5da8 Mon Sep 17 00:00:00 2001
From: flowerstonezl <125468358+flowerstonezl@users.noreply.github.com>
Date: Mon, 11 May 2026 19:34:35 +0800
Subject: [PATCH] first commit
---
.gitignore | 10 +
backend/.dockerignore | 7 +
backend/Dockerfile | 25 +
backend/docker-entrypoint.sh | 6 +
backend/journal/__init__.py | 0
backend/journal/admin.py | 196 ++
backend/journal/apps.py | 6 +
backend/journal/management/__init__.py | 0
.../journal/management/commands/__init__.py | 0
.../management/commands/seed_nepal_story.py | 85 +
backend/journal/migrations/0001_initial.py | 56 +
.../0002_expand_nepal_art_style_length.py | 18 +
...config_together_netease_unlock_nullable.py | 33 +
.../0004_netease_player_textfield.py | 18 +
.../0005_remove_netease_from_config.py | 22 +
.../journal/migrations/0006_memory_photo.py | 28 +
backend/journal/migrations/__init__.py | 0
backend/journal/models.py | 143 ++
backend/journal/serializers.py | 51 +
backend/journal/tests.py | 3 +
backend/journal/url | 4 +
backend/journal/urls.py | 9 +
backend/journal/views.py | 69 +
backend/manage.py | 22 +
backend/ourstory/__init__.py | 0
backend/ourstory/asgi.py | 16 +
backend/ourstory/settings.py | 139 +
backend/ourstory/urls.py | 14 +
backend/ourstory/wsgi.py | 16 +
backend/requirements.txt | 6 +
docker-compose.yml | 27 +
frontend/.dockerignore | 3 +
frontend/.env.development | 5 +
frontend/.gitignore | 24 +
frontend/.vscode/extensions.json | 3 +
frontend/Dockerfile | 17 +
frontend/README.md | 5 +
frontend/index.html | 20 +
frontend/nginx.conf | 31 +
frontend/package-lock.json | 2263 +++++++++++++++++
frontend/package.json | 34 +
frontend/public/audio/.gitkeep | 0
frontend/public/favicon.svg | 1 +
frontend/public/icons.svg | 24 +
frontend/public/lottie/heart.json | 73 +
frontend/src/App.vue | 7 +
frontend/src/api/client.ts | 77 +
frontend/src/assets/hero.png | Bin 0 -> 13057 bytes
frontend/src/assets/vite.svg | 1 +
frontend/src/assets/vue.svg | 1 +
frontend/src/components/FissurePage.vue | 112 +
.../src/components/HeartMangaBackdrop.vue | 25 +
frontend/src/components/HeartWindowPage.vue | 177 ++
frontend/src/components/LottieSticker.vue | 64 +
frontend/src/components/MessageForm.vue | 93 +
frontend/src/components/NepalChapterBand.vue | 22 +
frontend/src/components/NepalHero.vue | 46 +
.../src/components/NepalPageContainer.vue | 327 +++
frontend/src/components/NeteaseEmbed.vue | 44 +
frontend/src/components/PolaroidCard.vue | 209 ++
frontend/src/components/StandardNepalPage.vue | 87 +
frontend/src/components/Timeline.vue | 70 +
frontend/src/components/TogetherCounter.vue | 174 ++
frontend/src/components/WashiTape.vue | 28 +
.../standardScenes/ArgueAnimeScene.vue | 20 +
.../standardScenes/BlueAnimeScene.vue | 24 +
.../standardScenes/DawnAnimeScene.vue | 25 +
.../standardScenes/GoldAnimeScene.vue | 27 +
.../standardScenes/HomeAnimeScene.vue | 20 +
.../standardScenes/IkeaAnimeScene.vue | 79 +
.../standardScenes/LotusAnimeScene.vue | 29 +
.../standardScenes/MangaSceneRouter.vue | 43 +
.../standardScenes/MintAnimeScene.vue | 24 +
.../standardScenes/NightAnimeScene.vue | 23 +
.../standardScenes/PaperAnimeScene.vue | 11 +
.../standardScenes/RoseAnimeScene.vue | 24 +
frontend/src/config/neteasePlayer.ts | 17 +
frontend/src/constants/nepalThemes.ts | 82 +
frontend/src/main.ts | 5 +
frontend/src/style.css | 171 ++
frontend/src/utils/renderMarkdown.ts | 38 +
frontend/src/utils/storyPageTransition.ts | 100 +
frontend/src/utils/togetherDuration.ts | 48 +
frontend/src/views/OurStory.vue | 163 ++
frontend/src/vite-env.d.ts | 9 +
frontend/tsconfig.app.json | 14 +
frontend/tsconfig.json | 7 +
frontend/tsconfig.node.json | 24 +
frontend/vite.config.ts | 19 +
89 files changed, 6142 insertions(+)
create mode 100644 .gitignore
create mode 100644 backend/.dockerignore
create mode 100644 backend/Dockerfile
create mode 100644 backend/docker-entrypoint.sh
create mode 100644 backend/journal/__init__.py
create mode 100644 backend/journal/admin.py
create mode 100644 backend/journal/apps.py
create mode 100644 backend/journal/management/__init__.py
create mode 100644 backend/journal/management/commands/__init__.py
create mode 100644 backend/journal/management/commands/seed_nepal_story.py
create mode 100644 backend/journal/migrations/0001_initial.py
create mode 100644 backend/journal/migrations/0002_expand_nepal_art_style_length.py
create mode 100644 backend/journal/migrations/0003_config_together_netease_unlock_nullable.py
create mode 100644 backend/journal/migrations/0004_netease_player_textfield.py
create mode 100644 backend/journal/migrations/0005_remove_netease_from_config.py
create mode 100644 backend/journal/migrations/0006_memory_photo.py
create mode 100644 backend/journal/migrations/__init__.py
create mode 100644 backend/journal/models.py
create mode 100644 backend/journal/serializers.py
create mode 100644 backend/journal/tests.py
create mode 100644 backend/journal/url
create mode 100644 backend/journal/urls.py
create mode 100644 backend/journal/views.py
create mode 100644 backend/manage.py
create mode 100644 backend/ourstory/__init__.py
create mode 100644 backend/ourstory/asgi.py
create mode 100644 backend/ourstory/settings.py
create mode 100644 backend/ourstory/urls.py
create mode 100644 backend/ourstory/wsgi.py
create mode 100644 backend/requirements.txt
create mode 100644 docker-compose.yml
create mode 100644 frontend/.dockerignore
create mode 100644 frontend/.env.development
create mode 100644 frontend/.gitignore
create mode 100644 frontend/.vscode/extensions.json
create mode 100644 frontend/Dockerfile
create mode 100644 frontend/README.md
create mode 100644 frontend/index.html
create mode 100644 frontend/nginx.conf
create mode 100644 frontend/package-lock.json
create mode 100644 frontend/package.json
create mode 100644 frontend/public/audio/.gitkeep
create mode 100644 frontend/public/favicon.svg
create mode 100644 frontend/public/icons.svg
create mode 100644 frontend/public/lottie/heart.json
create mode 100644 frontend/src/App.vue
create mode 100644 frontend/src/api/client.ts
create mode 100644 frontend/src/assets/hero.png
create mode 100644 frontend/src/assets/vite.svg
create mode 100644 frontend/src/assets/vue.svg
create mode 100644 frontend/src/components/FissurePage.vue
create mode 100644 frontend/src/components/HeartMangaBackdrop.vue
create mode 100644 frontend/src/components/HeartWindowPage.vue
create mode 100644 frontend/src/components/LottieSticker.vue
create mode 100644 frontend/src/components/MessageForm.vue
create mode 100644 frontend/src/components/NepalChapterBand.vue
create mode 100644 frontend/src/components/NepalHero.vue
create mode 100644 frontend/src/components/NepalPageContainer.vue
create mode 100644 frontend/src/components/NeteaseEmbed.vue
create mode 100644 frontend/src/components/PolaroidCard.vue
create mode 100644 frontend/src/components/StandardNepalPage.vue
create mode 100644 frontend/src/components/Timeline.vue
create mode 100644 frontend/src/components/TogetherCounter.vue
create mode 100644 frontend/src/components/WashiTape.vue
create mode 100644 frontend/src/components/standardScenes/ArgueAnimeScene.vue
create mode 100644 frontend/src/components/standardScenes/BlueAnimeScene.vue
create mode 100644 frontend/src/components/standardScenes/DawnAnimeScene.vue
create mode 100644 frontend/src/components/standardScenes/GoldAnimeScene.vue
create mode 100644 frontend/src/components/standardScenes/HomeAnimeScene.vue
create mode 100644 frontend/src/components/standardScenes/IkeaAnimeScene.vue
create mode 100644 frontend/src/components/standardScenes/LotusAnimeScene.vue
create mode 100644 frontend/src/components/standardScenes/MangaSceneRouter.vue
create mode 100644 frontend/src/components/standardScenes/MintAnimeScene.vue
create mode 100644 frontend/src/components/standardScenes/NightAnimeScene.vue
create mode 100644 frontend/src/components/standardScenes/PaperAnimeScene.vue
create mode 100644 frontend/src/components/standardScenes/RoseAnimeScene.vue
create mode 100644 frontend/src/config/neteasePlayer.ts
create mode 100644 frontend/src/constants/nepalThemes.ts
create mode 100644 frontend/src/main.ts
create mode 100644 frontend/src/style.css
create mode 100644 frontend/src/utils/renderMarkdown.ts
create mode 100644 frontend/src/utils/storyPageTransition.ts
create mode 100644 frontend/src/utils/togetherDuration.ts
create mode 100644 frontend/src/views/OurStory.vue
create mode 100644 frontend/src/vite-env.d.ts
create mode 100644 frontend/tsconfig.app.json
create mode 100644 frontend/tsconfig.json
create mode 100644 frontend/tsconfig.node.json
create mode 100644 frontend/vite.config.ts
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ab23fa8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+backend/.venv/
+backend/__pycache__/
+backend/**/__pycache__/
+backend/db.sqlite3
+backend/media/
+backend/staticfiles/
+frontend/node_modules/
+frontend/dist/
+*.log
+.DS_Store
diff --git a/backend/.dockerignore b/backend/.dockerignore
new file mode 100644
index 0000000..e6e22c3
--- /dev/null
+++ b/backend/.dockerignore
@@ -0,0 +1,7 @@
+.venv
+__pycache__
+*.py[cod]
+db.sqlite3
+media
+staticfiles
+.git
diff --git a/backend/Dockerfile b/backend/Dockerfile
new file mode 100644
index 0000000..ab72762
--- /dev/null
+++ b/backend/Dockerfile
@@ -0,0 +1,25 @@
+FROM python:3.12-slim-bookworm
+
+WORKDIR /app
+
+ENV PYTHONDONTWRITEBYTECODE=1
+ENV PYTHONUNBUFFERED=1
+ENV DJANGO_DEBUG=false
+ENV SQLITE_PATH=/data/db.sqlite3
+ENV MEDIA_ROOT=/data/media
+ENV DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,backend
+ENV CORS_ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends libjpeg62-turbo zlib1g \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY . .
+RUN chmod +x /app/docker-entrypoint.sh
+
+EXPOSE 8000
+
+ENTRYPOINT ["/app/docker-entrypoint.sh"]
diff --git a/backend/docker-entrypoint.sh b/backend/docker-entrypoint.sh
new file mode 100644
index 0000000..f3abdb0
--- /dev/null
+++ b/backend/docker-entrypoint.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+set -e
+mkdir -p "$(dirname "${SQLITE_PATH:-/data/db.sqlite3}")" "${MEDIA_ROOT:-/data/media}"
+python manage.py migrate --noinput
+python manage.py collectstatic --noinput
+exec gunicorn ourstory.wsgi:application --bind 0.0.0.0:8000 --workers 2
diff --git a/backend/journal/__init__.py b/backend/journal/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/journal/admin.py b/backend/journal/admin.py
new file mode 100644
index 0000000..79d9121
--- /dev/null
+++ b/backend/journal/admin.py
@@ -0,0 +1,196 @@
+from django.contrib import admin
+from django.utils.html import format_html
+from django.utils.safestring import mark_safe
+
+from .models import Config, Guestbook, Memory, MemoryPhoto
+
+
+class JournalStaffAccessMixin:
+ """
+ 手账站点多为小项目:凡 is_staff 即可在后台增删改查(无需在「用户」里逐项勾选 journal.* 权限)。
+ 生产环境若需细分权限,可移除此 Mixin 并改回 Django 默认的 has_perm 校验。
+ """
+
+ def has_module_permission(self, request):
+ u = request.user
+ return bool(getattr(u, "is_active", False) and getattr(u, "is_staff", False))
+
+ def has_view_permission(self, request, obj=None):
+ u = request.user
+ return bool(getattr(u, "is_active", False) and getattr(u, "is_staff", False))
+
+ def has_change_permission(self, request, obj=None):
+ u = request.user
+ return bool(getattr(u, "is_active", False) and getattr(u, "is_staff", False))
+
+ def has_add_permission(self, request):
+ u = request.user
+ return bool(getattr(u, "is_active", False) and getattr(u, "is_staff", False))
+
+ def has_delete_permission(self, request, obj=None):
+ u = request.user
+ return bool(getattr(u, "is_active", False) and getattr(u, "is_staff", False))
+
+
+class MemoryPhotoInline(admin.TabularInline):
+ model = MemoryPhoto
+ extra = 1
+ fields = ("image", "sort_order")
+
+
+@admin.register(Memory)
+class MemoryAdmin(JournalStaffAccessMixin, admin.ModelAdmin):
+ inlines = (MemoryPhotoInline,)
+ list_display = (
+ "event_title",
+ "story_order",
+ "date",
+ "unique_feature_type",
+ "nepal_art_style",
+ "is_finale",
+ "thumb_preview",
+ )
+ list_display_links = ("event_title",)
+ list_filter = ("unique_feature_type", "nepal_art_style", "is_finale", "date")
+ list_editable = ("story_order",)
+ search_fields = ("event_title", "main_text")
+ readonly_fields = ("image_preview",)
+ ordering = ("story_order", "id")
+
+ fieldsets = (
+ (
+ "叙事",
+ {
+ "fields": (
+ "story_order",
+ "date",
+ "event_title",
+ "is_finale",
+ )
+ },
+ ),
+ (
+ "版式",
+ {
+ "fields": (
+ "unique_feature_type",
+ "nepal_art_style",
+ "image",
+ "image_preview",
+ "main_text",
+ )
+ },
+ ),
+ )
+
+ @admin.display(description="缩略图")
+ def thumb_preview(self, obj: Memory) -> str:
+ url = None
+ if obj.image:
+ url = obj.image.url
+ else:
+ first = obj.photos.order_by("sort_order", "id").first()
+ if first:
+ url = first.image.url
+ if not url:
+ return "—"
+ return format_html(
+ '',
+ url,
+ )
+
+ @admin.display(description="配图预览")
+ def image_preview(self, obj: Memory) -> str:
+ chunks: list[str] = []
+ if obj.image:
+ chunks.append(
+ format_html(
+ '