本功能为财务申请页面(accountLogApply.vue)添加商家选择器,使总后台管理员能够代表任意商家提交结算申请,同时保持普通商家用户只能为自己提交申请的权限隔离。
核心设计原则:
┌─────────────────────────────────────────┐
│ 用户界面层 (Vue Component) │
│ - 商家选择器组件 │
│ - 表单验证和提交 │
│ - 角色判断和条件渲染 │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ API 层 (HTTP Requests) │
│ - 商家列表查询 (/store/index) │
│ - 商家详情查询 (/store/read) │
│ - 提现申请提交 (/account/apply) │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ 服务层 (Laravel Services) │
│ - StoreService: 商家数据查询 │
│ - AccountService: 提现申请处理 │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ 数据层 (Database Models) │
│ - Store: 商家信息 │
│ - Member: 用户余额 │
│ - AccountLog: 提现记录 │
└─────────────────────────────────────────┘
用户登录
│
▼
获取用户信息 (user.store_id)
│
├─── store_id === 0 或 null ──→ 总后台管理员
│ │
│ ▼
│ 显示商家选择器
│ 要求选择商家
│
└─── store_id > 0 ──→ 商家用户
│
▼
隐藏商家选择器
自动使用当前商家ID
位置: accountLogApply.vue 中的 el-select 组件
Props/Data:
{
isSuperAdmin: Boolean, // 是否为超级管理员
applyForm: {
store_id: Number, // 选中的商家ID
user_id: Number, // 商家对应的用户ID
name: String, // 商家名称
real_name: String, // 真实姓名
phone: String, // 手机号
balance: Number // 可提现余额
},
storeOptions: Array, // 商家选项列表
storeSearchLoading: Boolean // 搜索加载状态
}
Methods:
// 检查用户角色
checkUserRole(): void
// 搜索商家列表
searchStores(query: string): Promise<void>
// 处理商家选择变化
handleStoreChange(storeId: number): Promise<void>
// 加载商家信息(商家用户专用)
loadStoreInfoForMerchant(): Promise<void>
// 加载申请记录
getList(): Promise<void>
核心逻辑:
checkUserRole() {
const user = this.user || {};
// 关键判断:store_id === 0 或 null/undefined 表示总后台管理员
// store_id > 0 表示商家用户
this.isSuperAdmin = !user.store_id || user.store_id === 0;
if (!this.isSuperAdmin) {
// 商家用户:自动加载当前商家信息
this.loadStoreInfoForMerchant();
}
}
端点: GET /admin/store/index
请求参数:
{
page: number, // 页码,默认 1
limit: number, // 每页数量,默认 15
name: string, // 商家名称(模糊搜索)
status: number // 商家状态,1-已审核
}
响应格式:
{
code: 0,
msg: "操作成功",
data: [
{
id: number, // 商家ID
name: string, // 商家名称
phone: string, // 手机号
real_name: string, // 真实姓名
user_id: number, // 关联的用户ID
balance: number, // 余额
status: number // 状态
}
],
count: number // 总数
}
端点: GET /admin/store/read
请求参数:
{
id: number // 商家ID
}
响应格式:
{
code: 0,
msg: "操作成功",
data: {
id: number,
name: string,
phone: string,
real_name: string,
user_id: number,
balance: number,
// ... 其他商家信息
}
}
端点: POST /admin/account/apply
请求参数:
{
store_id: number, // 商家ID(总后台管理员必填)
user_id: number, // 用户ID
money: number, // 提现金额
real_name: string, // 收款人姓名
bank_name: string, // 银行名称
bank_account: string, // 银行账号
remark: string // 备注
}
{
id: number, // 主键
user_id: number, // 关联用户ID (外键 -> Member.id)
name: string, // 商家名称
phone: string, // 手机号
real_name: string, // 真实姓名
status: number, // 状态:0-待审核,1-已审核,2-已拒绝
created_at: timestamp, // 创建时间
updated_at: timestamp // 更新时间
}
{
id: number, // 主键
store_id: number, // 关联商家ID (0表示总后台管理员)
username: string, // 用户名
phone: string, // 手机号
balance: decimal, // 余额
role_id: number, // 角色ID
created_at: timestamp,
updated_at: timestamp
}
{
id: number, // 主键
user_id: number, // 用户ID (外键 -> Member.id)
store_id: number, // 商家ID (外键 -> Store.id)
money: decimal, // 提现金额
real_name: string, // 收款人姓名
bank_name: string, // 银行名称
bank_account: string, // 银行账号
status: number, // 状态:0-待审核,1-已通过,2-已拒绝
remark: string, // 备注
created_at: timestamp,
updated_at: timestamp
}
Member (用户)
│
├─ store_id = 0 ──→ 总后台管理员
│
└─ store_id > 0 ──→ 商家用户
│
▼
Store (商家)
│
└─ user_id ──→ Member.id
属性是应该在系统所有有效执行中保持为真的特征或行为——本质上是关于系统应该做什么的正式声明。属性作为人类可读规范和机器可验证正确性保证之间的桥梁。
对于任意 用户,当用户的 store_id 为 0 或 null 时,商家选择器组件应该可见;当 store_id 大于 0 时,商家选择器应该隐藏 验证需求: 1.1, 1.5, 3.2, 3.3
对于任意 选中的商家,系统加载的商家信息应该包含 user_id、name、real_name、phone 和 balance 字段,且这些字段的值应该与数据库中该商家的记录一致 验证需求: 1.2
对于任意 两个不同的商家 A 和 B,当从商家 A 切换到商家 B 时,显示的商家信息(名称、手机号、余额)应该从 A 的信息变更为 B 的信息 验证需求: 1.3
对于任意 商家和提现申请,创建的提现记录中的 user_id 字段应该等于选中商家的 user_id 验证需求: 1.4
对于任意 搜索关键词,返回的所有商家记录的名称或手机号字段应该包含该关键词(模糊匹配) 验证需求: 2.1, 5.2
对于任意 商家选项,渲染的选项文本应该包含商家名称、手机号和余额信息 验证需求: 2.3
对于任意 选中的商家,加载的申请记录列表中的所有记录的 user_id 应该等于该商家的 user_id 验证需求: 4.1, 4.2
对于任意 商家列表 API 响应,返回的每个商家对象应该包含 id、name、phone、real_name、user_id 和 balance 字段 验证需求: 5.1
对于任意 分页参数(page, limit),当总记录数大于等于 page * limit 时,返回的数据数量应该等于 limit;否则应该等于剩余记录数 验证需求: 5.3
对于任意 商家 ID,商家详情 API 返回的数据应该包含该商家的完整信息(id、name、phone、real_name、user_id、balance 等),且 balance 字段应该反映该商家关联用户的当前余额 验证需求: 5.4
javascript
{
code: 1,
msg: "参数错误: [具体错误信息]",
data: null
}
javascript
{
code: 1,
msg: "[具体业务错误信息]",
data: null
}
javascript
{
code: 401,
msg: "无权限访问",
data: null
}
javascript
{
code: 500,
msg: "系统错误,请稍后重试",
data: null
}
测试框架: Jest + Vue Test Utils
测试范围:
角色判断逻辑测试
checkUserRole() 方法商家选择器显示/隐藏测试
表单验证测试
数据处理方法测试
handleStoreChange() 方法searchStores() 方法测试框架: PHPUnit
测试范围:
StoreService 测试
权限验证测试
提现申请测试
测试框架:
配置: 每个属性测试运行至少 100 次迭代
测试标注格式: **Feature: admin-merchant-selector, Property {number}: {property_text}**
属性测试列表:
属性 1: 商家选择器角色可见性
属性 2: 商家信息加载完整性
属性 3: 商家切换信息更新
属性 4: 提现记录 user_id 正确性
属性 5: 搜索结果关键词匹配
属性 6: 商家选项显示完整性
属性 7: 申请记录商家关联性
属性 8: API 响应数据结构完整性
属性 9: 分页数据量正确性
属性 10: 商家详情数据完整性
测试范围:
端到端用户流程测试
API 集成测试
权限隔离测试
测试数据库:
Mock 数据: