diff --git a/src/app/chat/chat-screen.tsx b/src/app/chat/chat-screen.tsx index eb3f0d8f..a247f6db 100644 --- a/src/app/chat/chat-screen.tsx +++ b/src/app/chat/chat-screen.tsx @@ -2,11 +2,6 @@ /** * ChatScreen 编排层 * - * 职责(本文件): - * - 订阅 auth 状态机(loginStatus)—— 派生 isGuest / 派鉴权生命周期事件给 chat 机器 - * - 屏幕生命周期事件(init / visible / invisible) - * - 不直接管理 WebSocket / 不直接读 AuthStorage(除 token 取值) - * * 子组件职责: * - ChatHeader:顶部栏(游客 banner / 菜单按钮) * - ChatArea:消息列表 diff --git a/src/app/chat/components/chat-send-button.module.css b/src/app/chat/components/chat-send-button.module.css index 0ceb4e4a..20a33e95 100644 --- a/src/app/chat/components/chat-send-button.module.css +++ b/src/app/chat/components/chat-send-button.module.css @@ -2,10 +2,11 @@ .button { flex: 0 0 auto; - width: 48px; + width: 40px; height: 40px; + aspect-ratio: 1 / 1; border: 0; - border-radius: 9999px; + border-radius: 50%; background: var(--color-button-gradient-end, #fc69df); color: #fff; cursor: pointer; diff --git a/src/app/chat/components/chat-send-button.tsx b/src/app/chat/components/chat-send-button.tsx index d2f3fe90..d643b720 100644 --- a/src/app/chat/components/chat-send-button.tsx +++ b/src/app/chat/components/chat-send-button.tsx @@ -8,9 +8,9 @@ * - 默认:粉色背景 + 渐变边缘 * - 聚焦 / 激活:完整 primaryGradient(accent → 透明) * - * 图标:lucide-react (tree-shakable,currentColor 继承父 color) + * 图标:lucide-react (tree-shakable,currentColor 继承父 color) */ -import { ArrowUp, Send } from "lucide-react"; +import { ArrowUp } from "lucide-react"; import styles from "./chat-send-button.module.css"; diff --git a/src/app/chat/components/pwa-install-overlay.tsx b/src/app/chat/components/pwa-install-overlay.tsx index dddfc007..fc9a71a2 100644 --- a/src/app/chat/components/pwa-install-overlay.tsx +++ b/src/app/chat/components/pwa-install-overlay.tsx @@ -14,6 +14,7 @@ */ import { useEffect } from "react"; +import { BrowserDetector } from "@/utils/browser-detect"; import { SpAsyncUtil } from "@/utils/storage"; import { pwaUtil } from "@/utils/pwa"; @@ -32,6 +33,7 @@ export function PwaInstallOverlay() { // isInstalled() 检查 standalone display-mode(避免给已安装用户再弹) if (!pwaUtil.isSupported()) return; if (pwaUtil.isInstalled()) return; + if (BrowserDetector.isInAppBrowser()) return; // 提前挂 beforeinstallprompt 监听,避免用户点 OK 时事件已在更早时机丢失。 pwaUtil.prepareInstallPrompt(); diff --git a/src/app/splash/components/splash-button.module.css b/src/app/splash/components/splash-button.module.css index 6cece395..e24d7c37 100644 --- a/src/app/splash/components/splash-button.module.css +++ b/src/app/splash/components/splash-button.module.css @@ -9,7 +9,6 @@ } .skip { - font-family: var(--font-athelas); font-size: var(--font-size-xxl); font-weight: 700; font-style: italic; @@ -58,7 +57,6 @@ } .facebookLabel { - font-family: var(--font-athelas); font-size: var(--font-size-xxl); font-weight: 700; color: #ffffff; diff --git a/src/app/splash/components/splash-content.tsx b/src/app/splash/components/splash-content.tsx index fdef115d..7dea7435 100644 --- a/src/app/splash/components/splash-content.tsx +++ b/src/app/splash/components/splash-content.tsx @@ -1,12 +1,5 @@ /** * Splash 主内容文案 - * - * 原始 Dart: lib/ui/splash/widgets/splash_content.dart - * - * 关键差异: - * - 使用 Athelas 字体(衬线体) - * - 标题加粗 + 斜体(与原 Dart 一致) - * - 颜色:白色(背景图上需高对比度) */ import styles from "./splash-content.module.css"; diff --git a/src/app/splash/components/splash-logo.module.css b/src/app/splash/components/splash-logo.module.css index 8e9829dd..6abba44b 100644 --- a/src/app/splash/components/splash-logo.module.css +++ b/src/app/splash/components/splash-logo.module.css @@ -7,6 +7,4 @@ display: block; width: auto; height: auto; - /* Apple 平台 Athelas 渲染时仍可能需要 fallback */ - font-family: var(--font-athelas, serif); } diff --git a/src/app/splash/components/splash-screen.module.css b/src/app/splash/components/splash-screen.module.css index d7a611da..84d90537 100644 --- a/src/app/splash/components/splash-screen.module.css +++ b/src/app/splash/components/splash-screen.module.css @@ -41,7 +41,6 @@ .bottom { margin: var(--spacing-xxxl) 0 0 0; - font-family: var(--font-athelas); font-size: var(--font-size-md); font-weight: 400; line-height: 1.5; diff --git a/src/core/fonts.ts b/src/core/fonts.ts index 0807400b..0cdc0a53 100644 --- a/src/core/fonts.ts +++ b/src/core/fonts.ts @@ -9,7 +9,7 @@ import localFont from "next/font/local"; * - 产出的 CSS 变量由 `globals.css` 桥接到 Tailwind(如 `font-sans`、`font-athelas`)。 * * 注意:本文件不通过 `src/lib/index.ts` 桶导出,避免被误打进客户端 bundle。 - * 引用方式:`import { geistSans, geistMono, athelas } from "@/lib/fonts";` + * 引用方式:`import { geistSans, geistMono, athelas } from "@/core/fonts";` */ const geistSans = Geist({ @@ -29,7 +29,7 @@ const geistMono = Geist_Mono({ * 放置位置:/public/fonts/Athelas-*.ttf * * 路径计算(`next/font/local` 要求相对路径从调用文件出发): - * src/lib/fonts.ts → ../../public/fonts/... + * src/core/fonts.ts → ../../public/fonts/... */ const athelas = localFont({ src: [ diff --git a/src/core/net/config/api_config.ts b/src/core/net/config/api_config.ts index dd1db524..c3ec69a7 100644 --- a/src/core/net/config/api_config.ts +++ b/src/core/net/config/api_config.ts @@ -4,8 +4,9 @@ * 通过环境变量配置后端服务地址与超时时间。 * 原始 Dart: lib/core/net/service_manager.dart */ +import { AppEnvUtil, type AppEnv } from "@/utils/app-env"; -export type AppEnv = "development" | "test" | "production"; +export type { AppEnv }; export interface ApiConfig { /** 后端 API 基础 URL(含协议) */ @@ -33,9 +34,7 @@ export class TimeoutConstants { * 解析当前环境 */ export function getAppEnv(): AppEnv { - const env = process.env.NEXT_PUBLIC_APP_ENV; - if (env === "production" || env === "test") return env; - return "development"; + return AppEnvUtil.current; } /** diff --git a/src/data/dto/chat/guest_chat_quota.ts b/src/data/dto/chat/guest_chat_quota.ts index 8bd7204d..19e63efd 100644 --- a/src/data/dto/chat/guest_chat_quota.ts +++ b/src/data/dto/chat/guest_chat_quota.ts @@ -6,6 +6,7 @@ import { type GuestChatQuotaInput, type GuestChatQuotaData, } from "@/data/schemas/chat/guest_chat_quota"; +import { AppEnvUtil } from "@/utils/app-env"; export class GuestChatQuota { // 静态常量 @@ -13,7 +14,7 @@ export class GuestChatQuota { static readonly maxQuotaPerDayTest = 4; static readonly defaultTotalQuota = 50; - static readonly defaultTotalQuotaDev = 5; + static readonly defaultTotalQuotaTest = 5; static readonly quotaExhausted = 0; declare readonly remaining: number; @@ -40,29 +41,22 @@ export class GuestChatQuota { return GuestChatQuota.quotaExhausted; } - /** - * 是否处于开发环境 - */ - static get isDevelopment(): boolean { - return process.env.NODE_ENV !== "production"; - } - /** * 获取当前环境的每日最大配额 */ static get threshold(): number { - return GuestChatQuota.isDevelopment - ? GuestChatQuota.maxQuotaPerDayTest - : GuestChatQuota.maxQuotaPerDay; + return AppEnvUtil.isProduction() + ? GuestChatQuota.maxQuotaPerDay + : GuestChatQuota.maxQuotaPerDayTest; } /** * 获取当前环境的总配额默认值 */ static get totalQuotaDefault(): number { - return GuestChatQuota.isDevelopment - ? GuestChatQuota.defaultTotalQuotaDev - : GuestChatQuota.defaultTotalQuota; + return AppEnvUtil.isProduction() + ? GuestChatQuota.defaultTotalQuota + : GuestChatQuota.defaultTotalQuotaTest; } /** diff --git a/src/integrations/index.ts b/src/integrations/index.ts index 9652de67..30be615f 100644 --- a/src/integrations/index.ts +++ b/src/integrations/index.ts @@ -2,5 +2,4 @@ * @file Automatically generated by barrelsby. */ -export * from "./browser-detect"; export * from "./media-recorder"; diff --git a/src/stores/auth/auth-events.ts b/src/stores/auth/auth-events.ts index e01d58b9..c58b5b6a 100644 --- a/src/stores/auth/auth-events.ts +++ b/src/stores/auth/auth-events.ts @@ -12,7 +12,6 @@ export type AuthEvent = | { type: "AuthPanelModeChanged"; mode: AuthPanelMode } | { type: "AuthModeChanged"; mode: AuthMode } | { type: "AuthFormCleared" } - | { type: "AuthReset" } | { type: "AuthLogoutSubmitted" } /** App 启动时一次性派发 —— 读 storage 把 loginStatus 同步到状态机 */ | { type: "AuthInit" } diff --git a/src/stores/auth/auth-machine.ts b/src/stores/auth/auth-machine.ts index 56efeb4d..9501502b 100644 --- a/src/stores/auth/auth-machine.ts +++ b/src/stores/auth/auth-machine.ts @@ -78,9 +78,6 @@ export const authMachine = setup({ errorMessage: null, }), }, - AuthReset: { - actions: assign(() => initialState), - }, AuthLogoutSubmitted: "loggingOut", // 启动一次性 init:从 storage 同步 loginStatus 到状态机 —— 由 派发 AuthInit: "initializing", diff --git a/src/stores/user/user-auth-sync.tsx b/src/stores/user/user-auth-sync.tsx index 26911c9d..78710f42 100644 --- a/src/stores/user/user-auth-sync.tsx +++ b/src/stores/user/user-auth-sync.tsx @@ -19,6 +19,7 @@ export function UserAuthSync() { const userDispatch = useUserDispatch(); const lastInitializedStatusRef = useRef(null); + //TODO 在测位栏界面监听变化,监听登录状态的变化若发生变化,则需要分发 user init 事件 useEffect(() => { if (loginStatus === "notLoggedIn" || loginStatus === "guest") { lastInitializedStatusRef.current = null; diff --git a/src/tokens/typography.css b/src/tokens/typography.css index 40352ca3..09459a8c 100644 --- a/src/tokens/typography.css +++ b/src/tokens/typography.css @@ -13,7 +13,6 @@ */ --font-system: var(--font-athelas), system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - --font-athelas: "Athelas", serif; /* Tailwind 默认字体别名:font-sans → font-system */ --font-sans: var(--font-system); diff --git a/src/utils/app-env.ts b/src/utils/app-env.ts index 96978737..1e7e5b74 100644 --- a/src/utils/app-env.ts +++ b/src/utils/app-env.ts @@ -3,13 +3,34 @@ * * 原始 Dart: lib/utils/app_env_util.dart * - * 替代 `AppEnvUtil.isDevelopment`:基于 Next.js 注入的 `process.env.NODE_ENV`。 + * 基于 `.env*` 中的 `NEXT_PUBLIC_APP_ENV` 判断当前业务环境。 + * + * 注意:该变量带 `NEXT_PUBLIC_` 前缀,可同时用于服务端和客户端代码。 */ -export const isDevelopment = (): boolean => - process.env.NODE_ENV !== "production"; +export type AppEnv = "development" | "test" | "production"; -export const isProduction = (): boolean => - process.env.NODE_ENV === "production"; +export class AppEnvUtil { + private constructor() {} -export const isTest = (): boolean => - process.env.NODE_ENV === "test"; + static get current(): AppEnv { + const env = process.env.NEXT_PUBLIC_APP_ENV; + if (env === "production" || env === "test") return env; + return "development"; + } + + static isDevelopment(): boolean { + return AppEnvUtil.current === "development"; + } + + static isProduction(): boolean { + return AppEnvUtil.current === "production"; + } + + static isTest(): boolean { + return AppEnvUtil.current === "test"; + } + + static canOutputLogs(): boolean { + return !AppEnvUtil.isProduction(); + } +} diff --git a/src/integrations/browser-detect.ts b/src/utils/browser-detect.ts similarity index 79% rename from src/integrations/browser-detect.ts rename to src/utils/browser-detect.ts index fbcd1d5f..4f473193 100644 --- a/src/integrations/browser-detect.ts +++ b/src/utils/browser-detect.ts @@ -29,13 +29,4 @@ export const BrowserDetector = { return /WebView|; wv\)|FBAN|FBAV|MicroMessenger|Instagram|Line\//i.test(userAgent); }, - /** 当前是否以 PWA standalone 模式运行 */ - isPWAStandalone(): boolean { - if (typeof window === "undefined") return false; - return ( - window.matchMedia?.("(display-mode: standalone)").matches === true || - // iOS Safari 旧版 - (navigator as Navigator & { standalone?: boolean }).standalone === true - ); - }, }; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 87ac2d7c..3cd858f0 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,22 +1,22 @@ import pino, { type Logger as PinoLogger, type LoggerOptions } from "pino"; -import { isProduction, isTest } from "./app-env"; +import { AppEnvUtil } from "./app-env"; /** * 全局 pino root logger(单例,进程级复用)。 * - * 配置驱动(基于 `app-env.ts` 的 `isProduction()` / `isTest()`): - * - production: info 级别 + JSON(机器可解析,接入日志平台) + * 配置驱动(基于 `app-env.ts` 的 `AppEnvUtil`): + * - production: silent(不输出日志) * - test: warn 级别(减少 vitest 噪音) * - development: debug 级别 + ISO 时间戳(人眼友好) */ const rootLogger: PinoLogger = pino(createLoggerOptions()); function createLoggerOptions(): LoggerOptions { - if (isProduction()) { - return { level: "info" }; + if (!AppEnvUtil.canOutputLogs()) { + return { level: "silent" }; } - if (isTest()) { + if (AppEnvUtil.isTest()) { return { level: "warn" }; } // development