# 查价系统 产品 UI/UX 设计方案 v1.0 | 版本 | 日期 | 作者 | 说明 | |------|------|------|------| | v1.0 | 2026-06-16 | 产品设计/UI/UX | 可直接用于前端开发与 Cursor 生成代码 | > 配套文档:[查价系统-PRD.md](./查价系统-PRD.md) · [查价系统-技术设计.md](./查价系统-技术设计.md) > 技术栈:Next.js 15 App Router · Tailwind CSS · shadcn/ui(定制 token)· Geist 字体 --- ## 1. 信息架构(Information Architecture) ### 1.1 页面列表 | 编号 | 页面名称 | 路由 | 角色 | 优先级 | |------|----------|------|------|--------| | P01 | 登录页 | `/login` | 全部 | P0 | | P02 | 工作台 | `/dashboard` | customer / operator / admin | P0 | | P03 | 卡派查价页 | `/quote` | customer | P0 | | P04 | 询价结果页 | `/quote/result/[quoteId]` | customer | P0 | | P05 | 询价历史 | `/history` | customer | P0 | | P06 | 加价配置 | `/admin/markup` | operator / admin | P0 | | P07 | 预警中心 | `/admin/alerts` | admin | P0 | | P08 | 403 无权限 | `/403` | 全部 | P1 | | P09 | 404 未找到 | `/not-found` | 全部 | P1 | **MVP 不包含页面**:注册页、个人设置、CC 对接页、下单页、多币种设置页。 ### 1.2 页面关系图 ``` 登录页 (/login) │ ├─ customer ──► 工作台 (/dashboard) │ │ │ ├─► 卡派查价 (/quote) │ │ │ │ │ └─► 询价结果 (/quote/result/:quoteId) │ │ │ └─► 询价历史 (/history) │ │ │ └─► 询价结果 (/quote/result/:quoteId) │ ├─ operator ──► 工作台 (/dashboard) │ └─► 加价配置 (/admin/markup) │ └─ admin ──► 工作台 (/dashboard) ├─► 加价配置 (/admin/markup) └─► 预警中心 (/admin/alerts) ``` **全局规则**: - 未登录访问受保护路由 → 重定向 `/login?redirect={path}` - `customer` 访问 `/admin/*` → `/403` - `operator` 访问 `/admin/alerts` → `/403` - 登录成功默认跳转 `/dashboard` ### 1.3 路由结构(Next.js App Router) ``` app/ ├── login/page.tsx # P01 ├── dashboard/page.tsx # P02 ├── quote/ │ ├── page.tsx # P03 │ └── result/[quoteId]/page.tsx # P04 ├── history/page.tsx # P05 ├── admin/ │ ├── markup/page.tsx # P06 │ └── alerts/page.tsx # P07 ├── 403/page.tsx # P08 └── not-found.tsx # P09 ``` | 路由 | 鉴权 | 布局 | |------|------|------| | `/login` | 公开 | `AuthLayout`(无侧边栏) | | `/dashboard` | JWT | `AppLayout` | | `/quote` | JWT + customer | `AppLayout` | | `/quote/result/[quoteId]` | JWT + customer(本人) | `AppLayout` | | `/history` | JWT + customer | `AppLayout` | | `/admin/markup` | JWT + operator/admin | `AdminLayout` | | `/admin/alerts` | JWT + admin | `AdminLayout` | ### 1.4 导航结构 **客户侧 `AppLayout` 顶栏**: - Logo「美美与共 · 卡派查价」 - 导航:工作台 | 卡派查价 | 询价历史 - 右侧:客户 ID + 退出 **管理侧 `AdminLayout` 顶栏 + 侧栏**: - 侧栏:工作台 | 加价配置 | 预警中心(admin 可见) - 顶栏:角色标签 + 退出 --- ## 2. 交互原型(Interaction Design) ### 2.1 全局交互规则 | 规则 ID | 规则 | |---------|------| | IX-01 | 所有提交按钮点击后 `disabled` + `loading`,最短 3s | | IX-02 | 表单字段 `onBlur` 触发校验;错误文案显示在字段下方 | | IX-03 | API 错误统一用 `ErrorBanner` 展示在页面主内容区顶部 | | IX-04 | 查价接口 `Cache-Control: no-store`,禁止浏览器缓存 | | IX-05 | 金额展示固定 `USD`,格式 `$1,234.56` | | IX-06 | 所有异步操作最长等待 30s,超时展示可重试入口 | --- ### 2.2 P01 登录页 `/login` **页面结构**: - Header:Logo + 产品名 - 主区:登录卡片(用户名、密码) - 操作区:登录按钮 **状态**:`idle` | `loading` | `error` | `success` | 状态 | UI | |------|-----| | idle | 表单可编辑,按钮「登录」 | | loading | 按钮 loading,输入框 disabled | | error | ErrorBanner「用户名或密码错误」 | | success | 跳转 `/dashboard` | **按钮行为**:点击 → `POST /api/auth/login` → 存 token → 按角色跳转 --- ### 2.3 P02 工作台 `/dashboard` **页面结构**: - Header:欢迎语 + 角色 - 主区:快捷入口卡片(按角色渲染) - 操作区:无 **客户卡片**:「卡派查价」「询价历史」 **运营卡片**:「加价配置」 **管理员卡片**:「加价配置」「预警中心」+ 未处理预警数量角标 --- ### 2.4 P03 卡派查价页 `/quote`(核心) **页面结构**: ``` ┌─────────────────────────────────────────────┐ │ Header(AppLayout 顶栏) │ ├─────────────────────────────────────────────┤ │ 页面标题:卡派查价 │ │ 副标题:填写货物信息,获取实时报价(约 30 秒内) │ ├──────────────────┬──────────────────────────┤ │ 主输入区(左 60%) │ 结果区(右 40%,空时占位) │ │ QuoteForm │ QuoteResultPanel │ ├──────────────────┴──────────────────────────┤ │ 操作区:获取报价(PrimaryButton) │ └─────────────────────────────────────────────┘ ``` **移动端(<768px)**:单列;输入区在上,结果区在下。 **页面状态机**: | 状态 | 触发 | 输入区 | 结果区 | 操作区 | |------|------|--------|--------|--------| | `idle` | 进入页面 | 可编辑 | 空状态插画 +「填写信息后获取报价」 | 按钮可点 | | `validating` | 点击提交 | 只读 | 不变 | 按钮 loading | | `processing` | POST 成功且 status=processing | 只读 | Skeleton +「正在查询报价…」+ 进度条(0–30s) | 按钮 disabled | | `success` | GET status=done 且 is_realtime=true | 可编辑(新询价生成新 request_id) | QuoteCard 双等级 | 按钮可点 | | `fallback` | GET status=done 且 is_realtime=false | 同 success | QuoteCard + WarningBanner「非实时报价,仅供参考」 | 按钮可点 | | `error` | GET status=failed 或超时 | 可编辑 | ErrorBanner + 重试按钮 | 按钮可点 | | `expired` | 倒计时归零 | 可编辑 | 蒙层 +「报价已过期」ConfirmDialog | 确认后回到 idle | **关键交互**: 1. 点击「获取报价」→ 前端生成 `request_id`(UUID v4) → `POST /api/quotes` 2. 若立即 `done`(L1/L2 命中)→ 直接 `success`/`fallback` 3. 若 `processing` → 每 2s `GET /api/quotes/{quoteId}`,最多 15 次 4. `success` 后展示 3 分钟倒计时 `CountdownTimer`;<30s 时文字变 Warning 色 5. 防重复:提交后按钮 disabled 3000ms;相同 request_id 由后端幂等 **表单字段布局(QuoteForm)**: | 分组 | 字段 | |------|------| | 提货地址 | 街道、城市、州(2 字母)、邮编 | | 派送地址 | 同上 | | 货物信息 | 重量+单位、长×宽×高+单位、件数、货物类型(Select) | | 偏好 | 单位制(公制/英制)、默认展示等级(标准/保证送达) | **即时校验**: | 字段 | 规则 | 错误文案 | |------|------|----------| | 邮编 | `^\d{5}(-\d{4})?$` | 邮编格式无效 | | 重量 | 1–50000(换算后 lb) | 重量超出范围 | | 件数 | 1–999 整数 | 件数无效 | --- ### 2.5 P04 询价结果页 `/quote/result/[quoteId]` **页面结构**: - Header:面包屑「卡派查价 / 报价详情」 - 主区:QuoteCard(双等级 Tab)+ 明细 breakdown - 操作区:「重新询价」「查看历史」 **状态**:`loading` | `success` | `fallback` | `error` | `expired` | 状态 | UI | |------|-----| | loading | 全页 Skeleton | | success | QuoteCard + CountdownTimer | | fallback | QuoteCard + WarningBanner | | error | ErrorBanner「无法获取报价」 | | expired | 过期提示 + 跳转 `/quote` 按钮 | **数据来源**:`GET /api/quotes/{quoteId}` --- ### 2.6 P05 询价历史 `/history` **页面结构**: - Header:标题「询价历史」 - 主区:表格(时间、路线摘要、价格、来源、状态) - 操作区:分页器(page/size 必填) **状态**:`loading` | `success` | `empty` | `error` | 状态 | UI | |------|-----| | loading | 表格 Skeleton 5 行 | | success | 数据表格 + 分页 | | empty | EmptyState「暂无询价记录」+ 按钮「去查价」 | | error | ErrorBanner | **行点击**:跳转 `/quote/result/{quoteId}` --- ### 2.7 P06 加价配置 `/admin/markup` **页面结构**: - Header:标题 + 搜索框(customer_id) - 主区:表格(客户 ID、加价比例、操作人、更新时间、操作) - 操作区:分页 **编辑交互**:行内「编辑」→ 右侧 Drawer - 字段:加价比例(NumberInput,0–30,步长 0.1) - 按钮:保存(Primary)/ 取消(Secondary) **状态**:`loading` | `success` | `empty` | `error` | `saving` | 状态 | UI | |------|-----| | saving | 保存按钮 loading,Drawer 内字段 disabled | | 校验失败 | 字段下「加价比例不能超过 30%」 | --- ### 2.8 P07 预警中心 `/admin/alerts` **页面结构**: - Header:标题 + 筛选(类型、状态 open/resolved) - 主区:表格(时间、类型、quote_id、偏差%、状态) - 详情:行点击展开 Drawer(detail_json) - 操作区:「标记已处理」 **预警类型标签色**: | 类型 | 标签色 | |------|--------| | PRICE_DEVIATION | Warning | | STALE_FALLBACK | Warning | | RPA_FAILED | Error | | RPA_CAPTCHA | Error | | STRUCT_CHANGE | Error | --- ## 3. 视觉设计(Visual Design System) ### 3.1 色彩系统 | Token | HEX | 用途 | |-------|-----|------| | `--color-primary` | `#0D9488` | 主按钮、链接、焦点环 | | `--color-primary-hover` | `#0F766E` | 主按钮 hover | | `--color-primary-active` | `#115E59` | 主按钮 active | | `--color-success` | `#16A34A` | 成功状态、实时报价标识 | | `--color-warning` | `#D97706` | 非实时角标、倒计时 <30s、预警 | | `--color-error` | `#DC2626` | 错误、失败状态 | | `--color-bg` | `#F8FAFC` | 页面背景 | | `--color-surface` | `#FFFFFF` | 卡片、表单背景 | | `--color-border` | `#E2E8F0` | 分割线、输入框边框 | | `--color-text-primary` | `#0F172A` | 标题、正文 | | `--color-text-secondary` | `#64748B` | 辅助说明、标签 | | `--color-text-disabled` | `#94A3B8` | 禁用文字 | **Tailwind 映射**:primary=`teal-600`,neutral=`slate` ### 3.2 字体系统 | Token | 字体 | 字号 | 字重 | 行高 | 用途 | |-------|------|------|------|------|------| | `--font-display` | Geist Sans | 30px / 1.875rem | 600 | 1.2 | 页面 H1 | | `--font-h2` | Geist Sans | 24px / 1.5rem | 600 | 1.3 | 区块标题 | | `--font-h3` | Geist Sans | 18px / 1.125rem | 600 | 1.4 | 卡片标题 | | `--font-body` | Geist Sans | 16px / 1rem | 400 | 1.6 | 正文 | | `--font-caption` | Geist Sans | 14px / 0.875rem | 400 | 1.5 | 辅助说明 | | `--font-label` | Geist Sans | 14px / 0.875rem | 500 | 1.4 | 表单标签 | | `--font-mono` | Geist Mono | 16px / 1rem | 500 | 1.4 | 金额、quote_id | **规则**: - 金额、单号使用 `font-mono` - 正文最大宽度 `max-w-[65ch]` ### 3.3 间距系统(Spacing) | Token | 值 | 用途 | |-------|-----|------| | `--space-1` | 4px | 图标与文字间距 | | `--space-2` | 8px | 字段内 padding、标签间距 | | `--space-3` | 12px | 紧凑列表项 | | `--space-4` | 16px | 表单项间距、卡片内 padding | | `--space-6` | 24px | 区块间距 | | `--space-8` | 32px | 页面边距、卡片间距 | **页面边距**:`px-4 md:px-6 lg:px-8`;内容区 `max-w-7xl mx-auto` ### 3.4 圆角系统 | Token | 值 | 用途 | |-------|-----|------| | `--radius-sm` | 6px | 输入框、标签 | | `--radius-md` | 8px | 按钮 | | `--radius-lg` | 12px | 卡片 | | `--radius-xl` | 16px | 弹窗 | ### 3.5 阴影系统 | Token | 值 | 用途 | |-------|-----|------| | `--shadow-card` | `0 1px 3px rgba(15,23,42,0.06), 0 1px 2px rgba(15,23,42,0.04)` | 卡片 | | `--shadow-modal` | `0 10px 25px rgba(15,23,42,0.12)` | 弹窗、Drawer | | `--shadow-none` | none | 表格行、内嵌面板 | ### 3.6 组件风格 #### Button | 变体 | 背景 | 文字 | 边框 | 高度 | |------|------|------|------|------| | Primary | `#0D9488` | `#FFFFFF` | none | 40px | | Secondary | `#FFFFFF` | `#0F172A` | `#E2E8F0` | 40px | | Ghost | transparent | `#0D9488` | none | 40px | | Danger | `#DC2626` | `#FFFFFF` | none | 40px | `border-radius: 8px`;`padding: 0 16px`;loading 时左侧 16px 旋转 Spinner #### Input - 高度 40px;`border: 1px solid #E2E8F0`;`radius: 6px` - Focus:`ring-2 ring-teal-600/20 border-teal-600` - Error:`border-error` + 下方 12px 红色 caption #### Card(QuoteCard) - 背景 `#FFFFFF`;`border: 1px solid #E2E8F0`;`radius: 12px`;`padding: 24px`;`shadow-card` - 顶部 Tab:「标准」「保证送达」 - 价格区:`font-mono text-3xl font-semibold` - 非实时角标:右上角 Warning 色 pill「非实时报价,仅供参考」 #### Loading - 全页/区块:Skeleton(灰条 `bg-slate-200 animate-pulse`) - 按钮内:`Spinner` 16×16,`border-2 border-white/30 border-t-white` - 查价进度:线性进度条 + 文案「正在查询报价…({elapsed}s / 30s)」 #### Error 提示 - `ErrorBanner`:背景 `#FEF2F2`,左边框 4px `#DC2626`,内边距 16px - `WarningBanner`:背景 `#FFFBEB`,左边框 4px `#D97706` --- ## 4. 设计规范(Design System for AI Coding) ### 4.1 UI 组件清单 | 组件名 | 路径 | 用途 | |--------|------|------| | `PrimaryButton` | `components/ui/primary-button.tsx` | 主操作 | | `SecondaryButton` | `components/ui/secondary-button.tsx` | 取消、次要操作 | | `InputField` | `components/ui/input-field.tsx` | 文本输入 + label + error | | `SelectField` | `components/ui/select-field.tsx` | 货物类型、州 | | `QuoteForm` | `components/quote/quote-form.tsx` | 查价表单 | | `QuoteCard` | `components/quote/quote-card.tsx` | 双等级报价展示 | | `QuoteResultPanel` | `components/quote/quote-result-panel.tsx` | 结果区容器(含状态切换) | | `CountdownTimer` | `components/quote/countdown-timer.tsx` | 3 分钟倒计时 | | `LoadingSpinner` | `components/ui/loading-spinner.tsx` | 按钮/行内 loading | | `Skeleton` | `components/ui/skeleton.tsx` | 骨架屏 | | `ErrorBanner` | `components/ui/error-banner.tsx` | 错误提示 | | `WarningBanner` | `components/ui/warning-banner.tsx` | 非实时/预警提示 | | `EmptyState` | `components/ui/empty-state.tsx` | 空列表 | | `ConfirmDialog` | `components/ui/confirm-dialog.tsx` | 过期重询确认 | | `DataTable` | `components/ui/data-table.tsx` | 历史/加价/预警表格 | | `PageHeader` | `components/layout/page-header.tsx` | 标题区 | | `AppLayout` | `components/layout/app-layout.tsx` | 客户布局 | | `AdminLayout` | `components/layout/admin-layout.tsx` | 管理端布局 | ### 4.2 状态规范(统一枚举) ```typescript // 查价页 type QuotePageStatus = | 'idle' | 'validating' | 'processing' | 'success' | 'fallback' | 'error' | 'expired'; // 通用数据页 type DataPageStatus = 'loading' | 'success' | 'empty' | 'error'; // 按钮 type ButtonState = 'default' | 'loading' | 'disabled'; ``` | 状态 | 视觉 | 交互 | |------|------|------| | loading | Skeleton 或 Spinner | 禁止重复提交 | | success | 正常内容 + Success/Primary 强调 | 可操作 | | error | ErrorBanner | 展示重试 | | empty | EmptyState 插画 + CTA | 引导主路径 | | disabled | opacity 0.5 + cursor-not-allowed | 不响应点击 | ### 4.3 命名规范 | 类型 | 规则 | 示例 | |------|------|------| | React 组件 | PascalCase | `QuoteCard` | | 文件 | kebab-case | `quote-card.tsx` | | Props 接口 | 组件名 + Props | `QuoteCardProps` | | 页面状态 hook | use + 页面 + Status | `useQuotePageStatus` | | Tailwind class | 语义组合,禁止随意 magic number | `text-text-secondary` 用 CSS 变量 | | API 状态字段 | 与 PRD 一致 | `status`, `is_realtime`, `source_type` | ### 4.4 UI 约束规则(写死) | ID | 规则 | |----|------| | UI-01 | 所有触发 API 的按钮必须有 `loading` 状态 | | UI-02 | 所有 API 请求必须有 `error` 状态展示(ErrorBanner) | | UI-03 | 所有表单字段必须有 `validation` 状态(字段下错误文案) | | UI-04 | 查价提交按钮提交后 disabled 3000ms | | UI-05 | 轮询最长 30s,超时进入 `error` 状态 | | UI-06 | `is_realtime=false` 必须显示 WarningBanner,不可隐藏 | | UI-07 | 金额必须用 `font-mono`,保留 2 位小数 | | UI-08 | 界面文案全部中文 | | UI-09 | 禁止使用 emoji 作为 UI 图标;使用 `@phosphor-icons/react` | | UI-10 | 移动端所有页面单列布局,禁止横向滚动 | | UI-11 | 全高区域使用 `min-h-[100dvh]`,禁止 `h-screen` | ### 4.5 Tailwind 配置片段(globals.css) ```css @tailwind base; @tailwind components; @tailwind utilities; :root { --color-primary: #0d9488; --color-primary-hover: #0f766e; --color-success: #16a34a; --color-warning: #d97706; --color-error: #dc2626; --color-bg: #f8fafc; --color-surface: #ffffff; --color-border: #e2e8f0; --color-text-primary: #0f172a; --color-text-secondary: #64748b; --radius-md: 8px; --radius-lg: 12px; --space-4: 16px; --space-6: 24px; --space-8: 32px; } body { @apply bg-[var(--color-bg)] text-[var(--color-text-primary)] antialiased; font-family: var(--font-geist-sans), system-ui, sans-serif; } ``` ### 4.6 响应式断点 | 断点 | 宽度 | 布局规则 | |------|------|----------| | default | <768px | 单列;查价表单全宽;隐藏侧栏改底部 Tab | | `md` | ≥768px | 查价页左右 60/40 分栏 | | `lg` | ≥1024px | 管理端显示固定侧栏 240px | | `xl` | ≥1280px | 内容区 `max-w-7xl` 居中 | ### 4.7 页面 ↔ 组件映射(Cursor 直接生成) | 页面 | 主要组件 | |------|----------| | `/login` | `InputField` ×2, `PrimaryButton`, `ErrorBanner` | | `/dashboard` | `PageHeader`, 快捷入口 Card ×N | | `/quote` | `QuoteForm`, `QuoteResultPanel`, `QuoteCard`, `CountdownTimer`, `ConfirmDialog` | | `/quote/result/[id]` | `QuoteCard`, `CountdownTimer`, `WarningBanner` | | `/history` | `DataTable`, `EmptyState`, 分页 | | `/admin/markup` | `DataTable`, Drawer + `InputField`, `PrimaryButton` | | `/admin/alerts` | `DataTable`, 类型 Tag, Drawer 详情 | --- ## 附录 A:与 PRD / 技术设计映射 | PRD/技术项 | UI 实现 | |------------|---------| | GD-5 异步轮询 | `/quote` processing 状态 + 2s 轮询 | | 双等级报价 | `QuoteCard` Tab:标准 / 保证送达 | | 3 分钟有效期 | `CountdownTimer` | | is_realtime=false | `WarningBanner` | | 过期重询 | `ConfirmDialog` | | 运营加价 | `/admin/markup` Drawer | | 预警中心 | `/admin/alerts` 表格 + 处理按钮 | ## 附录 B:文案清单(中文) | 场景 | 文案 | |------|------| | 提交查价 | 获取报价 | | 处理中 | 正在查询报价,请稍候… | | 非实时 | 非实时报价,仅供参考 | | 失败 | 暂时无法获取报价,请稍后重试 | | 超时 | 查询超时,请重试 | | 过期确认 | 报价已过期,是否重新询价? | | 空历史 | 暂无询价记录 | ## 附录 C:图标(Phosphor) | 场景 | 图标名 | |------|--------| | 查价 | `Truck` | | 历史 | `ClockCounterClockwise` | | 预警 | `Warning` | | 成功 | `CheckCircle` | | 错误 | `XCircle` | | 加载 | `CircleNotch`(旋转) |