From 1adf71248fec5fbaea4617937be3c2da21bbb9fa Mon Sep 17 00:00:00 2001 From: toom1996 <23cm.cn@gmail.com> Date: Thu, 22 Oct 2026 10:36:49 +0800 Subject: [PATCH] update --- package.json | 1 + scripts/local-proxy.mjs | 57 ++++++++ src/components/ComingSoon.astro | 22 +-- src/components/views/Login.astro | 237 ++++++++++++++++++++++--------- src/layouts/Layout.astro | 93 ++++++------ src/lib/i18n/dictionary.ts | 33 ++++- 6 files changed, 319 insertions(+), 124 deletions(-) create mode 100644 scripts/local-proxy.mjs diff --git a/package.json b/package.json index aae594c..8748e43 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dev": "astro dev", "build": "astro build", "preview": "astro preview", + "preview:full": "node scripts/local-proxy.mjs", "check": "astro check", "astro": "astro" }, diff --git a/scripts/local-proxy.mjs b/scripts/local-proxy.mjs new file mode 100644 index 0000000..5c85d37 --- /dev/null +++ b/scripts/local-proxy.mjs @@ -0,0 +1,57 @@ +// 本地预览用的极简代理:模拟生产里 proxy(nginx) 的分流。 +// +// /api/* → 后端(默认 127.0.0.1:8090) +// 其余 → astro preview(默认 127.0.0.1:4322) +// +// 为什么需要它:生产环境里浏览器只访问一个域名,/api 由 nginx 反代到后端; +// 本地没有 nginx,astro preview 只跑 Astro 自己的路由,/api 会 404。 +// 这个脚本让本地的访问方式和生产一致(同源),不产生 CORS,也不需要改任何业务配置。 +// +// 用法: +// node scripts/local-proxy.mjs # 默认 http://localhost:8099 +// PROXY_PORT=9000 node scripts/local-proxy.mjs +// (先启动 astro preview,再启动它) +import http from 'node:http' + +const PORT = Number(process.env.PROXY_PORT || 8099) +const WEB_ORIGIN = process.env.WEB_ORIGIN || 'http://127.0.0.1:4322' +const API_ORIGIN = process.env.API_ORIGIN || 'http://127.0.0.1:8090' + +const server = http.createServer((req, res) => { + const url = req.url || '/' + const isApi = url === '/api' || url.startsWith('/api/') + const target = new URL(url, isApi ? API_ORIGIN : WEB_ORIGIN) + + const proxyReq = http.request( + { + protocol: target.protocol, + hostname: target.hostname, + port: target.port, + path: target.pathname + target.search, + method: req.method, + headers: { ...req.headers, host: target.host }, + }, + (proxyRes) => { + res.writeHead(proxyRes.statusCode || 502, proxyRes.headers) + proxyRes.pipe(res) + } + ) + + proxyReq.on('error', (err) => { + // 后端没起 / preview 没起时给出明确提示,而不是挂住 + res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' }) + res.end( + `local-proxy: 连不上 ${isApi ? API_ORIGIN : WEB_ORIGIN}\n` + + `${err.message}\n\n` + + (isApi ? '先把后端跑起来(8090)\n' : '先跑 npm run preview(4322)\n') + ) + }) + + req.pipe(proxyReq) +}) + +server.listen(PORT, () => { + console.log(`[local-proxy] http://localhost:${PORT}`) + console.log(` /api/* → ${API_ORIGIN}`) + console.log(` 其余 → ${WEB_ORIGIN}`) +}) diff --git a/src/components/ComingSoon.astro b/src/components/ComingSoon.astro index 3f055ad..660bd70 100644 --- a/src/components/ComingSoon.astro +++ b/src/components/ComingSoon.astro @@ -7,23 +7,27 @@ interface Props { moduleZh?: string note?: string } -const { module = "This module", moduleZh = "", note = "" } = Astro.props -const { locale } = getI18n(Astro) +const { module = "", moduleZh = "", note = "" } = Astro.props +const { locale, t } = getI18n(Astro) + +// 原实现整段是硬编码中文,英文站会直接显示中文。这里全部走词典, +// 模块名按语言取:中文优先 moduleZh,英文只取 module。 +const moduleLabel = locale === 'cn' ? moduleZh || module : module || moduleZh ---
- Coming Soon - 暂未上线 + {t('Coming Soon')} + {t('Archive')}
-

暂未上线

-

{module}{moduleZh && ` · ${moduleZh}`}

+

{t('Coming Soon')}

+

{moduleLabel}

- {note || "该模块正在筹备中,尚未对外开放。站点正按模块逐个完善,你可以先浏览已上线的走秀档案。"} + {note || t('This module is under construction and not open to the public yet. Browse the runway archive first.')}

- 浏览走秀档案 → - 返回首页 + {t('Browse runway archive →')} + {t('Back to home')}
diff --git a/src/components/views/Login.astro b/src/components/views/Login.astro index e681b9a..bcb092f 100644 --- a/src/components/views/Login.astro +++ b/src/components/views/Login.astro @@ -1,12 +1,17 @@ --- // 登录页的 en/cn 共享实现(语言由 Astro.currentLocale 运行时决定)。 // 对应页面:src/pages/{en,cn}/login.astro —— 仅做薄包装。 -// 注意:本页**不走 Layout**,自带完整 文档(独立极简版式,故保留 )。 +// +// 设计方向:把登录页当成「一期杂志的封面页」——整幅满血封面 + 竖排刊头 + 套准标记 + +// 极端字距的巨标题。延续全站的黑白 / 直角 / 粗线语言,但把张力拉满,避免"白底表单"的平庸。 +// 本页**不走 Layout**,自带完整 文档。 import '@/styles/global.css' import { getI18n } from '@/lib/i18n' import { href, ROUTES } from '@/lib/routes' import { config } from '@/lib/config' import { LOCALE_NAMES } from '@/lib/i18n' +import { getSsgIndexRunway, getSsgStreetSnapPopular } from '@/lib/ssg' +import { toAbs } from '@/lib/request' import { i18n } from 'astro:config/client'; import { toCodes } from 'astro:i18n'; @@ -24,7 +29,6 @@ const promptLabel = t('login prompt') const accountLabel = t('username or email') const passwordLabel = t('password') const failLabel = t('login failed') -const membersLabel = t('members') const backLabel = t('back to site') const homeUrl = href(locale, ROUTES.runwayLooks) const SITE_NAME = config.siteName ?? "Xisoa" @@ -35,7 +39,19 @@ const loginDesc = locale === 'cn' const loginKeywords = locale === 'cn' ? `登录,账户,${SITE_NAME}` : `login,account,${SITE_NAME}` + +// 封面区:库里原图统一是 240×360(爬虫入库下的是缩略图), +// 放大成整屏必然糊,所以改用「接触印样」版式——多张图按原始尺寸铺成网格, +// 既不会拉伸,也符合时尚档案的视觉习惯。取不到图则降级为纯黑排版块。 +const showCovers = (await getSsgIndexRunway()).hotBrand + .filter((b) => b.cover) + .map((b) => toAbs(b.cover)) +const snapCovers = (await getSsgStreetSnapPopular(8)) + .filter((s) => s.cover) + .map((s) => toAbs(s.cover)) +const sheet = [...new Set([...showCovers, ...snapCovers])].slice(0, 6) --- + @@ -45,96 +61,187 @@ const loginKeywords = locale === 'cn' {pageTitle} + - -
+
- -
- + +
+ {backLabel} -
- -
    - {languages.map((l) => ( -
  • - {l.label} -
  • - ))} -
+
+ + +
+ +
    + {languages.map((l) => ( +
  • + {l.label} +
  • + ))} +
+
- -
-
-
-

{membersLabel}

-

{loginLabel}

+ +
+ + +
+ - -
+ +
+ -
-

{promptLabel}

+

+ {t('members only')} +

-
-
- - +

+ {loginLabel} +

+ +

{promptLabel}

+ + +
+ 01 +
+ + +
-
- - +
+ 02 +
+ + +
-

+

- +
+
- - - + + - {/* Top Footer Grid */} -
- {/* Column 1: Brand & Manifesto */} -
-
- + {/* 页脚主体:黑底反白,作为整页收尾(沿用全站直角 / 粗线 / 宽字距语言) */} +
+
+ + {/* 品牌 */} +
+

{t('Archive')}

+

{SITE_NAME} - - - 2026 - +

+

+ {t('Minimalist editorial portfolio documenting haute couture shows, backstage fittings, and street culture. Integrated with 2026 color forecast intelligence and AI palette generator.')} +

+
+ Paris·Milan·Tokyo·New York +
-

- {t('Minimalist editorial portfolio documenting haute couture shows, backstage fittings, and street culture. Integrated with 2026 color forecast intelligence and AI palette generator.')} -

+ {/* 导航 */} + - {/* Location Badge */} -
- Paris - • - Milan - • - Tokyo - • - New York + {/* 站点操作(全部是真实可用的入口,不留死链) */} +
+

{t('Site')}

+
- {/* Column 2: Explore */} -
-

Explore

- + {/* 底部条 */} +
+

© 2026 {SITE_NAME}. {t('all rights reserved.')}

+

{t('Runway · Street · Archive')}

- - {/* Column 3: Connect */} -
-

Connect

- -
-
- -
- © 2026 {SITE_NAME}. {t('all rights reserved.')}
diff --git a/src/lib/i18n/dictionary.ts b/src/lib/i18n/dictionary.ts index a2fc4a5..2271977 100644 --- a/src/lib/i18n/dictionary.ts +++ b/src/lib/i18n/dictionary.ts @@ -21,6 +21,29 @@ export const dict: Record = { 'loading': { cn: '加载中' }, + // ── 通用文案 / 页脚 / 登录页 ── + // ⚠️ 必须全小写:translate() 用 dict[key.toLowerCase()] 查表, + // 带大写字母的 key 永远命中不了,会静默回退英文。 + 'page not found': { cn: '页面不存在' }, + 'coming soon': { cn: '即将上线' }, + 'archive': { cn: '档案' }, + 'archive note': { cn: '档案说明' }, + 'explore': { cn: '探索' }, + 'site': { cn: '站点' }, + 'back to top ↑': { cn: '返回顶部 ↑' }, + 'signing in…': { cn: '登录中…' }, + 'runway · street · archive': { cn: '秀场 · 街拍 · 档案' }, + 'paris · milan · tokyo': { cn: '巴黎 · 米兰 · 东京' }, + 'this module is under construction and not open to the public yet. browse the runway archive first.': + { cn: '该模块正在筹备中,尚未对外开放。你可以先浏览已上线的走秀档案。' }, + 'browse runway archive →': { cn: '浏览走秀档案 →' }, + 'back to home': { cn: '返回首页' }, + 'enter the archive →': { cn: '进入档案 →' }, + 'members only': { cn: '仅限会员' }, + 'fashion archive': { cn: '时尚档案' }, + 'forgot password?': { cn: '忘记密码?' }, + 'ask the editor': { cn: '联系管理员' }, + // ── 导航 / Navigation ── 'home': { cn: '首页' }, 'login': { cn: '登录' }, @@ -95,11 +118,11 @@ export const dict: Record = { 'no street snap id specified': { cn: '未指定街拍 ID' }, 'failed to load street snap. please refresh or try later.': { cn: '加载街拍失败,请刷新或稍后再试' }, 'brands': { cn: '品牌' }, - 'Year': { cn: '年份' }, - 'Sort': { cn: '排序' }, - 'Most photos': { cn: '照片最多' }, - 'Newest year': { cn: '年份最新' }, - 'Oldest year': { cn: '年份最早' }, + 'year': { cn: '年份' }, + 'sort': { cn: '排序' }, + 'most photos': { cn: '照片最多' }, + 'newest year': { cn: '年份最新' }, + 'oldest year': { cn: '年份最早' }, // ── 地区 / Cities(街拍按城市分类,值对齐后端 street_snap.city 列;translate 会小写化 key)── 'city': { cn: '地区' },