20 KiB
查价系统 产品 UI/UX 设计方案 v1.0
| 版本 | 日期 | 作者 | 说明 |
|---|---|---|---|
| v1.0 | 2026-06-16 | 产品设计/UI/UX | 可直接用于前端开发与 Cursor 生成代码 |
配套文档:查价系统-PRD.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/*→/403operator访问/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 |
关键交互:
- 点击「获取报价」→ 前端生成
request_id(UUID v4) →POST /api/quotes - 若立即
done(L1/L2 命中)→ 直接success/fallback - 若
processing→ 每 2sGET /api/quotes/{quoteId},最多 15 次 success后展示 3 分钟倒计时CountdownTimer;<30s 时文字变 Warning 色- 防重复:提交后按钮 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) - 按钮内:
Spinner16×16,border-2 border-white/30 border-t-white - 查价进度:线性进度条 + 文案「正在查询报价…({elapsed}s / 30s)」
Error 提示
ErrorBanner:背景#FEF2F2,左边框 4px#DC2626,内边距 16pxWarningBanner:背景#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 状态规范(统一枚举)
// 查价页
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)
@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(旋转) |