feat: 新增门店列表页面并修复全局类型错误

1. 新增门店列表页(筛选/统计/表格/抽屉编辑),使用 mockjs 提供接口数据
2. 新增门店相关枚举、API 定义、路由配置
3. 修复 auth.ts loginApi 参数类型不匹配
4. 修复 merchant-center stores 属性路径错误及 merchant prop 类型不兼容
5. 修复 merchant-setting showSubmitButton 不存在于 VbenFormProps
This commit is contained in:
2026-02-15 16:35:22 +08:00
parent 0a161d185e
commit 4be997df63
13 changed files with 1535 additions and 1053 deletions

View File

@@ -0,0 +1,4 @@
// Mock 数据入口,仅在开发环境下使用
import './store';
console.warn('[Mock] Mock 数据已启用');

View File

@@ -0,0 +1,253 @@
import Mock from 'mockjs';
const Random = Mock.Random;
/** mockjs 请求回调参数 */
interface MockRequestOptions {
url: string;
type: string;
body: null | string;
}
/** 门店筛选参数 */
interface StoreFilterParams {
keyword?: string;
businessStatus?: string;
auditStatus?: string;
serviceType?: string;
page?: string;
pageSize?: string;
}
// 预定义门店数据,保证每次请求返回一致的数据
const storePool = generateStores(23);
function generateStores(count: number) {
const districts = [
'朝阳区建国路88号',
'海淀区中关村大街66号',
'朝阳区望京西路50号',
'通州区新华大街120号',
'丰台区丰台路18号',
'西城区西单北大街100号',
'东城区王府井大街200号',
'大兴区黄村镇兴华路30号',
'昌平区回龙观东大街15号',
'顺义区府前街8号',
'石景山区石景山路22号',
'房山区良乡拱辰大街55号',
'密云区鼓楼东大街10号',
'怀柔区青春路6号',
'平谷区府前街12号',
'门头沟区新桥大街3号',
'延庆区妫水北街9号',
'亦庄经济开发区荣华南路1号',
'望京SOHO T1-2层',
'三里屯太古里南区B1',
'国贸商城3层',
'五道口华联商厦1层',
'中关村食宝街B1层',
];
const managerNames = [
'张伟',
'李娜',
'王磊',
'赵敏',
'刘洋',
'陈静',
'杨帆',
'周杰',
'吴芳',
'孙涛',
'马丽',
'朱军',
'胡明',
'郭强',
'何欢',
'林峰',
'徐婷',
'高远',
'罗斌',
'梁宇',
'宋佳',
'唐亮',
'韩雪',
];
const storeNames = [
'老三家外卖(朝阳店)',
'老三家外卖(海淀店)',
'老三家外卖(望京店)',
'老三家外卖(通州店)',
'老三家外卖(丰台店)',
'老三家外卖(西单店)',
'老三家外卖(王府井店)',
'老三家外卖(大兴店)',
'老三家外卖(回龙观店)',
'老三家外卖(顺义店)',
'老三家外卖(石景山店)',
'老三家外卖(良乡店)',
'老三家外卖(密云店)',
'老三家外卖(怀柔店)',
'老三家外卖(平谷店)',
'老三家外卖(门头沟店)',
'老三家外卖(延庆店)',
'老三家外卖(亦庄店)',
'老三家外卖望京SOHO店',
'老三家外卖(三里屯店)',
'老三家外卖(国贸店)',
'老三家外卖(五道口店)',
'老三家外卖(中关村店)',
];
const avatarColors = [
'#3b82f6',
'#f59e0b',
'#8b5cf6',
'#ef4444',
'#22c55e',
'#06b6d4',
'#ec4899',
'#f97316',
'#14b8a6',
'#6366f1',
];
const stores = [];
for (let i = 0; i < count; i++) {
// 1. 按索引分配营业状态,模拟真实分布
let businessStatus = 1;
if (i >= 21) {
businessStatus = Random.pick([1, 2, 3]);
} else if (i >= 18) {
businessStatus = 3;
} else if (i >= 14) {
businessStatus = 2;
}
// 2. 按索引分配审核状态
let auditStatus = 2;
if (i < 20) {
auditStatus = 1;
} else if (i < 22) {
auditStatus = 0;
}
// 3. 循环分配服务方式组合
const serviceTypeCombos = [[1], [1, 2], [1, 2, 3], [1, 3], [2, 3]];
stores.push({
id: Random.guid(),
name: storeNames[i] || `老三家外卖(分店${i + 1}`,
code: `ST2025${String(i + 1).padStart(4, '0')}`,
contactPhone: `138****${String(8001 + i).slice(-4)}`,
managerName: managerNames[i] || Random.cname(),
address: `北京市${districts[i] || `朝阳区某路${i + 1}`}`,
coverImage: '',
businessStatus,
auditStatus,
serviceTypes: serviceTypeCombos[i % serviceTypeCombos.length],
createdAt: Random.datetime('yyyy-MM-dd'),
_avatarColor: avatarColors[i % avatarColors.length],
});
}
return stores;
}
function filterStores(params: StoreFilterParams) {
let list = [...storePool];
// 1. 关键词模糊匹配(名称/编码/电话)
if (params.keyword) {
const kw = params.keyword.toLowerCase();
list = list.filter(
(s) =>
s.name.toLowerCase().includes(kw) ||
s.code.toLowerCase().includes(kw) ||
s.contactPhone.includes(kw),
);
}
// 2. 营业状态筛选
if (params.businessStatus) {
const status = Number(params.businessStatus);
list = list.filter((s) => s.businessStatus === status);
}
// 3. 审核状态筛选
if (params.auditStatus !== undefined && params.auditStatus !== '') {
const status = Number(params.auditStatus);
list = list.filter((s) => s.auditStatus === status);
}
// 4. 服务方式筛选
if (params.serviceType) {
const type = Number(params.serviceType);
list = list.filter((s) => (s.serviceTypes ?? []).includes(type));
}
return list;
}
/** 从 URL 中解析查询参数 */
function parseUrlParams(url: string): StoreFilterParams {
const parsed = new URL(url, 'http://localhost');
const params: Record<string, string> = {};
parsed.searchParams.forEach((value, key) => {
params[key] = value;
});
return params;
}
// 门店列表
Mock.mock(/\/store\/list/, 'get', (options: MockRequestOptions) => {
const params = parseUrlParams(options.url);
const page = Number(params.page) || 1;
const pageSize = Number(params.pageSize) || 10;
const filtered = filterStores(params);
const start = (page - 1) * pageSize;
const items = filtered.slice(start, start + pageSize);
return {
code: 200,
data: {
items,
total: filtered.length,
page,
pageSize,
},
};
});
// 门店统计
Mock.mock(/\/store\/stats/, 'get', () => {
return {
code: 200,
data: {
total: storePool.length,
operating: storePool.filter((s) => s.businessStatus === 1).length,
resting: storePool.filter((s) => s.businessStatus === 2).length,
pendingAudit: storePool.filter((s) => s.auditStatus === 0).length,
},
};
});
// 创建门店
Mock.mock(/\/store\/create/, 'post', () => {
return { code: 200, data: null };
});
// 更新门店
Mock.mock(/\/store\/update/, 'post', () => {
return { code: 200, data: null };
});
// 删除门店
Mock.mock(/\/store\/delete/, 'post', () => {
return { code: 200, data: null };
});
// 设置 mock 响应延迟
Mock.setup({ timeout: '200-400' });