diff --git a/.gitignore b/.gitignore index ba42257b..663c0f2e 100644 --- a/.gitignore +++ b/.gitignore @@ -84,5 +84,12 @@ offline-backend-20260512/backend/tests/* deploy .history +# Repo-root offline dependency pack (local only) +/dist/ + +# Frontend offline pack working directories +frontend-web/.offline-pack-build/ +frontend-web/.offline-extract-test/ + # Frontend skills components must be tracked (excluded from the broad skills/ ignore above) !frontend-web/src/components/workspace/skills/ diff --git a/frontend-web/.env.example b/frontend-web/.env.example index ae6121cf..80a2b0f7 100644 --- a/frontend-web/.env.example +++ b/frontend-web/.env.example @@ -5,15 +5,14 @@ # ├─────────────────┼──────────────────────────────────────────────────┤ # │ pnpm dev │ .env.development + .env.development.local │ # │ pnpm build │ .env.production + .env.production.local │ -# │ pnpm build:sit │ .env.sit + .env.sit.local │ +# │ pnpm build:network │ .env.network + .env.network.local(内网地址) │ # │ scripts/start-* │ 脚本注入的环境变量会覆盖上述文件(见下方说明) │ # └─────────────────┴──────────────────────────────────────────────────┘ # # 推荐: # - 日常本地开发:改 .env.development(已提交,团队默认 UAT 地址) # - 本机连 127.0.0.1:8001:复制 .env.development.local.example → .env.development.local -# - 内网 UAT 打包部署:改 .env.production 后再执行 pnpm build -# - SIT 环境打包:改 .env.sit 后再执行 pnpm build:sit(地址为 UAT 的 sit 版本) +# - 内网打包部署:改 .env.network 后执行 pnpm build:network(或改 .env.production 后 pnpm build) # # 若浏览器直连后端报 CORS,请在 Gateway 配置 CORS_ORIGINS,包含前端源站,例如: # CORS_ORIGINS=http://127.0.0.1:5173,http://localhost:5173,http://127.0.0.1:5174 diff --git a/frontend-web/.env.network b/frontend-web/.env.network new file mode 100644 index 00000000..f8962ea6 --- /dev/null +++ b/frontend-web/.env.network @@ -0,0 +1,30 @@ +# 内网 / 局域网生产打包(npm run build:network 或 pnpm build:network) +# 打包前请将 192.168.1.100 改为实际服务器 IP;变量会写入 dist,运行时不可再改 + +# Gateway / DeerFlow API(与 offline-backend DEPLOY 文档一致) +VITE_BACKEND_BASE_URL=http://47.88.25.99 +VITE_LANGGRAPH_BASE_URL=http://47.88.25.99/api + +# 轻应用「新建笔记」系列接口 +VITE_NOTE_API_BASE_URL=http://192.168.1.100:8000 +# CMZS 后端接口 +VITE_API_BASE_URL=http://192.168.1.100/zncm +# 佳来模型对话接口 +VITE_AIPI_CHAT_URL=http://192.168.1.100/gpt +# 多轮检索接口 +VITE_AIPI_GENERATE_CHAT_URL=http://192.168.1.100/jialai_b1_chat +VITE_AIPI_TASK_CHAT_URL=http://192.168.1.100/qq_b1_chat +VITE_AIPI_KNOWLEDGE_URL=http://192.168.1.100/knowlibrary +VITE_AIPI_B1COH_URL=http://192.168.1.100/web +VITE_AIPI_B2ACTOR_URL=http://192.168.1.100 +VITE_AIPI_B2ACTOR_XWT_PATH=/fromB/toXWT?isFrame=1 +VITE_AIPI_KNOWLEDGE_IFRAME_API_URL=http://192.168.1.100/knowes/api/iframeKnowledgeUrl +VITE_AIPI_KNOWLEDGE_IFRAME_SKIN=red +VITE_AIPI_MOPECHAT_URL=http://192.168.1.100/likun_b1_mope +VITE_AIPI_BACKENP_URL=http://192.168.1.100 +VITE_AIPI_CONSUMER_URL=http://192.168.1.100/consumer +VITE_AIPI_PGLINKTO_URL=http://192.168.1.100 +VITE_AIPI_XKLINKTO_URL=http://192.168.1.100 + +# 可选:Canvas 独立 LangGraph 地址 +# VITE_CANVAS_LANGGRAPH_BASE_URL= diff --git a/frontend-web/package.json b/frontend-web/package.json index 69d9ebd5..6026ed89 100644 --- a/frontend-web/package.json +++ b/frontend-web/package.json @@ -7,6 +7,7 @@ "dev": "vite", "build": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build", "build:sit": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build --mode sit", + "build:network": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build --mode network", "preview": "vite preview", "typecheck": "tsc --noEmit" }, diff --git a/frontend-web/src/App.tsx b/frontend-web/src/App.tsx index ea03c986..5649c323 100644 --- a/frontend-web/src/App.tsx +++ b/frontend-web/src/App.tsx @@ -2,6 +2,7 @@ import { useMemo } from "react"; import { Navigate, Route, Routes } from "react-router-dom"; import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { FontScaleApplier } from "@/components/font-scale"; import { QueryClientProvider } from "@/components/query-client-provider"; import { AuthSyncWatcher } from "@/core/auth/sync"; import { I18nProvider } from "@/core/i18n/context"; @@ -39,6 +40,7 @@ function AppProviders({ children }: { children: React.ReactNode }) { + {children} diff --git a/frontend-web/src/components/font-scale.tsx b/frontend-web/src/components/font-scale.tsx new file mode 100644 index 00000000..14c97c8e --- /dev/null +++ b/frontend-web/src/components/font-scale.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { useEffect } from "react"; + +import { useLocalSettings } from "@/core/settings"; + +/** + * 基准字号 = 16px,对应《深蓝色 UI 规范》§2.1.2 后台系统正文字号(16px Regular)。 + * 用户在「外观 → 字号」里选择的倍数通过 CSS 变量 `--ui-font-scale` 生效:globals.css + * 只把 Tailwind 的 `--text-*` 字号 token 乘以该倍数,**不动间距 token**,所以放大 / 缩小 + * 的只有文字本身,padding / margin / 宽高等布局尺寸保持不变,不会把页面挤错位。 + * `fontScale === 1` 即默认 16px。 + */ +export const BASE_FONT_PX = 16; + +export const FONT_SCALE_OPTIONS: { value: number; label: string; px: number }[] = + [ + { value: 0.875, label: "小", px: 14 }, + { value: 1, label: "标准", px: 16 }, + { value: 1.125, label: "大", px: 18 }, + { value: 1.25, label: "特大", px: 20 }, + ]; + +/** + * 把用户选择的字号倍数写到 的 `--ui-font-scale` CSS 变量。挂在应用根部, + * 保证刷新后即时生效、全局统一。只设变量、不直接改 font-size,因此布局不受影响。 + * 不渲染任何 DOM。 + */ +export function FontScaleApplier() { + const [settings] = useLocalSettings(); + const { fontScale } = settings.display; + + useEffect(() => { + const scale = + typeof fontScale === "number" && Number.isFinite(fontScale) && fontScale > 0 + ? fontScale + : 1; + const root = document.documentElement; + root.style.setProperty("--ui-font-scale", String(scale)); + // 清掉旧实现可能残留的根级 font-size 内联样式(旧版用它做整页缩放)。 + root.style.removeProperty("font-size"); + }, [fontScale]); + + return null; +} diff --git a/frontend-web/src/components/ui/button.tsx b/frontend-web/src/components/ui/button.tsx index 31e89b42..836b8705 100644 --- a/frontend-web/src/components/ui/button.tsx +++ b/frontend-web/src/components/ui/button.tsx @@ -1,6 +1,6 @@ -import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; -import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; @@ -14,7 +14,7 @@ const buttonVariants = cva( destructive: "cursor-pointer bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: - "cursor-pointer border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + "cursor-pointer border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-[#213D5E] dark:bg-[#022248] dark:text-[#CCD2DA] dark:hover:border-[#213D5E] dark:hover:bg-[#0a2f58] dark:hover:text-[#CCD2DA] dark:[&_svg]:text-[#CCD2DA]", secondary: "cursor-pointer bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: @@ -37,27 +37,27 @@ const buttonVariants = cva( }, ); -const Button = React.forwardRef< - HTMLButtonElement, - React.ComponentProps<"button"> & - VariantProps & { - asChild?: boolean; - } ->(({ className, variant = "default", size = "default", asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - - return ( - - ); -}); - -Button.displayName = "Button"; - -export { Button, buttonVariants }; +const Button = React.forwardRef< + HTMLButtonElement, + React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean; + } +>(({ className, variant = "default", size = "default", asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + + return ( + + ); +}); + +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/frontend-web/src/components/ui/empty.tsx b/frontend-web/src/components/ui/empty.tsx index f2933eda..b5fffa9c 100644 --- a/frontend-web/src/components/ui/empty.tsx +++ b/frontend-web/src/components/ui/empty.tsx @@ -34,7 +34,7 @@ const emptyMediaVariants = cva( variants: { variant: { default: "bg-transparent", - icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6", + icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6 dark:text-[#55A6FE] dark:bg-[#022248]", }, }, defaultVariants: { @@ -62,7 +62,7 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { return (
); @@ -73,7 +73,7 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4", + "text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4 dark:text-[#55A6FE]", className, )} {...props} diff --git a/frontend-web/src/components/ui/input.tsx b/frontend-web/src/components/ui/input.tsx index 6537bb0f..55d760d6 100644 --- a/frontend-web/src/components/ui/input.tsx +++ b/frontend-web/src/components/ui/input.tsx @@ -3,6 +3,14 @@ import * as React from "react"; import { cn } from "@/lib/utils"; function Input({ className, type, ...props }: React.ComponentProps<"input">) { + const usesDarkNativePicker = + type === "number" || + type === "date" || + type === "datetime-local" || + type === "time" || + type === "month" || + type === "week"; + return ( ) { className={cn( "h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base outline-none transition-[color,box-shadow] md:text-sm", "border-input placeholder:text-muted-foreground", - "dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEFF] dark:placeholder:text-[#566B7F] rounded-[4px] text-[16px]", + "dark:border-[#1B477E] dark:bg-[#0D2D54] dark:text-[#AACEFF] dark:placeholder:text-[#566B7F] dark:rounded-[4px] dark:text-[16px] md:dark:text-[16px]", + usesDarkNativePicker && "dark:[color-scheme:dark]", "file:text-foreground selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] dark:focus-visible:border-[#3b82f6] dark:focus-visible:ring-[#3b82f6]/30", "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40", diff --git a/frontend-web/src/components/ui/select.tsx b/frontend-web/src/components/ui/select.tsx index 327e1350..5f3e26a9 100644 --- a/frontend-web/src/components/ui/select.tsx +++ b/frontend-web/src/components/ui/select.tsx @@ -24,69 +24,70 @@ function SelectValue({ return ; } -const SelectTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - size?: "sm" | "default"; - } ->(({ className, size = "default", children, ...props }, ref) => { - return ( - - {children} - - - - - ); -}); - -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + size?: "sm" | "default"; + } +>(({ className, size = "default", children, ...props }, ref) => { + return ( + + {children} + + + + + ); +}); -const SelectContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, position = "item-aligned", align = "center", ...props }, ref) => { - return ( - - - - - {children} - - - - - ); -}); - -SelectContent.displayName = SelectPrimitive.Content.displayName; +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "item-aligned", align = "center", ...props }, ref) => { + return ( + + + + + {children} + + + + + ); +}); + +SelectContent.displayName = SelectPrimitive.Content.displayName; function SelectLabel({ className, @@ -95,40 +96,44 @@ function SelectLabel({ return ( ); } -const SelectItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => { - return ( - - - - - - - {children} - - ); -}); - -SelectItem.displayName = SelectPrimitive.Item.displayName; +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => { + return ( + + + + + + + {children} + + ); +}); + +SelectItem.displayName = SelectPrimitive.Item.displayName; function SelectSeparator({ className, @@ -151,7 +156,7 @@ function SelectScrollUpButton({
- {label} + {label} {!editing && (