HaniBlindBox/.kiro/specs/goods-management-frontend/design.md
2026-01-17 18:04:08 +08:00

434 lines
12 KiB
Markdown
Raw 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.

# 商品管理模块前端设计文档
## 架构设计
### 目录结构
```
src/views/business/goods/
├── index.vue # 盒子列表页面
├── type.vue # 盒子类型管理页面
├── components/
│ ├── GoodsSearchForm.vue # 盒子搜索表单
│ ├── GoodsTable.vue # 盒子列表表格
│ ├── GoodsAddDialog.vue # 盒子新增弹窗
│ ├── GoodsEditDialog.vue # 盒子编辑弹窗
│ ├── GoodsExtendDialog.vue # 盒子扩展设置弹窗
│ ├── PrizeListDialog.vue # 奖品列表弹窗
│ ├── PrizeAddDialog.vue # 奖品新增弹窗
│ ├── PrizeEditDialog.vue # 奖品编辑弹窗
│ ├── TypeAddDialog.vue # 类型新增弹窗
│ └── TypeEditDialog.vue # 类型编辑弹窗
src/api/business/
├── goods.ts # 商品API接口
```
### 组件关系图
```
index.vue (盒子列表)
├── GoodsSearchForm.vue (搜索表单)
├── GoodsTable.vue (列表表格)
│ ├── GoodsAddDialog.vue (新增弹窗)
│ ├── GoodsEditDialog.vue (编辑弹窗)
│ ├── GoodsExtendDialog.vue (扩展设置弹窗)
│ └── PrizeListDialog.vue (奖品列表弹窗)
│ ├── PrizeAddDialog.vue (奖品新增弹窗)
│ └── PrizeEditDialog.vue (奖品编辑弹窗)
type.vue (类型管理)
├── TypeAddDialog.vue (类型新增弹窗)
└── TypeEditDialog.vue (类型编辑弹窗)
```
## 数据模型
### 盒子类型枚举
```typescript
export enum GoodsType {
YiFanShang = 1, // 一番赏
WuXianShang = 2, // 无限赏
LeiTaiShang = 3, // 擂台赏
FuDai = 5, // 福袋
XingYunShang = 6, // 幸运赏
LingZhuShang = 8, // 领主赏
LianJiShang = 9, // 连击赏
MangHe = 10, // 盲盒
XingYunShangNew = 11,// 幸运赏(新)
FuLiWu = 15, // 福利屋
FanBeiShang = 16, // 翻倍赏
TeShuHeZi = 17, // 特殊盒子
}
export const GoodsTypeLabels: Record<GoodsType, string> = {
[GoodsType.YiFanShang]: '一番赏',
[GoodsType.WuXianShang]: '无限赏',
[GoodsType.LeiTaiShang]: '擂台赏',
[GoodsType.FuDai]: '福袋',
[GoodsType.XingYunShang]: '幸运赏',
[GoodsType.LingZhuShang]: '领主赏',
[GoodsType.LianJiShang]: '连击赏',
[GoodsType.MangHe]: '盲盒',
[GoodsType.XingYunShangNew]: '幸运赏(新)',
[GoodsType.FuLiWu]: '福利屋',
[GoodsType.FanBeiShang]: '翻倍赏',
[GoodsType.TeShuHeZi]: '特殊盒子',
};
```
### 盒子类型字段配置
```typescript
export interface GoodsTypeFieldConfig {
showStock: boolean; // 显示套数
showLock: boolean; // 显示锁箱配置
showDailyLimit: boolean; // 显示每日限购
showRage: boolean; // 显示怒气值
showItemCard: boolean; // 显示道具卡
showLingzhu: boolean; // 显示领主配置
showLianji: boolean; // 显示连击配置
showTimeConfig: boolean; // 显示时间配置(福利屋)
showAutoXiajia: boolean; // 显示自动下架
showCoupon: boolean; // 显示发券开关
showIntegral: boolean; // 显示发积分开关
showDescription: boolean; // 显示盒子描述
showQuanjuXiangou: boolean; // 显示限购次数
}
export const GoodsTypeFieldConfigs: Record<GoodsType, GoodsTypeFieldConfig> = {
[GoodsType.YiFanShang]: {
showStock: true, showLock: true, showDailyLimit: true,
showRage: false, showItemCard: false, showLingzhu: false,
showLianji: false, showTimeConfig: false, showAutoXiajia: true,
showCoupon: true, showIntegral: true, showDescription: false,
showQuanjuXiangou: false,
},
[GoodsType.WuXianShang]: {
showStock: false, showLock: false, showDailyLimit: false,
showRage: true, showItemCard: true, showLingzhu: false,
showLianji: false, showTimeConfig: false, showAutoXiajia: true,
showCoupon: true, showIntegral: true, showDescription: false,
showQuanjuXiangou: false,
},
// ... 其他类型配置
};
```
### 盒子数据模型
```typescript
export interface GoodsItem {
id: number;
type: GoodsType;
title: string;
price: number;
stock: number;
imgUrl: string;
imgUrlDetail: string;
status: number; // 0-下架 1-上架 2-售罄
sort: number;
isNew: boolean;
isShouZhe: boolean; // 首抽五折
lockIs: boolean; // 锁箱开关
lockTime: number; // 锁箱时间(秒)
dailyXiangou: number; // 每日限购
quanjuXiangou: number; // 限购次数
rageIs: boolean; // 怒气值开关
rage: number; // 怒气值
itemCardId: number; // 道具卡ID
couponIs: boolean; // 发券开关
integralIs: boolean; // 发积分开关
isAutoXiajia: boolean; // 自动下架开关
xiajiaLirun: number; // 下架利润值(%)
xiajiaAutoCoushu: number; // 下架抽数阈值
xiajiaJine: number; // 下架金额
unlockAmount: number; // 解锁金额
choujiangXianzhi: number; // 抽奖门槛
goodsDescribe: string; // 盒子描述
// 福利屋专用
flwStartTime: string; // 开始时间
flwEndTime: string; // 结束时间
openTime: string; // 开奖时间
// 领主赏专用
lingzhuIs: boolean;
lingzhuFan: number;
lingzhuShangId: number;
// 连击赏专用
lianJiNum: number;
lianJiShangId: number;
// 时间戳
createTime: string;
updateTime: string;
}
```
### 奖品数据模型
```typescript
export enum PrizeCategory {
XianHuo = 1, // 现货
YuShou = 2, // 预售
HuoBi = 3, // 货币
BaoXiang = 4, // 宝箱
}
export interface PrizeItem {
id: number;
goodsId: number;
title: string;
category: PrizeCategory;
level: string; // A/B/C/D/E等
price: number; // 售价
costPrice: number; // 采购价
referencePrice: number; // 参考价
quantity: number; // 数量(一番赏等)
probability: number; // 概率%(无限赏等)
giftMultiple: number; // 赠送倍率(翻倍赏)
isLingzhu: boolean; // 是否领主(领主赏)
preSaleTime: string; // 预售时间
sort: number;
imgUrl: string;
detailImgUrl: string;
giftCurrency: GiftCurrency[]; // 赠送货币配置
children?: PrizeItem[]; // 宝箱子奖品
createTime: string;
updateTime: string;
}
export interface GiftCurrency {
type: string; // 货币类型
amount: number; // 数量
}
```
## API接口设计
```typescript
// src/api/business/goods.ts
import request from '@/utils/request';
const BASE_URL = '/api/admin/business/goods';
// 盒子管理
export const goodsApi = {
// 获取盒子列表
getList: (params: GoodsListParams) =>
request.get<PageResult<GoodsItem>>(`${BASE_URL}`, { params }),
// 获取盒子详情
getDetail: (id: number) =>
request.get<GoodsItem>(`${BASE_URL}/${id}`),
// 创建盒子
create: (data: GoodsCreateRequest) =>
request.post<{ id: number }>(`${BASE_URL}`, data),
// 更新盒子
update: (id: number, data: GoodsUpdateRequest) =>
request.put(`${BASE_URL}/${id}`, data),
// 删除盒子
delete: (id: number) =>
request.delete(`${BASE_URL}/${id}`),
// 设置盒子状态
setStatus: (id: number, status: number) =>
request.put(`${BASE_URL}/${id}/status`, { status }),
// 获取盒子奖品列表
getPrizes: (goodsId: number) =>
request.get<PrizeItem[]>(`${BASE_URL}/${goodsId}/prizes`),
// 添加奖品
addPrize: (goodsId: number, data: PrizeCreateRequest) =>
request.post<{ id: number }>(`${BASE_URL}/${goodsId}/prizes`, data),
};
// 奖品管理
export const prizesApi = {
// 获取奖品详情
getDetail: (id: number) =>
request.get<PrizeItem>(`/api/admin/business/prizes/${id}`),
// 更新奖品
update: (id: number, data: PrizeUpdateRequest) =>
request.put(`/api/admin/business/prizes/${id}`, data),
// 删除奖品
delete: (id: number) =>
request.delete(`/api/admin/business/prizes/${id}`),
};
// 盒子类型管理
export const goodsTypeApi = {
// 获取类型列表
getList: () =>
request.get<GoodsTypeItem[]>(`${BASE_URL}/types`),
// 创建类型
create: (data: GoodsTypeCreateRequest) =>
request.post(`${BASE_URL}/types`, data),
// 更新类型
update: (id: number, data: GoodsTypeUpdateRequest) =>
request.put(`${BASE_URL}/types/${id}`, data),
// 删除类型
delete: (id: number) =>
request.delete(`${BASE_URL}/types/${id}`),
};
```
## 组件设计
### GoodsAddDialog 盒子新增弹窗
核心逻辑:根据盒子类型动态显示/隐藏字段
```typescript
// 计算属性:根据类型获取字段配置
const fieldConfig = computed(() => {
return GoodsTypeFieldConfigs[formData.type] || defaultFieldConfig;
});
// 监听类型变化,重置相关字段
watch(() => formData.type, (newType) => {
resetTypeSpecificFields();
});
```
### PrizeListDialog 奖品列表弹窗
核心逻辑:根据盒子类型动态显示不同列
```typescript
// 计算属性:动态列配置
const dynamicColumns = computed(() => {
const type = props.goodsType;
const columns = [...baseColumns];
// 一番赏/擂台赏/福袋等显示数量列
if ([1, 3, 5, 6, 10, 11, 15, 17].includes(type)) {
columns.push({ prop: 'quantity', label: '奖品数量' });
}
// 无限赏/翻倍赏等显示概率列
if ([2, 8, 9, 16, 17].includes(type)) {
columns.push({ prop: 'probability', label: '真实概率(%)' });
}
// 翻倍赏显示赠送倍率
if ([16, 17].includes(type)) {
columns.push({ prop: 'giftMultiple', label: '赠送倍率' });
}
return columns;
});
```
## 路由配置
```typescript
// src/router/modules/business.ts
{
path: 'goods',
name: 'BusinessGoods',
component: () => import('@/views/business/goods/index.vue'),
meta: { title: '盒子管理', icon: 'goods', permission: 'goods:list' }
},
{
path: 'goods/type',
name: 'BusinessGoodsType',
component: () => import('@/views/business/goods/type.vue'),
meta: { title: '盒子类型', icon: 'type', permission: 'goods:type' }
},
```
## 属性测试设计
### 测试用例
```csharp
// GoodsManagementFrontendPropertyTests.cs
[Property]
public Property GoodsTypeFieldConfig_ShouldBeConsistent()
{
// 验证每种盒子类型都有对应的字段配置
}
[Property]
public Property GoodsCreate_WithValidData_ShouldSucceed()
{
// 验证有效数据创建盒子成功
}
[Property]
public Property GoodsCreate_WithInvalidType_ShouldFail()
{
// 验证无效类型创建盒子失败
}
[Property]
public Property PrizeCreate_WithValidData_ShouldSucceed()
{
// 验证有效数据创建奖品成功
}
[Property]
public Property PrizeProbability_ShouldNotExceed100()
{
// 验证奖品概率总和不超过100%
}
```
## 状态管理
使用组合式API管理状态不需要Pinia store
```typescript
// 盒子列表状态
const goodsListState = reactive({
loading: false,
list: [] as GoodsItem[],
total: 0,
searchParams: {
title: '',
status: null,
type: null,
page: 1,
pageSize: 20,
},
});
// 奖品列表状态
const prizeListState = reactive({
loading: false,
list: [] as PrizeItem[],
goodsId: 0,
goodsType: 0,
});
```
## 错误处理
```typescript
// 统一错误处理
const handleApiError = (error: any, defaultMessage: string) => {
const message = error.response?.data?.message || defaultMessage;
ElMessage.error(message);
};
// 使用示例
try {
await goodsApi.create(formData);
ElMessage.success('创建成功');
} catch (error) {
handleApiError(error, '创建失败');
}
```