575 lines
17 KiB
Markdown
575 lines
17 KiB
Markdown
# 设计文档
|
||
|
||
## 概述
|
||
|
||
珠宝商城微信小程序基于 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 Plus(Web 端) |
|
||
| 后端 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 操作
|
||
- 订单创建和状态流转
|
||
- 购物车操作
|
||
- 版房搜索
|
||
- 规格数据导入导出
|