# 前端技术规范 ## 运行环境 - Node.js >= 20.9 - npm >= 10.x ## 项目结构 ### Monorepo 架构 - 包管理:npm workspaces + Turborepo 2.0 - 项目根目录:`geo_bsp/` - 共享包命名空间:`@geomative/*` ### 应用划分 ``` apps/ ├── web/ # 公开网站 — Next.js 16 (Turbopack),端口 3000(国际站) / 3002(中文站) ├── admin/ # 后台管理系统 — Vite 6 + React 19 + React Router DOM 7,端口 3003 └── api/ # 后端 API 服务 — Hono,端口 3001 packages/ ├── types/ # 共享 TypeScript 类型定义 (@geomative/types) ├── ui/ # 共享 UI 组件库 (@geomative/ui) ├── utils/ # 共享工具函数 (@geomative/utils) └── config/ # 共享配置 — Tailwind、ESLint、Prettier、TSConfig (@geomative/config) ``` ### 共享包依赖方向 ``` config -> types -> utils -> ui -> apps/* ``` - 禁止循环依赖 - `types` 不依赖 `ui` - `utils` 不依赖 `ui` - `ui` 可依赖 `types` 和 `utils` ## 技术栈 | 类别 | 技术 | 版本 | |------|------|------| | 前端框架 | Next.js (App Router) | 16 | | 后台管理 | Vite + React Router DOM | 6 / 7 | | UI 库 | React | 19 | | 样式方案 | Tailwind CSS | 3.4 | | 类型系统 | TypeScript | 5.0+ | | 服务端状态 | TanStack React Query | API 数据获取、缓存、同步 | | 客户端状态 | Zustand | 全局 UI 状态、认证状态、权限缓存(仅 admin) | | 表单处理 | React Hook Form + Zod | - | | 富文本编辑 | Tiptap | - | | 国际化 | next-intl | 4.9 | | 认证 | JWT + RBAC | - | ## apps/web — 公开网站 ### 路由结构(App Router) ``` app/ ├── page.tsx # 首页 ├── layout.tsx # 根布局(国际化) ├── globals.css # 全局样式 │ ├── about/ # 关于我们 │ ├── company/ # 公司介绍 │ ├── history/ # 发展历程 │ ├── certifications/ # 资质认证 │ ├── partners/ # 合作伙伴 │ ├── dealers/ # 经销商 │ └── contact/ # 联系我们 │ ├── products/ # 产品中心 │ ├── [category]/ # 产品分类 │ └── [category]/[slug]/ # 产品详情 │ ├── solutions/ # 解决方案 │ ├── page.tsx # 方案总览 │ └── [slug]/ # 方案详情 │ ├── academy/ # 技术学院 │ ├── product-operation/ # 产品操作 │ ├── case-library/ # 案例库 │ ├── geophysical-knowledge/ # 物探知识 │ └── certification/ # 认证培训 │ ├── support/ # 技术支持 │ ├── knowledge-base/ # 知识库 │ ├── faq/ # FAQ │ ├── downloads/ # 下载中心 │ ├── software-updates/ # 软件更新 │ ├── after-sales/ # 售后服务 │ └── warranty/ # 保修政策 │ ├── community/ # 社区动态 ├── geometa/ # GeoMeta 平台 ├── configurator/ # 报价配置器 ├── cases/[slug]/ # 案例详情 ├── pages/[slug]/ # 动态页面 ├── privacy-policy/ # 隐私政策 └── terms-of-use/ # 使用条款 ``` ### 关键配置 - ISR 策略:首页 30 分钟,产品页 1 小时 - 动态路由参数为 `Promise` 类型:`params: Promise<{ slug: string }>` - 开发模式使用 `--turbopack` 标志 - 多语言:中文站无前缀,国际站 `/en` 前缀 ## apps/admin — 后台管理系统 ### 当前架构(Vite + React Router) Admin 使用 Vite 6 + React 19 + React Router DOM 7,非 Next.js。路由通过 `createBrowserRouter` 配置,所有页面使用 `lazy()` 懒加载实现代码分割。 ``` apps/admin/src/ ├── components/ │ ├── editor/ # Tiptap 富文本编辑器 │ ├── help/ # 帮助系统组件 │ ├── navigation/ # 导航管理组件 │ ├── seo/ # SEO 管理组件 │ └── structured-editors/ # 结构化页面编辑器 ├── data/help/ # 帮助文档数据 ├── hooks/ # 自定义 Hooks ├── lib/ │ ├── auth.ts # JWT Token 管理 │ ├── api-client.ts # API 客户端(自动刷新 Token) │ ├── help-utils.ts # 帮助工具 │ └── utils.ts # 通用工具 ├── pages/ # 页面组件(全部懒加载) ├── providers/ │ ├── auth-provider.tsx # 认证上下文(将迁移至 useAuthStore) │ └── query-provider.tsx # React Query Provider ├── stores/ # Zustand 全局状态管理 │ ├── useAuthStore.ts # 认证状态(替代 AuthProvider Context) │ ├── useUIStore.ts # 全局 UI 状态(侧边栏折叠、全局 loading,持久化) │ ├── useLocaleStore.ts # 编辑器语言偏好(持久化) │ └── usePermissionStore.ts # 权限数据缓存(为细粒度权限矩阵预留) └── router/ ├── index.tsx # 路由配置(createBrowserRouter) ├── AppRouter.tsx # 路由容器 ├── DashboardLayout.tsx # 仪表盘布局(侧边栏 + 内容区) ├── AuthLayout.tsx # 登录页布局 └── AuthGuard.tsx # 路由守卫(JWT 认证检查) ``` ### 当前路由结构 ``` /login # 登录页(AuthLayout) / # 仪表盘首页(AuthGuard 保护) /navigation # 导航管理 /navigation/:menuId # 菜单分支列表 /navigation/:menuId/:branchId # 子分支列表 /navigation/:menuId/:branchId/sub/:subBranchId # 子分支详情 /pages # 页面列表 /pages/:pageId # 页面编辑器 /pages/fixed/:slug # 固定页面编辑器 /content # 内容管理首页 /content/events # 活动管理 /content/news # 新闻管理 /content/reveals # 发布管理 /downloads # 下载资源列表 /downloads/categories # 下载分类管理 /downloads/:id # 下载资源编辑器 /analytics # 数据分析 /permissions # 权限管理 /settings # 系统设置 /about-me # 个人信息 /manual # 使用手册 /help/:pageId # 帮助详情 ``` ### 当前侧边栏菜单 ``` 导航管理 /navigation module: navigation 页面管理 /pages module: pages 内容管理 /content module: content 权限管理 /permissions module: permissions 系统设置 /settings module: settings 使用手册 /manual module: settings About Me /about-me module: settings ``` ### 多模块演进计划 Admin 将从当前单一 CMS 后台演进为统一企业管理平台,新增设备管理、报价管理、运输管理等模块。详见 `docNew/12-Admin多模块架构设计.md`。 **目标目录结构(演进后):** ``` app/(dashboard)/ ├── layout.tsx # 统一布局,侧边栏按权限动态渲染 ├── (cms)/ # 官网管理模块 │ ├── navigation/ # 导航管理 │ ├── pages/ # 页面管理 │ ├── content/ # 内容管理 │ ├── downloads/ # 下载资源 │ └── seo/ # SEO 管理 ├── (devices)/ # 设备管理模块(新增) │ ├── list/ # 设备列表 │ ├── [id]/ # 设备详情 │ ├── maintenance/ # 维护记录 │ └── categories/ # 设备分类 ├── (quotations)/ # 报价系统模块(新增) │ ├── list/ # 报价列表 │ ├── create/ # 新建报价 │ ├── [id]/ # 报价详情 │ └── templates/ # 报价模板 ├── (shipping)/ # 运输管理模块(新增) │ ├── orders/ # 运输工单 │ ├── tracking/ # 物流追踪 │ └── logistics/ # 物流商管理 └── (system)/ # 系统管理(跨模块) ├── users/ # 用户管理 ├── roles/ # 角色管理 + 权限矩阵配置 ├── locales/ # 语言设置 └── settings/ # 系统配置 ``` **目标侧边栏分组(按权限动态渲染):** ``` 官网管理 ├── 导航管理 ├── 页面管理 ├── 内容管理 └── 下载资源 设备管理(新增) ├── 设备列表 ├── 维护记录 └── 设备分类 报价管理(新增) ├── 报价列表 ├── 新建报价 └── 报价模板 运输管理(新增) ├── 运输工单 ├── 物流追踪 └── 物流商管理 系统设置 ├── 用户管理 ├── 角色权限 ├── 语言设置 └── 系统配置 ``` **模块间隔离原则:** - 路由隔离:每个模块独立路由分组 `(module-name)/` - 组件隔离:模块专用组件放模块目录内,跨模块复用的提取到 `packages/ui` - API 隔离:每个模块独立 API namespace(如 `/admin/devices/*`) - 类型隔离:每个模块独立类型文件,通过 `packages/types` 统一导出 - 状态隔离:React Query key 使用模块前缀(如 `['devices', 'list']`),Zustand store 按职责拆分不按模块拆分 - 禁止跨模块直接引用,必须通过共享包通信 ### API 路由规划 ``` /admin/auth/* # 认证(现有) /admin/navigation/* # 导航管理(现有) /admin/pages/* # 页面管理(现有) /admin/content/* # 内容管理(现有) /admin/analytics/* # 数据分析(现有) /admin/downloads/* # 下载资源(现有) /admin/users/* # 用户管理(现有) /admin/devices/* # 设备管理(新增) /admin/quotations/* # 报价管理(新增) /admin/shipping/* # 运输管理(新增) /admin/permissions/* # 权限矩阵管理(新增) ``` ## 编码规范 ### 命名规范 #### 文件命名 - 组件文件:PascalCase,如 `Button.tsx`、`HeroCarousel.tsx` - 工具/钩子文件:camelCase,如 `useAuth.ts`、`fetchNavigation.ts` - 类型文件:camelCase,如 `models.ts`、`cms.ts` - 样式文件:kebab-case,如 `globals.css` - 配置文件:kebab-case,如 `tailwind.config.ts`、`next.config.ts` - 页面目录:kebab-case,如 `products/`、`solutions/`、`tech-academy/` - 测试文件:被测文件名 + `.test.ts(x)`,如 `Button.test.tsx` #### 代码命名 - 组件名:PascalCase,如 `LayoutShell`、`HomeHero` - 函数/变量名:camelCase,如 `fetchNavigation`、`activeLocale` - 常量:UPPER_SNAKE_CASE,如 `MAX_RETRIES`、`DEFAULT_LOCALE` - 类型/接口名:PascalCase,如 `NavigationItem`、`PageContent` - 枚举值:PascalCase,如 `ButtonVariant.Primary` - 自定义 Hook:`use` 前缀,如 `useAuth`、`useTranslations` - 事件处理函数:`handle` 前缀,如 `handleSubmit`、`handleClick` - 布尔变量/属性:`is`/`has`/`should` 前缀,如 `isLoading`、`hasError` #### 类型命名后缀约定 - API 响应类型:`XxxResponse`,如 `ProductsResponse` - API 请求参数:`XxxRequest`,如 `CreatePageRequest` - Props 类型:`XxxProps`,如 `ButtonProps` - 状态类型:`XxxState`,如 `FormState` - 枚举类型:无后缀,如 `Locale`、`ButtonVariant` ### 导入规范 #### 导入顺序 ```typescript // 1. React / Next.js 核心模块 import { useState, useEffect } from 'react'; import { NextRequest } from 'next/server'; // 2. 第三方库 import { useTranslations } from 'next-intl'; import { useForm } from 'react-hook-form'; // 3. 共享包 (@geomative/*) import { NavigationItem, Product } from '@geomative/types'; import { Button, Card } from '@geomative/ui'; // 4. 应用内部模块(使用路径别名) import { apiClient } from '@/lib/api'; import { AuthProvider } from '@/providers/auth-provider'; // 5. 相对路径导入 import { LayoutClientShell } from './components/LayoutClientShell'; import { fetchNavigation } from '../lib/navigation'; // 6. 类型导入(使用 type 关键字) import type { Metadata } from 'next'; ``` #### 路径别名 - `apps/web`:使用 `@/` 前缀映射到 `src/` 或根目录 - `apps/admin`:使用 `@/` 前缀映射到 `src/` - 共享包:使用 `@geomative/*` 命名空间 #### 导出规范 - 优先使用命名导出(named export) - 每个组件文件一个主组件导出 - 使用 barrel export(`index.ts`)整理模块导出 - 类型导出使用 `export type` 语法 ```typescript // 组件导出示例 export { Button } from './Button'; export type { ButtonProps, ButtonVariant } from './Button'; ``` ### 组件规范 #### Server Components vs Client Components(apps/web) - 默认使用 Server Components(Next.js App Router 约定) - 仅在需要交互(state、effects、event handlers)时使用 Client Components - Client Components 必须在文件顶部添加 `"use client"` 指令 - 布局组件(Layout)默认为 Server Component - 数据获取优先在 Server Component 中完成 ```typescript // Server Component — 默认,无需声明 export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; const product = await fetchProduct(slug); return ; } // Client Component — 需要交互时使用 "use client" import { useState } from 'react'; export function ProductFilter({ categories }: ProductFilterProps) { const [active, setActive] = useState('all'); // ... } ``` #### 组件结构顺序 ```typescript // 1. "use client" 指令(如需要) "use client" // 2. 导入 import { useState } from 'react'; // 3. 类型定义 interface ExampleProps { title: string; children: React.ReactNode; } // 4. 组件定义 export function Example({ title, children }: ExampleProps) { // 4a. hooks const t = useTranslations('common'); // 4b. 派生状态 const formattedTitle = title.toUpperCase(); // 4c. 事件处理 const handleClick = () => { /* ... */ }; // 4d. 渲染 return (

{formattedTitle}

{children}
); } ``` #### 组件设计原则 - 单一职责:每个组件只做一件事 - 组合优于继承:使用 children 和 props 组合 - Props 解构:始终解构 props,不要使用 `props.xxx` - Forward Ref:可复用组件需要支持 ref 转发 - 有意义的默认值:为可选 props 提供合理的默认值 ### 样式规范 #### Tailwind CSS 使用约定 - 使用 Tailwind 工具类,禁止手写 CSS(除非 CSS 变量或动画关键帧) - 类名顺序遵循一致性(建议:布局 -> 尺寸 -> 间距 -> 排版 -> 颜色 -> 其他) - 超过 5 个类名时考虑提取为组件或使用 `@apply`(谨慎使用) - 响应式设计使用 Mobile-first 方式:`md:` -> `lg:` - 条件样式使用模板字符串或 `clsx`/`cn` 工具函数 #### 设计令牌(Design Tokens) | 令牌 | 值 | 用途 | |------|-----|------| | 主色 | `#3a6b35` | 品牌绿,CTA 按钮、强调元素 | | 辅色 | 大地色系 | 深绿、岩石灰、矿物绿 | | 字体 | Inter | 英文主字体 | | 中文回退 | Source Han Sans SC, PingFang SC | 中文排版 | | 断点 | `md: 769px`, `lg: 1025px` | 响应式分界 | | 触摸目标 | 最小 44px | 移动端可访问性 | | 对比度 | >= 4.5:1 | WCAG 2.1 AA | #### 响应式断点 ```css /* Mobile-first */ /* 默认:< 769px (Mobile) */ /* md: >= 769px (Tablet) */ /* lg: >= 1025px (Desktop) */ ``` ### TypeScript 规范 - 启用 strict 模式 - 禁止使用 `any`,如必须使用需添加注释说明原因 - 函数参数和返回值必须有明确的类型注解 - 优先使用 `interface` 定义对象类型,`type` 用于联合类型、工具类型 - 使用 `as const` 断言定义常量对象 - 异步函数返回类型明确标注 `Promise` - 使用 TypeScript 枚举或联合字面量类型替代魔法字符串 ```typescript // 推荐 interface NavigationItem { id: string; title: Record; path: string; } type Locale = 'zh-CN' | 'en'; type ButtonVariant = 'primary' | 'secondary' | 'outline'; // 避免 const config: any = {}; // 禁止 function handleClick(e) { /* ... */ } // 缺少类型 ``` ### 国际化规范(next-intl,apps/web) #### 翻译文件 - 翻译文件位于 `apps/web/messages/` 目录 - 文件格式:JSON(`zh-CN.json`、`en.json`) - 使用命名空间分隔:`home`、`products`、`common`、`nav` 等 #### 使用方式 ```typescript // Server Component import { useTranslations } from 'next-intl'; export default function Page() { const t = useTranslations('home'); return

{t('title')}

; } // Client Component "use client" import { useTranslations } from 'next-intl'; export function ProductCard() { const t = useTranslations('products'); return {t('addToCart')}; } ``` #### 内容回退机制 - 回退链:请求语言 -> 基准语言 -> `en` - 多语言内容使用 `Record` 类型存储 - 导航项通过 `visible_locales` 控制语言可见性 #### URL 策略 - 中文站:无语言前缀(如 `/products`) - 国际站:`/en` 前缀(如 `/en/products`) - SEO 自动生成 hreflang 替代链接 ### API 调用规范 #### 数据获取(apps/web) - Server Components 中直接使用 `fetch` 或 API 客户端 - Client Components 中使用 TanStack React Query - API 调用超时设置:5 秒 - API 不可用时使用本地回退内容 ```typescript // Server Component 数据获取 async function getProduct(slug: string) { try { const res = await fetch(`${API_URL}/api/products/${slug}`, { next: { revalidate: 3600 } }); if (!res.ok) throw new Error('Failed to fetch'); return await res.json(); } catch { return getFallbackProduct(slug); // 回退机制 } } ``` #### API 客户端(apps/admin) - 使用 `src/lib/api-client.ts` 统一管理 API 请求 - JWT Token 存储在 localStorage - 自动刷新过期 Token(Access Token + Refresh Token 双令牌) - API 请求前缀:`/admin/*`(需认证) #### React Query 约定 - 使用 Query Key 工厂模式 - 避免在组件中直接调用 `fetch`,通过 hooks 封装 - 乐观更新用于即时反馈操作 ### 路由规范 #### Next.js App Router(apps/web) - 使用文件系统路由:`app/` 目录 - 动态路由参数为 `Promise` 类型:`params: Promise<{ slug: string }>` - 布局使用 `layout.tsx`,页面使用 `page.tsx` - 加载状态使用 `loading.tsx` - 错误处理使用 `error.tsx` - ISR 策略:首页 30 分钟,产品页 1 小时 #### React Router(apps/admin) - 使用 `createBrowserRouter` 配置路由 - 所有页面使用 `lazy()` 懒加载实现代码分割 - `AuthGuard` 组件保护需要认证的路由 - 路由守卫在 `src/router/AuthGuard.tsx` - 布局结构:`DashboardLayout`(侧边栏 + 内容区)和 `AuthLayout`(登录页) ### 表单规范 - 使用 React Hook Form 管理表单状态 - 使用 Zod 定义验证 schema - 通过 `zodResolver` 连接 React Hook Form 和 Zod ```typescript import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; const schema = z.object({ email: z.string().email(), password: z.string().min(8), }); type LoginForm = z.infer; export function LoginForm() { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema), }); // ... } ``` ### 注释规范 - 公共组件和 Hook 必须有 JSDoc 注释 - 复杂业务逻辑处需有行内注释说明意图 - 禁止保留无意义的注释和注释掉的废弃代码 - `// TODO:` 标注待办事项,需包含上下文说明 - `// NOTE:` 标注重要设计决策或注意事项 ### 错误处理规范 - 页面级错误使用 `error.tsx` 捕获 - API 调用失败必须提供回退方案 - 禁止捕获错误后不做任何处理(空 catch) - 用户操作失败时展示友好的错误提示 - 使用 TypeScript 的 discriminated union 处理不同状态 ### 性能规范 - Server Components:默认使用,减少客户端 JS 体积 - 代码分割:路由级自动分割,大型组件使用 `dynamic()` 懒加载 - 图片优化:使用 `OptimizedImage` 组件或 Next.js `` - ISR:合理设置页面重新验证时间 - Bundle 分析:定期检查包体积 - Standalone 输出:生产构建使用 `output: 'standalone'` ### 无障碍规范(A11y) - 语义化 HTML:使用正确的标签(`