JewelryMall/.kiro/specs/jewelry-mall/design.md
2026-02-14 19:29:15 +08:00

17 KiB
Raw Permalink Blame History

设计文档

概述

珠宝商城微信小程序基于 uni-app (Vue 3) 开发,采用前后端分离架构。前端为微信小程序(用户端)和 Web 管理后台(管理员端),后端提供统一的 RESTful API 服务。系统核心围绕商品展示、购物车、订单管理、钻戒计算器和版房展示五大模块构建,不接入线上支付和物流追踪 API。

架构

整体架构

graph TB
    subgraph 用户端["用户端 (微信小程序)"]
        MP[uni-app Vue 3 小程序]
    end

    subgraph 管理端["管理端 (Web)"]
        ADMIN[管理后台 Web 应用]
    end

    subgraph 后端服务["后端 API 服务"]
        API[RESTful API]
        AUTH[鉴权模块]
    end

    subgraph 数据层["数据层"]
        DB[(MySQL 数据库)]
        OSS[文件存储/OSS]
    end

    MP -->|HTTP 请求| API
    ADMIN -->|HTTP 请求| API
    API --> AUTH
    API --> DB
    API --> OSS

前端页面结构

graph LR
    subgraph TabBar["底部 TabBar"]
        HOME[首页/商品列表]
        CART[购物车]
        MOLD[版房专区]
        MINE[我的]
    end

    HOME --> PRODUCT_DETAIL[商品详情]
    HOME --> CATEGORY[分类筛选]
    PRODUCT_DETAIL --> SPEC_PANEL[详细参数面板]
    CART --> ORDER_SUBMIT[订单提交]
    ORDER_SUBMIT --> ORDER_DETAIL[订单详情]
    MINE --> ORDER_LIST[订单列表]
    ORDER_LIST --> ORDER_DETAIL
    MINE --> ADDRESS[收货地址管理]
    MINE --> ABOUT[关于我们]

    CALC[钻戒计算器] -.->|独立入口| TabBar

技术选型

层级 技术方案
前端框架 uni-app + Vue 3 + Composition API
状态管理 Pinia
网络请求 uni.request 封装
UI 组件 uni-ui + 自定义组件
管理后台 Vue 3 + Element PlusWeb 端)
后端 API Node.js + Express / Koa
数据库 MySQL
文件存储 本地存储 / 云存储 OSS

组件与接口

前端核心组件

1. 页面组件

组件 路径 职责
HomePage pages/home/index.vue 商品列表、分类筛选
ProductDetail pages/product/detail.vue 商品详情、banner、规格选择
CartPage pages/cart/index.vue 购物车管理
OrderSubmit pages/order/submit.vue 订单提交
OrderDetail pages/order/detail.vue 订单详情
OrderList pages/order/list.vue 订单列表
MoldGallery pages/mold/index.vue 版房专区
RingCalculator pages/calculator/index.vue 钻戒计算器
MinePage pages/mine/index.vue 我的页面
AddressManage pages/address/index.vue 收货地址管理

2. 公共组件

组件 职责
ProductCard 商品卡片(列表项)
SpecPanel 详细参数选择弹窗
BannerSwiper 商品详情顶部轮播(图片+视频)
CustomerServiceBtn 客服按钮(二维码/小程序客服)
ShippingNotice 统一发货公告

API 接口设计

商品模块

GET    /api/products              # 商品列表(支持分类筛选、分页)
GET    /api/products/:id          # 商品详情(含 Base_Attribute
GET    /api/products/:id/specs    # 商品详细参数选项
POST   /api/products/:id/spec-data  # 根据参数组合获取 Spec_Data_List
GET    /api/categories            # 商品分类列表

购物车模块

GET    /api/cart                  # 获取购物车列表
POST   /api/cart                  # 添加商品到购物车
PUT    /api/cart/:id              # 更新购物车项(数量等)
DELETE /api/cart/:id              # 删除购物车项

订单模块

POST   /api/orders                # 提交订单
GET    /api/orders                # 获取用户订单列表
GET    /api/orders/:id            # 获取订单详情
PUT    /api/orders/:id/cancel     # 取消订单

版房模块

GET    /api/molds                 # 版房列表(支持搜索)
GET    /api/molds/:id             # 版房详情

用户模块

POST   /api/auth/wx-login         # 微信登录
GET    /api/user/profile          # 用户信息
GET    /api/user/addresses        # 收货地址列表
POST   /api/user/addresses        # 新增收货地址
PUT    /api/user/addresses/:id    # 编辑收货地址
DELETE /api/user/addresses/:id    # 删除收货地址

管理后台接口

# 商品管理
POST   /api/admin/products           # 创建商品
PUT    /api/admin/products/:id       # 编辑商品
DELETE /api/admin/products/:id       # 删除商品
POST   /api/admin/products/import    # 规格数据表格导入
GET    /api/admin/products/export    # 规格数据表格导出
GET    /api/admin/stock-alerts       # 库存预警列表

# 订单管理
POST   /api/admin/orders             # 手动创建订单
PUT    /api/admin/orders/:id         # 修改订单
PUT    /api/admin/orders/:id/status  # 更新订单状态(已支付/已发货)

# 版房管理
POST   /api/admin/molds              # 创建版房信息
PUT    /api/admin/molds/:id          # 编辑版房信息
DELETE /api/admin/molds/:id          # 删除版房信息

# 文件上传
POST   /api/admin/upload             # 上传图片/视频/支付凭证

数据模型

商品 (Product)

interface Product {
  id: number
  name: string              // 商品名称
  basePrice: number         // 基础价格
  styleNo: string           // 款号
  stock: number             // 库存数量
  totalStock: number        // 总库存(用于计算库存百分比)
  loss: number              // 损耗
  laborCost: number         // 工费
  categoryId: number        // 分类 ID
  bannerImages: string[]    // banner 图片 URL 列表
  bannerVideo?: string      // banner 视频 URL
  status: 'on' | 'off'     // 上架/下架
  createdAt: string
  updatedAt: string
}

商品分类 (Category)

interface Category {
  id: number
  name: string
  parentId?: number
  sort: number
}

详细参数配置 (DetailParameterConfig)

interface DetailParameterConfig {
  id: number
  productId: number
  fineness: string[]        // 成色选项列表
  mainStone: string[]       // 主石选项列表
  ringSize: string[]        // 手寸选项列表
}

规格数据 (SpecData)

interface SpecData {
  id: number
  productId: number
  modelName: string         // 商品型号名称
  fineness: string          // 成色
  mainStone: string         // 主石
  ringSize: string          // 手寸
  goldTotalWeight: number   // 金料总重
  goldNetWeight: number     // 金料净重
  loss: number              // 损耗
  goldLoss: number          // 金耗
  goldPrice: number         // 金价
  goldValue: number         // 金值
  mainStoneCount: number    // 主石数量
  mainStoneWeight: number   // 主石石重
  mainStoneUnitPrice: number // 主石单价
  mainStoneAmount: number   // 主石金额
  sideStoneCount: number    // 副石数量
  sideStoneWeight: number   // 副石石重
  sideStoneUnitPrice: number // 副石单价
  sideStoneAmount: number   // 副石金额
  accessoryAmount: number   // 配件金额
  processingFee: number     // 加工工费
  settingFee: number        // 镶石工费
  totalLaborCost: number    // 总工费
  totalPrice: number        // 总价
}

购物车项 (CartItem)

interface CartItem {
  id: number
  userId: number
  productId: number
  specDataId: number        // 关联的规格数据
  quantity: number
  product: Product          // 关联商品信息
  specData: SpecData        // 关联规格数据
  checked: boolean          // 前端勾选状态(本地)
}

订单 (Order)

interface Order {
  id: number
  orderNo: string           // 订单号
  userId: number
  status: 'pending' | 'paid' | 'shipped' | 'cancelled'
  totalPrice: number
  // 收货信息
  receiverName: string
  receiverPhone: string
  receiverAddress: string
  // 支付信息(已支付后填充)
  paymentTime?: string
  paymentProof?: string     // 支付凭证图片 URL
  // 物流信息(已发货后填充)
  shippingCompany?: string  // 物流公司名
  shippingNo?: string       // 物流单号
  createdAt: string
  updatedAt: string
}

订单商品项 (OrderItem)

interface OrderItem {
  id: number
  orderId: number
  productId: number
  specDataId: number
  quantity: number
  unitPrice: number
  product: Product
  specData: SpecData
}

版房信息 (MoldInfo)

interface MoldInfo {
  id: number
  name: string              // 名称
  styleNo?: string          // 款号
  barcodeNo?: string        // 条码号
  style?: string            // 款式
  images: string[]          // 展示图片 URL 列表
  createdAt: string
  updatedAt: string
}

收货地址 (Address)

interface Address {
  id: number
  userId: number
  name: string
  phone: string
  province: string
  city: string
  district: string
  detail: string
  isDefault: boolean
}

用户 (User)

interface User {
  id: number
  openId: string            // 微信 openId
  nickname: string
  avatar: string
  createdAt: string
}

钻戒计算器输入/输出 (RingCalculatorData)

interface RingCalculatorInput {
  goldWeight: number        // 金重(g)
  mainStoneWeight: number   // 主石重(ct)
  sideStoneWeight: number   // 副石重(ct)
  lossRate: number          // 损耗(倍率)
  moldGoldPrice: number     // 倒模金价(元)
  mainStoneUnitPrice: number // 主石单价(元)
  sideStoneUnitPrice: number // 副石单价(元)
  sideStoneCount: number    // 副石粒数(p)
  microSettingFee: number   // 微镶费(元/粒)
  mainStoneSettingFee: number // 主石镶费(元)
  threeDFee: number         // 3D起板费(元)
  basicLaborCost: number    // 基本工费(元)
  otherCost: number         // 其他费用(元)
}

interface RingCalculatorResult {
  netGoldWeight: number     // 净金重(g)
  weightWithLoss: number    // 含耗重(g)
  goldValue: number         // 金值(元)
  mainStoneTotal: number    // 主石总价(元)
  sideStoneTotal: number    // 副石总价(元)
  microSettingTotal: number // 微镶总价(元)
  totalPrice: number        // 总价(元)
}

钻戒计算器核心计算逻辑(纯函数)

function calculateRing(input: RingCalculatorInput): RingCalculatorResult {
  const netGoldWeight = input.goldWeight - input.mainStoneWeight * 0.2 - input.sideStoneWeight * 0.2
  const weightWithLoss = netGoldWeight * input.lossRate
  const goldValue = weightWithLoss * input.moldGoldPrice
  const mainStoneTotal = input.mainStoneWeight * input.mainStoneUnitPrice
  const sideStoneTotal = input.sideStoneWeight * input.sideStoneUnitPrice
  const microSettingTotal = input.sideStoneCount * input.microSettingFee
  const totalPrice = goldValue + mainStoneTotal + sideStoneTotal
    + input.mainStoneSettingFee + microSettingTotal
    + input.threeDFee + input.basicLaborCost + input.otherCost

  return {
    netGoldWeight,
    weightWithLoss,
    goldValue,
    mainStoneTotal,
    sideStoneTotal,
    microSettingTotal,
    totalPrice,
  }
}

正确性属性

属性Property是指在系统所有有效执行中都应成立的特征或行为——本质上是对系统应做什么的形式化陈述。属性是人类可读规格与机器可验证正确性保证之间的桥梁。

Property 1购物车添加商品不变量

对于任意 商品和有效规格组合,将其添加到购物车后,购物车中应包含该商品和规格的条目,且购物车项数增加 1。

Validates: Requirements 2.1

Property 2购物车总金额计算正确性

对于任意 购物车商品集合和勾选状态组合,展示的总金额应等于所有已勾选商品的单价乘以数量之和。

Validates: Requirements 2.3

Property 3订单状态与展示字段匹配

对于任意 订单及其状态(待支付/已支付/已发货),订单详情展示的信息字段应与状态匹配:已支付状态应包含支付时间,已发货状态应包含物流公司名称和物流单号。

Validates: Requirements 3.5, 3.6

Property 4钻戒计算器公式正确性

对于任意 有效的 RingCalculatorInput所有数值字段为非负数calculateRing 函数的输出应满足:

  • netGoldWeight = goldWeight - mainStoneWeight × 0.2 - sideStoneWeight × 0.2
  • weightWithLoss = netGoldWeight × lossRate
  • goldValue = weightWithLoss × moldGoldPrice
  • mainStoneTotal = mainStoneWeight × mainStoneUnitPrice
  • sideStoneTotal = sideStoneWeight × sideStoneUnitPrice
  • microSettingTotal = sideStoneCount × microSettingFee
  • totalPrice = goldValue + mainStoneTotal + sideStoneTotal + mainStoneSettingFee + microSettingTotal + threeDFee + basicLaborCost + otherCost

Validates: Requirements 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8

Property 5版房搜索结果匹配

对于任意 关键词和版房数据集,搜索返回的每一条结果的名称、款号、条码号或款式中至少有一个字段包含该关键词。

Validates: Requirements 5.3

Property 6规格数据导入导出 Round-Trip

对于任意 有效的商品规格数据集合,导出为表格文件后再导入,应得到与原始数据等价的规格数据集合。

Validates: Requirements 8.3

Property 7库存预警阈值判断

对于任意 商品集合,库存预警列表应恰好包含所有 stock/totalStock < 0.1 的商品,且按库存数量从少到多排序。

Validates: Requirements 8.4

Property 8订单价格自动重算

对于任意 订单及其商品项变更(修改商品或型号),变更后的订单总价应等于所有订单项的单价乘以数量之和。

Validates: Requirements 9.4

错误处理

前端错误处理

场景 处理方式
网络请求失败 展示友好提示(如"网络异常,请稍后重试"),提供重试按钮
商品已下架/售罄 在商品详情页和购物车中标记状态,禁止加入购物车或下单
购物车为空时点击下单 提示"请先选择商品"
订单提交失败 提示错误原因,保留用户填写的信息,支持重新提交
钻戒计算器输入非法值 输入框校验,提示"请输入有效数字",不执行计算
版房搜索无结果 展示"未找到匹配结果"提示
微信登录失败 提示用户重新授权,引导至授权页面
图片/视频加载失败 展示占位图,不影响页面其他内容

后端错误处理

场景 处理方式
请求参数校验失败 返回 400 状态码,附带具体字段错误信息
资源不存在 返回 404 状态码
未授权访问 返回 401 状态码,前端引导重新登录
管理员权限不足 返回 403 状态码
库存不足 返回业务错误码,前端提示"库存不足"
订单状态流转非法 返回业务错误码(如已取消的订单不能改为已支付)
规格数据导入格式错误 返回具体行号和错误原因,支持部分导入
文件上传失败 返回错误信息,前端提示重新上传
数据库操作异常 记录错误日志,返回 500 状态码,前端展示通用错误提示

订单状态流转规则

stateDiagram-v2
    [*] --> 待支付 : 提交订单
    待支付 --> 已支付 : 后台确认支付
    已支付 --> 已发货 : 后台确认发货
    待支付 --> 已取消 : 用户取消
    已取消 --> [*]
    已发货 --> [*]

合法状态流转:

  • 待支付 → 已支付(仅后台操作)
  • 已支付 → 已发货(仅后台操作)
  • 待支付 → 已取消(用户或后台操作)
  • 其他状态流转均为非法操作

测试策略

测试框架选型

类型 工具
单元测试 Vitest
属性测试 fast-check + Vitest
组件测试 @vue/test-utils + Vitest
API 测试 Supertest + Vitest

属性测试Property-Based Testing

使用 fast-check 库进行属性测试,每个属性测试至少运行 100 次迭代。

每个属性测试必须以注释标注对应的设计文档属性编号:

// Feature: jewelry-mall, Property 4: 钻戒计算器公式正确性

属性测试覆盖的正确性属性:

  • Property 1购物车添加商品不变量
  • Property 2购物车总金额计算正确性
  • Property 3订单状态与展示字段匹配
  • Property 4钻戒计算器公式正确性
  • Property 5版房搜索结果匹配
  • Property 6规格数据导入导出 Round-Trip
  • Property 7库存预警阈值判断
  • Property 8订单价格自动重算

每个正确性属性由一个独立的属性测试实现。

单元测试

单元测试覆盖具体示例、边界情况和错误条件:

  • 钻戒计算器:输入为 0 的边界情况、负数输入校验
  • 购物车:空购物车操作、重复添加同一商品
  • 订单:非法状态流转、缺少必填字段
  • 版房搜索:空关键词、特殊字符
  • 库存预警:库存恰好为 10% 的边界值
  • 规格数据导入:格式错误、缺少必填列

组件测试

使用 @vue/test-utils 测试关键 Vue 组件的渲染和交互逻辑:

  • 商品详情页 banner 渲染
  • 购物车勾选和金额计算
  • 订单提交页贵重物品提醒勾选逻辑
  • 钻戒计算器输入和结果展示

API 集成测试

使用 Supertest 测试后端 API 的请求/响应:

  • 商品 CRUD 操作
  • 订单创建和状态流转
  • 购物车操作
  • 版房搜索
  • 规格数据导入导出