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

575 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 设计文档
## 概述
珠宝商城微信小程序基于 uni-app (Vue 3) 开发,采用前后端分离架构。前端为微信小程序(用户端)和 Web 管理后台(管理员端),后端提供统一的 RESTful API 服务。系统核心围绕商品展示、购物车、订单管理、钻戒计算器和版房展示五大模块构建,不接入线上支付和物流追踪 API。
## 架构
### 整体架构
```mermaid
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
```
### 前端页面结构
```mermaid
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)
```typescript
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)
```typescript
interface Category {
id: number
name: string
parentId?: number
sort: number
}
```
### 详细参数配置 (DetailParameterConfig)
```typescript
interface DetailParameterConfig {
id: number
productId: number
fineness: string[] // 成色选项列表
mainStone: string[] // 主石选项列表
ringSize: string[] // 手寸选项列表
}
```
### 规格数据 (SpecData)
```typescript
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)
```typescript
interface CartItem {
id: number
userId: number
productId: number
specDataId: number // 关联的规格数据
quantity: number
product: Product // 关联商品信息
specData: SpecData // 关联规格数据
checked: boolean // 前端勾选状态(本地)
}
```
### 订单 (Order)
```typescript
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)
```typescript
interface OrderItem {
id: number
orderId: number
productId: number
specDataId: number
quantity: number
unitPrice: number
product: Product
specData: SpecData
}
```
### 版房信息 (MoldInfo)
```typescript
interface MoldInfo {
id: number
name: string // 名称
styleNo?: string // 款号
barcodeNo?: string // 条码号
style?: string // 款式
images: string[] // 展示图片 URL 列表
createdAt: string
updatedAt: string
}
```
### 收货地址 (Address)
```typescript
interface Address {
id: number
userId: number
name: string
phone: string
province: string
city: string
district: string
detail: string
isDefault: boolean
}
```
### 用户 (User)
```typescript
interface User {
id: number
openId: string // 微信 openId
nickname: string
avatar: string
createdAt: string
}
```
### 钻戒计算器输入/输出 (RingCalculatorData)
```typescript
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 // 总价(元)
}
```
### 钻戒计算器核心计算逻辑(纯函数)
```typescript
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 状态码前端展示通用错误提示 |
### 订单状态流转规则
```mermaid
stateDiagram-v2
[*] --> 待支付 : 提交订单
待支付 --> 已支付 : 后台确认支付
已支付 --> 已发货 : 后台确认发货
待支付 --> 已取消 : 用户取消
已取消 --> [*]
已发货 --> [*]
```
合法状态流转
- 待支付 已支付仅后台操作
- 已支付 已发货仅后台操作
- 待支付 已取消用户或后台操作
- 其他状态流转均为非法操作
## 测试策略
### 测试框架选型
| 类型 | 工具 |
|------|------|
| 单元测试 | Vitest |
| 属性测试 | fast-check + Vitest |
| 组件测试 | @vue/test-utils + Vitest |
| API 测试 | Supertest + Vitest |
### 属性测试Property-Based Testing
使用 fast-check 库进行属性测试每个属性测试至少运行 100 次迭代
每个属性测试必须以注释标注对应的设计文档属性编号
```typescript
// 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 操作
- 订单创建和状态流转
- 购物车操作
- 版房搜索
- 规格数据导入导出