You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chajia/docs/查价系统-UI设计.md

560 lines
20 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 查价系统 产品 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`
**页面结构**
- HeaderLogo + 产品名
- 主区:登录卡片(用户名、密码)
- 操作区:登录按钮
**状态**`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`(核心)
**页面结构**
```
┌─────────────────────────────────────────────┐
│ HeaderAppLayout 顶栏) │
├─────────────────────────────────────────────┤
│ 页面标题:卡派查价 │
│ 副标题:填写货物信息,获取实时报价(约 30 秒内) │
├──────────────────┬──────────────────────────┤
│ 主输入区(左 60% │ 结果区(右 40%,空时占位) │
│ QuoteForm │ QuoteResultPanel │
├──────────────────┴──────────────────────────┤
│ 操作区获取报价PrimaryButton
└─────────────────────────────────────────────┘
```
**移动端(<768px**:单列;输入区在上,结果区在下。
**页面状态机**
| 状态 | 触发 | 输入区 | 结果区 | 操作区 |
|------|------|--------|--------|--------|
| `idle` | 进入页面 | 可编辑 | 空状态插画 +「填写信息后获取报价」 | 按钮可点 |
| `validating` | 点击提交 | 只读 | 不变 | 按钮 loading |
| `processing` | POST 成功且 status=processing | 只读 | Skeleton +「正在查询报价…」+ 进度条(030s | 按钮 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})?$` | 邮编格式无效 |
| 重量 | 150000(换算后 lb | 重量超出范围 |
| 件数 | 1999 整数 | 件数无效 |
---
### 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
- 字段:加价比例(NumberInput030,步长 0.1
- 按钮:保存(Primary/ 取消(Secondary
**状态**`loading` | `success` | `empty` | `error` | `saving`
| 状态 | UI |
|------|-----|
| saving | 保存按钮 loadingDrawer 内字段 disabled |
| 校验失败 | 字段下「加价比例不能超过 30%」 |
---
### 2.8 P07 预警中心 `/admin/alerts`
**页面结构**
- Header:标题 + 筛选(类型、状态 open/resolved
- 主区:表格(时间、类型、quote_id、偏差%、状态)
- 详情:行点击展开 Drawerdetail_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
#### CardQuoteCard
- 背景 `#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`(旋转) |