12 KiB
12 KiB
商品管理模块前端设计文档
架构设计
目录结构
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 (类型编辑弹窗)
数据模型
盒子类型枚举
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]: '特殊盒子',
};
盒子类型字段配置
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,
},
// ... 其他类型配置
};
盒子数据模型
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;
}
奖品数据模型
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接口设计
// 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 盒子新增弹窗
核心逻辑:根据盒子类型动态显示/隐藏字段
// 计算属性:根据类型获取字段配置
const fieldConfig = computed(() => {
return GoodsTypeFieldConfigs[formData.type] || defaultFieldConfig;
});
// 监听类型变化,重置相关字段
watch(() => formData.type, (newType) => {
resetTypeSpecificFields();
});
PrizeListDialog 奖品列表弹窗
核心逻辑:根据盒子类型动态显示不同列
// 计算属性:动态列配置
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;
});
路由配置
// 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' }
},
属性测试设计
测试用例
// 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:
// 盒子列表状态
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,
});
错误处理
// 统一错误处理
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, '创建失败');
}