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