auto-save 2026-05-15 17:11 (~6)
This commit is contained in:
@@ -1,12 +1,5 @@
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"files_changed": 8,
|
||||
"hash": "8c6ee1d",
|
||||
"message": "auto-save 2026-05-14 01:45 (+6, ~2)",
|
||||
"ts": "2026-05-14T01:46:08+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 2,
|
||||
"message": "启动 Codex 接力会话 · 已载入 Claude / Codex 最近会话,等待下一条指令 · 分支 HEAD · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 01:45 (+6, ~2)",
|
||||
@@ -3251,6 +3244,13 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 4 项未提交变更 · 最近提交:auto-save 2026-05-15 17:00 (~3)",
|
||||
"files_changed": 4
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-15T17:06:22+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-15 17:06 (+1, ~4)",
|
||||
"hash": "f3230ff",
|
||||
"files_changed": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
2
RULES.md
2
RULES.md
@@ -21,7 +21,7 @@
|
||||
- 管理后台:待定
|
||||
- 服务器目录:`/opt/skg-marketing-studio`
|
||||
- 生产启动:`docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d --build`
|
||||
- 生产架构:`web` 容器用 Nginx 承载 Next 静态导出;未登录访问工作台跳转 `/login/`,`/api/` 通过 Nginx `auth_request` 校验 FastAPI 会话 Cookie 后反代到 `skg-marketing-api:4291`;Traefik 通过 `coolify` 外部网络接入 80/443
|
||||
- 生产架构:`web` 容器用 Nginx 承载 Next 静态导出;`/login/`、`/_next/`、`/assets/` 等登录页必需静态资源公开访问;未登录访问工作台跳转 `/login/`,`/api/` 通过 Nginx `auth_request` 校验 FastAPI 会话 Cookie 后反代到 `skg-marketing-api:4291`;Traefik 通过 `coolify` 外部网络接入 80/443
|
||||
- 持久化目录:服务器 `./data/jobs` 挂载到后端 `/data/jobs`
|
||||
- 登录凭证:用户名写下方快捷登录;密码明文备份只放服务器 `/root/skg-marketing-studio-login.txt`,生产环境变量 `WEB_AUTH_PASSWORD` / `WEB_AUTH_SESSION_SECRET` 只放服务器 `deploy/.env.production`
|
||||
|
||||
|
||||
@@ -87,6 +87,11 @@ server {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location /assets/ {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ~* ^/(icon|apple-icon|favicon|manifest|placeholder).* {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri =404;
|
||||
|
||||
@@ -536,7 +536,7 @@
|
||||
<tr>
|
||||
<td>生产站点</td>
|
||||
<td><code>https://marketing.skg.com</code></td>
|
||||
<td>公司域名已解析到 VPS <code>76.13.31.179</code>。线上由既有 Coolify / Traefik 负责 HTTPS 入口,项目 <code>web</code> 容器用 Nginx 承载静态前端;未登录访问工作台跳转 <code>/login/</code>,<code>/api/</code> 通过 <code>auth_request</code> 校验 FastAPI 会话 Cookie 后再反代。</td>
|
||||
<td>公司域名已解析到 VPS <code>76.13.31.179</code>。线上由既有 Coolify / Traefik 负责 HTTPS 入口,项目 <code>web</code> 容器用 Nginx 承载静态前端;<code>/login/</code>、<code>/_next/</code>、<code>/assets/</code> 为公开登录页资源,未登录访问工作台跳转 <code>/login/</code>,<code>/api/</code> 通过 <code>auth_request</code> 校验 FastAPI 会话 Cookie 后再反代。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>生产部署</td>
|
||||
@@ -591,8 +591,8 @@
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td><code>web/app/page.tsx</code></td><td>产品工作台主状态:jobs、activeJobId、按 job 隔离的 selectedFrames/详情面板状态、clipboard、ReactFlow 节点和边;负责打开/找回画布工作面板。</td></tr>
|
||||
<tr><td><code>web/app/login/page.tsx</code></td><td>生产登录页:账号密码表单、保持登录、错误/成功状态;左侧展示区改为高级产品入口页结构,使用本地 SKG 颈部按摩仪产品图、黑白/香槟金视觉、Secure Studio 胶囊和素材/声音/成片状态栏。</td></tr>
|
||||
<tr><td><code>web/components/login/animated-login-characters.tsx</code></td><td>登录页旧版四个几何角色组件:仍保留在代码中作为历史/备用组件,但当前生产登录页主视觉已不再挂载。</td></tr>
|
||||
<tr><td><code>web/app/login/page.tsx</code></td><td>生产登录页:账号密码表单、保持登录、错误/成功状态;左侧展示区改为高级产品入口页结构,使用本地 SKG 颈部按摩仪产品图、黑白/香槟金视觉、Secure Studio 胶囊和素材/声音/成片状态栏;同时保留动态角色作为 Live Studio Modules 小组件。</td></tr>
|
||||
<tr><td><code>web/components/login/animated-login-characters.tsx</code></td><td>登录页四个几何动态角色组件:当前以小型 Live Studio Modules 方式挂在产品区,保留鼠标眼神跟随、输入、显示密码、错误和成功状态反馈。</td></tr>
|
||||
<tr><td><code>web/components/nodes/index.tsx</code></td><td>DAG 节点定义:Input、VisualLab、Audio、Compose,以及画布工作面板 KeyframePanel / VideoFramePanel;旧 Keyframe/Storyboard/VideoGen 组件保留但不再挂主画布。</td></tr>
|
||||
<tr><td><code>web/components/audio-strip.tsx</code></td><td>底部吸附音频条:可拖拽调整高度;播放原音频时移动指针,逐个高亮英文/中文字幕节点和对应波形,并在右侧固定显示按原音频时长生成的 SKG 英文产品口播和 MiniMax 随机英文配音。</td></tr>
|
||||
<tr><td><code>web/components/lightbox.tsx</code></td><td>关键帧素材准备面板:清洗、统一主体候选、参考帧网格、六张主体重绘图、每帧去主体场景图、纵向 6 行产品融合镜头工作表和审核。</td></tr>
|
||||
@@ -949,8 +949,8 @@ SubjectAsset {
|
||||
</header>
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>上一版虽然吸收了官网元素,但仍偏浅灰卡片拼接,卡通角色舞台削弱了公司级工具和高端健康硬件的质感。</p>
|
||||
<p><strong>改动:</strong>生产登录页主视觉改为真实 SKG 颈部按摩仪产品摄影卡,采用黑白/香槟金主轴、超大标题、克制胶囊状态、素材/声音/成片三段式状态栏;几何角色组件不再作为当前登录页主视觉挂载。新增本地静态图 <code>web/public/assets/skg-g7-pro-neck-massager.png</code>,避免远程官网图在生产登录页破图。</p>
|
||||
<p><strong>影响:</strong><code>web/app/login/page.tsx</code>、<code>web/app/globals.css</code>、<code>web/public/assets/skg-g7-pro-neck-massager.png</code>、<code>docs/source-analysis.html</code>。</p>
|
||||
<p><strong>改动:</strong>生产登录页主视觉改为真实 SKG 颈部按摩仪产品摄影卡,采用黑白/香槟金主轴、超大标题、克制胶囊状态、素材/声音/成片三段式状态栏;几何角色组件不再抢占主视觉,但作为 <code>Live Studio Modules</code> 小组件保留,继续响应鼠标眼神跟随、输入、显示密码、错误和成功状态。新增本地静态图 <code>web/public/assets/skg-g7-pro-neck-massager.png</code>,并在生产 Nginx 放行 <code>/assets/</code>,避免登录页产品图被登录保护拦截。</p>
|
||||
<p><strong>影响:</strong><code>web/app/login/page.tsx</code>、<code>web/app/globals.css</code>、<code>web/public/assets/skg-g7-pro-neck-massager.png</code>、<code>deploy/nginx.conf</code>、<code>RULES.md</code>、<code>docs/source-analysis.html</code>。</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="change">
|
||||
|
||||
@@ -296,6 +296,50 @@
|
||||
font-weight: 600;
|
||||
line-height: 1.05;
|
||||
}
|
||||
.login-dynamic-dock {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 118px;
|
||||
width: 252px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.72);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.64);
|
||||
padding: 12px;
|
||||
box-shadow: 0 22px 52px rgba(40, 40, 40, 0.12);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
.login-dynamic-dock__label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: rgba(40, 40, 40, 0.46);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.login-dynamic-dock .login-character-stage {
|
||||
min-height: 116px;
|
||||
border-color: rgba(40, 40, 40, 0.08);
|
||||
background:
|
||||
linear-gradient(90deg, rgba(40, 40, 40, 0.04) 1px, transparent 1px),
|
||||
linear-gradient(180deg, rgba(40, 40, 40, 0.04) 1px, transparent 1px),
|
||||
rgba(255, 255, 255, 0.5);
|
||||
background-size: 22px 22px, 22px 22px, auto;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.84);
|
||||
}
|
||||
.login-dynamic-dock .login-character-stage::after {
|
||||
height: 48%;
|
||||
background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.7));
|
||||
}
|
||||
.login-dynamic-dock .login-stage-grid {
|
||||
inset: 12px;
|
||||
border-color: rgba(40, 40, 40, 0.08);
|
||||
}
|
||||
.login-dynamic-dock .login-characters-container {
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
transform: translateX(-50%) scale(0.28);
|
||||
}
|
||||
.login-studio-chip {
|
||||
position: absolute;
|
||||
display: inline-flex;
|
||||
@@ -816,6 +860,18 @@
|
||||
.login-product-caption b {
|
||||
font-size: 22px;
|
||||
}
|
||||
.login-dynamic-dock {
|
||||
left: 0;
|
||||
bottom: 132px;
|
||||
width: 210px;
|
||||
padding: 10px;
|
||||
}
|
||||
.login-dynamic-dock .login-character-stage {
|
||||
min-height: 94px;
|
||||
}
|
||||
.login-dynamic-dock .login-characters-container {
|
||||
transform: translateX(-50%) scale(0.22);
|
||||
}
|
||||
.login-studio-chip {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import type { FormEvent } from "react"
|
||||
import { useState } from "react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import {
|
||||
AlertCircle,
|
||||
ArrowRight,
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Sparkles,
|
||||
UserRound,
|
||||
} from "lucide-react"
|
||||
import { AnimatedLoginCharacters, type LoginCharacterMood } from "@/components/login/animated-login-characters"
|
||||
|
||||
type LoginStatus = "idle" | "loading" | "success"
|
||||
|
||||
@@ -25,11 +26,33 @@ export default function LoginPage() {
|
||||
const [password, setPassword] = useState("")
|
||||
const [remember, setRemember] = useState(true)
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [activeField, setActiveField] = useState<"username" | "password" | null>(null)
|
||||
const [error, setError] = useState("")
|
||||
const [status, setStatus] = useState<LoginStatus>("idle")
|
||||
const [eyeOffset, setEyeOffset] = useState({ x: 0, y: 0 })
|
||||
|
||||
useEffect(() => {
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
const centerX = window.innerWidth / 2
|
||||
const centerY = window.innerHeight / 2
|
||||
const nextX = Math.max(-1, Math.min(1, (event.clientX - centerX) / centerX))
|
||||
const nextY = Math.max(-1, Math.min(1, (event.clientY - centerY) / centerY))
|
||||
setEyeOffset({ x: nextX * 8, y: nextY * 5.5 })
|
||||
}
|
||||
window.addEventListener("pointermove", onPointerMove)
|
||||
return () => window.removeEventListener("pointermove", onPointerMove)
|
||||
}, [])
|
||||
|
||||
const disabled = status === "loading" || status === "success"
|
||||
|
||||
const mood: LoginCharacterMood = useMemo(() => {
|
||||
if (status === "success") return "success"
|
||||
if (error) return "error"
|
||||
if (showPassword && activeField === "password") return "peek"
|
||||
if (activeField || username || password) return "typing"
|
||||
return "idle"
|
||||
}, [activeField, error, password, showPassword, status, username])
|
||||
|
||||
async function onSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
setError("")
|
||||
@@ -95,6 +118,10 @@ export default function LoginPage() {
|
||||
<span>G7 Pro Fold</span>
|
||||
<b>Neck Massager</b>
|
||||
</div>
|
||||
<div className="login-dynamic-dock">
|
||||
<span className="login-dynamic-dock__label">Live Studio Modules</span>
|
||||
<AnimatedLoginCharacters mood={mood} eyeOffset={eyeOffset} />
|
||||
</div>
|
||||
<div className="login-studio-chip login-studio-chip--visual">
|
||||
<Sparkles className="h-4 w-4" />
|
||||
<span>Visual Asset Flow</span>
|
||||
@@ -141,6 +168,8 @@ export default function LoginPage() {
|
||||
disabled={disabled}
|
||||
autoComplete="username"
|
||||
placeholder="请输入账号"
|
||||
onFocus={() => setActiveField("username")}
|
||||
onBlur={() => setActiveField(null)}
|
||||
onChange={(event) => {
|
||||
setUsername(event.target.value)
|
||||
if (error) setError("")
|
||||
@@ -160,6 +189,8 @@ export default function LoginPage() {
|
||||
type={showPassword ? "text" : "password"}
|
||||
autoComplete="current-password"
|
||||
placeholder="请输入密码"
|
||||
onFocus={() => setActiveField("password")}
|
||||
onBlur={() => setActiveField(null)}
|
||||
onChange={(event) => {
|
||||
setPassword(event.target.value)
|
||||
if (error) setError("")
|
||||
|
||||
Reference in New Issue
Block a user