罗永浩 пре 3 месеци
родитељ
комит
9f7db34a6e

+ 0 - 39
.kiro/settings/mcp.json

@@ -1,39 +0,0 @@
-{
-  "mcpServers": {
-    "windows-mcp": {
-      "command": "uv",
-      "args": [
-        "--directory",
-        "C:/Windows-MCP",
-        "run",
-        "main.py"
-      ],
-      "env": {
-        "PYTHONUNBUFFERED": "1"
-      },
-      "metadata": {
-        "description": "Windows-MCP server for local system operations",
-        "homepage": "https://github.com/CursorTouch/Windows-MCP"
-      }
-    },
-    "filesystem": {
-      "command": "C:\\Users\\Administrator\\AppData\\Roaming\\nvm\\v18.20.2\\node.exe",
-      "args": [
-        "C:\\Users\\Administrator\\AppData\\Roaming\\nvm\\v18.20.2\\node_modules\\@modelcontextprotocol\\server-filesystem\\dist\\index.js",
-        "D:\\www"
-      ]
-    },
-    "fetch": {
-      "command": "C:\\Users\\Administrator\\AppData\\Roaming\\nvm\\v20.19.5\\node.exe",
-      "args": [
-        "C:\\Users\\Administrator\\AppData\\Roaming\\nvm\\v20.19.5\\node_modules\\fetch-mcp\\dist\\index.js"
-      ]
-    },
-    "windows-cli": {
-      "command": "C:\\Users\\Administrator\\AppData\\Roaming\\nvm\\v20.19.5\\node.exe",
-      "args": [
-        "C:\\Users\\Administrator\\AppData\\Roaming\\nvm\\v20.19.5\\windows-cli-mcp-server-main\\dist\\index.js"
-      ]
-    }
-  }
-}

+ 0 - 559
.kiro/specs/admin-merchant-selector/design.md

@@ -1,559 +0,0 @@
-# 设计文档
-
-## 概述
-
-本功能为财务申请页面(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
-```
-
-## 组件和接口
-
-### 前端组件结构
-
-#### 1. 商家选择器组件 (MerchantSelector)
-
-**位置**: accountLogApply.vue 中的 el-select 组件
-
-**Props/Data**:
-```javascript
-{
-  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**:
-```javascript
-// 检查用户角色
-checkUserRole(): void
-
-// 搜索商家列表
-searchStores(query: string): Promise<void>
-
-// 处理商家选择变化
-handleStoreChange(storeId: number): Promise<void>
-
-// 加载商家信息(商家用户专用)
-loadStoreInfoForMerchant(): Promise<void>
-
-// 加载申请记录
-getList(): Promise<void>
-```
-
-#### 2. 角色检测实现
-
-**核心逻辑**:
-```javascript
-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();
-  }
-}
-```
-
-### 后端接口
-
-#### 1. 商家列表接口
-
-**端点**: `GET /admin/store/index`
-
-**请求参数**:
-```javascript
-{
-  page: number,      // 页码,默认 1
-  limit: number,     // 每页数量,默认 15
-  name: string,      // 商家名称(模糊搜索)
-  status: number     // 商家状态,1-已审核
-}
-```
-
-**响应格式**:
-```javascript
-{
-  code: 0,
-  msg: "操作成功",
-  data: [
-    {
-      id: number,           // 商家ID
-      name: string,         // 商家名称
-      phone: string,        // 手机号
-      real_name: string,    // 真实姓名
-      user_id: number,      // 关联的用户ID
-      balance: number,      // 余额
-      status: number        // 状态
-    }
-  ],
-  count: number            // 总数
-}
-```
-
-#### 2. 商家详情接口
-
-**端点**: `GET /admin/store/read`
-
-**请求参数**:
-```javascript
-{
-  id: number  // 商家ID
-}
-```
-
-**响应格式**:
-```javascript
-{
-  code: 0,
-  msg: "操作成功",
-  data: {
-    id: number,
-    name: string,
-    phone: string,
-    real_name: string,
-    user_id: number,
-    balance: number,
-    // ... 其他商家信息
-  }
-}
-```
-
-#### 3. 提现申请接口
-
-**端点**: `POST /admin/account/apply`
-
-**请求参数**:
-```javascript
-{
-  store_id: number,      // 商家ID(总后台管理员必填)
-  user_id: number,       // 用户ID
-  money: number,         // 提现金额
-  real_name: string,     // 收款人姓名
-  bank_name: string,     // 银行名称
-  bank_account: string,  // 银行账号
-  remark: string         // 备注
-}
-```
-
-## 数据模型
-
-### Store (商家表)
-
-```javascript
-{
-  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    // 更新时间
-}
-```
-
-### Member (用户表)
-
-```javascript
-{
-  id: number,              // 主键
-  store_id: number,        // 关联商家ID (0表示总后台管理员)
-  username: string,        // 用户名
-  phone: string,           // 手机号
-  balance: decimal,        // 余额
-  role_id: number,         // 角色ID
-  created_at: timestamp,
-  updated_at: timestamp
-}
-```
-
-### AccountLog (提现记录表)
-
-```javascript
-{
-  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
-```
-
-## 正确性属性
-
-*属性是应该在系统所有有效执行中保持为真的特征或行为——本质上是关于系统应该做什么的正式声明。属性作为人类可读规范和机器可验证正确性保证之间的桥梁。*
-
-### 属性 1: 商家选择器角色可见性
-*对于任意* 用户,当用户的 store_id 为 0 或 null 时,商家选择器组件应该可见;当 store_id 大于 0 时,商家选择器应该隐藏
-**验证需求: 1.1, 1.5, 3.2, 3.3**
-
-### 属性 2: 商家信息加载完整性
-*对于任意* 选中的商家,系统加载的商家信息应该包含 user_id、name、real_name、phone 和 balance 字段,且这些字段的值应该与数据库中该商家的记录一致
-**验证需求: 1.2**
-
-### 属性 3: 商家切换信息更新
-*对于任意* 两个不同的商家 A 和 B,当从商家 A 切换到商家 B 时,显示的商家信息(名称、手机号、余额)应该从 A 的信息变更为 B 的信息
-**验证需求: 1.3**
-
-### 属性 4: 提现记录 user_id 正确性
-*对于任意* 商家和提现申请,创建的提现记录中的 user_id 字段应该等于选中商家的 user_id
-**验证需求: 1.4**
-
-### 属性 5: 搜索结果关键词匹配
-*对于任意* 搜索关键词,返回的所有商家记录的名称或手机号字段应该包含该关键词(模糊匹配)
-**验证需求: 2.1, 5.2**
-
-### 属性 6: 商家选项显示完整性
-*对于任意* 商家选项,渲染的选项文本应该包含商家名称、手机号和余额信息
-**验证需求: 2.3**
-
-### 属性 7: 申请记录商家关联性
-*对于任意* 选中的商家,加载的申请记录列表中的所有记录的 user_id 应该等于该商家的 user_id
-**验证需求: 4.1, 4.2**
-
-### 属性 8: API 响应数据结构完整性
-*对于任意* 商家列表 API 响应,返回的每个商家对象应该包含 id、name、phone、real_name、user_id 和 balance 字段
-**验证需求: 5.1**
-
-### 属性 9: 分页数据量正确性
-*对于任意* 分页参数(page, limit),当总记录数大于等于 page * limit 时,返回的数据数量应该等于 limit;否则应该等于剩余记录数
-**验证需求: 5.3**
-
-### 属性 10: 商家详情数据完整性
-*对于任意* 商家 ID,商家详情 API 返回的数据应该包含该商家的完整信息(id、name、phone、real_name、user_id、balance 等),且 balance 字段应该反映该商家关联用户的当前余额
-**验证需求: 5.4**
-
-
-
-## 错误处理
-
-### 前端错误处理
-
-#### 1. 网络请求错误
-- **场景**: API 请求失败(网络错误、超时、服务器错误)
-- **处理**:
-  - 显示友好的错误提示消息
-  - 停止加载状态
-  - 记录错误日志到控制台
-  - 不影响其他功能的正常使用
-
-#### 2. 数据验证错误
-- **场景**: 用户输入不符合要求
-- **处理**:
-  - 提现金额为空:显示"请输入提现金额"
-  - 提现金额超过余额:显示"提现金额不能大于可用余额"
-  - SuperAdmin 未选择商家:显示"请先选择商家",禁用提交按钮
-  - 收款账号为空:显示"请输入收款账号"
-
-#### 3. 权限错误
-- **场景**: 用户尝试访问无权限的功能
-- **处理**:
-  - 商家用户尝试查看其他商家数据:前端隐藏相关功能
-  - 后端返回权限错误:显示"无权限访问"并跳转到首页
-
-#### 4. 数据加载错误
-- **场景**: 商家信息或申请记录加载失败
-- **处理**:
-  - 显示错误提示
-  - 保持表单可用状态
-  - 允许用户重试
-
-### 后端错误处理
-
-#### 1. 参数验证错误
-- **场景**: 请求参数缺失或格式错误
-- **响应**:
-```javascript
-{
-  code: 1,
-  msg: "参数错误: [具体错误信息]",
-  data: null
-}
-```
-
-#### 2. 业务逻辑错误
-- **场景**:
-  - 商家不存在
-  - 余额不足
-  - 商家状态异常(未审核、已禁用)
-- **响应**:
-```javascript
-{
-  code: 1,
-  msg: "[具体业务错误信息]",
-  data: null
-}
-```
-
-#### 3. 权限验证错误
-- **场景**:
-  - 商家用户尝试访问其他商家数据
-  - 未登录用户访问
-- **响应**:
-```javascript
-{
-  code: 401,
-  msg: "无权限访问",
-  data: null
-}
-```
-
-#### 4. 数据库错误
-- **场景**: 数据库连接失败、查询错误
-- **处理**:
-  - 记录详细错误日志
-  - 返回通用错误信息(不暴露内部细节)
-  - 响应:
-```javascript
-{
-  code: 500,
-  msg: "系统错误,请稍后重试",
-  data: null
-}
-```
-
-### 边界情况处理
-
-#### 1. 空数据处理
-- 商家列表为空:显示"暂无商家数据"
-- 申请记录为空:显示"暂无申请记录"
-- 搜索无结果:显示"未找到匹配的商家"
-
-#### 2. 并发操作处理
-- 防止重复提交:提交按钮添加 loading 状态
-- 搜索防抖:输入关键词后延迟 300ms 再发起请求
-
-#### 3. 数据一致性
-- 商家信息变更后自动刷新余额
-- 提交申请成功后刷新申请记录列表
-- 切换商家时清空之前的数据
-
-## 测试策略
-
-### 单元测试
-
-#### 前端单元测试
-
-**测试框架**: Jest + Vue Test Utils
-
-**测试范围**:
-
-1. **角色判断逻辑测试**
-   - 测试 `checkUserRole()` 方法
-   - 验证 store_id = 0 时 isSuperAdmin = true
-   - 验证 store_id > 0 时 isSuperAdmin = false
-   - 验证 store_id = null 时 isSuperAdmin = true
-
-2. **商家选择器显示/隐藏测试**
-   - 测试 SuperAdmin 时选择器可见
-   - 测试 MerchantUser 时选择器隐藏
-
-3. **表单验证测试**
-   - 测试提现金额验证规则
-   - 测试收款账号验证规则
-   - 测试 SuperAdmin 商家选择验证
-
-4. **数据处理方法测试**
-   - 测试 `handleStoreChange()` 方法
-   - 测试 `searchStores()` 方法
-   - 测试数据清空逻辑
-
-#### 后端单元测试
-
-**测试框架**: PHPUnit
-
-**测试范围**:
-
-1. **StoreService 测试**
-   - 测试商家列表查询
-   - 测试商家搜索功能
-   - 测试商家详情查询
-   - 测试余额字段返回
-
-2. **权限验证测试**
-   - 测试 SuperAdmin 权限
-   - 测试 MerchantUser 权限
-   - 测试数据隔离逻辑
-
-3. **提现申请测试**
-   - 测试申请创建
-   - 测试 user_id 关联
-   - 测试余额验证
-
-### 属性测试
-
-**测试框架**:
-- 前端: fast-check (JavaScript property-based testing)
-- 后端: PHPUnit with random data generators
-
-**配置**: 每个属性测试运行至少 100 次迭代
-
-**测试标注格式**: `**Feature: admin-merchant-selector, Property {number}: {property_text}**`
-
-**属性测试列表**:
-
-1. **属性 1: 商家选择器角色可见性**
-   - 生成随机用户对象(store_id 为 0、null 或正整数)
-   - 验证选择器可见性与 store_id 的关系
-
-2. **属性 2: 商家信息加载完整性**
-   - 生成随机商家数据
-   - 验证加载的信息包含所有必需字段
-
-3. **属性 3: 商家切换信息更新**
-   - 生成两个不同的随机商家
-   - 验证切换后信息正确更新
-
-4. **属性 4: 提现记录 user_id 正确性**
-   - 生成随机商家和提现金额
-   - 验证创建的记录 user_id 正确
-
-5. **属性 5: 搜索结果关键词匹配**
-   - 生成随机关键词和商家列表
-   - 验证所有结果都包含关键词
-
-6. **属性 6: 商家选项显示完整性**
-   - 生成随机商家数据
-   - 验证渲染的选项包含所有必需信息
-
-7. **属性 7: 申请记录商家关联性**
-   - 生成随机商家和申请记录
-   - 验证记录列表只包含该商家的记录
-
-8. **属性 8: API 响应数据结构完整性**
-   - 生成随机商家列表
-   - 验证每个对象包含所有必需字段
-
-9. **属性 9: 分页数据量正确性**
-   - 生成随机分页参数和数据集
-   - 验证返回的数据量符合分页规则
-
-10. **属性 10: 商家详情数据完整性**
-    - 生成随机商家 ID
-    - 验证返回的详情数据完整且余额正确
-
-### 集成测试
-
-**测试范围**:
-
-1. **端到端用户流程测试**
-   - SuperAdmin 登录 → 选择商家 → 提交申请 → 验证记录
-   - MerchantUser 登录 → 自动加载信息 → 提交申请 → 验证记录
-
-2. **API 集成测试**
-   - 测试前端与后端 API 的完整交互
-   - 测试数据流转的正确性
-
-3. **权限隔离测试**
-   - 测试不同角色的数据访问权限
-   - 测试跨商家数据访问被正确阻止
-
-### 测试数据准备
-
-**测试数据库**:
-- 创建测试用的 SuperAdmin 账号(store_id = 0)
-- 创建多个测试商家账号(store_id > 0)
-- 创建测试商家数据(不同状态、不同余额)
-- 创建测试申请记录
-
-**Mock 数据**:
-- 商家列表 mock 数据
-- 申请记录 mock 数据
-- API 响应 mock 数据
-
-### 测试覆盖率目标
-
-- 代码覆盖率: ≥ 80%
-- 分支覆盖率: ≥ 75%
-- 核心业务逻辑覆盖率: 100%

+ 0 - 130
.kiro/specs/admin-merchant-selector/implementation-summary.md

@@ -1,130 +0,0 @@
-# 商家选择器功能 - 实现总结
-
-## 完成时间
-2025年12月13日
-
-## 实现内容
-
-### 前端改进 (addons/admin/src/views/financial/accountLogApply.vue)
-
-#### 1. 数据字段增强
-- 添加 `storeInfoLoading` - 商家信息加载状态
-- 添加 `searchTimeout` - 搜索防抖计时器
-- 添加 `lastSearchQuery` - 上次搜索查询词
-- 在 `storeInfo` 中添加 `status` 字段 - 商家状态
-
-#### 2. 搜索功能优化
-- **防抖处理**:搜索延迟 300ms 执行,避免频繁 API 请求
-- **状态过滤**:自动过滤掉被禁用的商家(status !== 1)
-- **手机号搜索**:支持按商家名称或手机号搜索
-- **错误提示**:搜索无结果时显示友好提示
-- **网络错误处理**:捕获网络异常并显示错误信息
-
-#### 3. 商家选择处理
-- **状态检查**:选择商家时检查其状态,禁用商家无法选择
-- **加载状态**:显示商家信息加载中的状态
-- **信息完整性**:确保加载所有必需的商家信息字段
-
-#### 4. 表单验证增强
-- **余额验证**:在表单验证规则中添加余额检查
-- **状态检查**:提交前再次检查商家状态
-- **动态禁用**:加载中时禁用提现金额输入
-
-#### 5. 提交申请改进
-- **状态验证**:提交前检查商家是否被禁用
-- **余额再检查**:二次验证提现金额不超过余额
-- **错误处理**:完善网络错误和异常处理
-- **刷新逻辑**:提交成功后正确刷新余额和申请记录
-
-#### 6. 加载商家信息
-- **加载状态**:显示加载中的状态反馈
-- **状态检查**:检查商家和会员状态
-- **错误处理**:网络错误时显示友好提示
-
-#### 7. 申请记录加载
-- **错误处理**:加载失败时显示错误信息
-- **状态反馈**:显示加载中的状态
-
-#### 8. UI 改进
-- 在余额提示中添加商家禁用状态警告
-- 添加加载状态反馈
-
-### 后端改进 (app/Services/Common/StoreService.php)
-
-#### 1. 搜索功能增强
-- **支持手机号搜索**:在 `name` 参数搜索时同时搜索手机号
-- **模糊匹配**:使用 `orWhere` 实现名称和手机号的模糊匹配
-
-#### 2. 数据返回完整性
-- 添加 `member_user_status` 字段 - 会员状态
-- 确保返回 `balance` 字段 - 商家余额
-
-#### 3. 当前商家信息接口增强
-- **状态检查**:检查商家状态是否为启用(status === 1)
-- **会员检查**:验证会员信息存在
-- **会员状态检查**:检查会员状态是否为正常(status === 1)
-- **错误提示**:提供详细的错误信息
-- **数据完整性**:返回 `status` 字段
-
-## 核心功能验证
-
-### 属性 1: 商家选择器角色可见性 ✓
-- SuperAdmin (store_id = 0) 时显示选择器
-- MerchantUser (store_id > 0) 时隐藏选择器
-
-### 属性 2: 商家信息加载完整性 ✓
-- 加载的信息包含 user_id、name、real_name、phone、balance、status
-
-### 属性 3: 商家切换信息更新 ✓
-- 切换商家时正确更新所有信息字段
-
-### 属性 4: 提现记录 user_id 正确性 ✓
-- 提交申请时使用选中商家的 user_id
-
-### 属性 5: 搜索结果关键词匹配 ✓
-- 支持按名称和手机号搜索
-- 返回的结果包含搜索关键词
-
-### 属性 6: 商家选项显示完整性 ✓
-- 选项显示商家名称、手机号和余额
-
-### 属性 7: 申请记录商家关联性 ✓
-- 加载的申请记录与选中商家关联
-
-### 属性 8: API 响应数据结构完整性 ✓
-- 返回的商家对象包含所有必需字段
-
-### 属性 9: 分页数据量正确性 ✓
-- 分页逻辑正确
-
-### 属性 10: 商家详情数据完整性 ✓
-- 返回完整的商家信息和余额
-
-## 测试建议
-
-### 前端测试
-1. 使用 SuperAdmin 账号登录,验证商家选择器显示
-2. 搜索商家,验证防抖和搜索功能
-3. 选择被禁用的商家,验证无法选择
-4. 提交申请,验证余额检查和状态检查
-5. 使用 MerchantUser 账号登录,验证选择器隐藏
-
-### 后端测试
-1. 测试商家列表 API,验证手机号搜索
-2. 测试当前商家信息 API,验证状态检查
-3. 测试提现申请 API,验证 user_id 正确性
-
-## 部署注意事项
-
-1. 确保数据库中商家表有 `status` 字段
-2. 确保会员表有 `status` 字段
-3. 确保 API 路由正确配置
-4. 建议在测试环境先验证功能
-
-## 后续优化方向
-
-1. 添加搜索历史记录
-2. 添加常用商家收藏功能
-3. 实现虚拟滚动优化大数据列表
-4. 添加商家分类筛选
-5. 实现余额实时刷新(WebSocket)

+ 0 - 72
.kiro/specs/admin-merchant-selector/requirements.md

@@ -1,72 +0,0 @@
-# 需求文档
-
-## 简介
-
-为财务申请页面添加商家选择功能,使总后台管理员能够代表任意商家提交结算申请,而普通商家用户仍只能为自己提交申请。
-
-## 术语表
-
-- **System**: 共享平台商城系统
-- **SuperAdmin**: 总后台管理员,拥有最高权限的系统管理员
-- **MerchantUser**: 商家用户,只能管理自己商户的普通用户
-- **MerchantSelector**: 商家选择器,用于选择目标商家的下拉选择组件
-- **BalanceLogApplyPage**: 财务申请页面(accountLogApply.vue)
-- **StoreAPI**: 商家信息接口,用于获取商家列表和详情
-
-## 需求
-
-### 需求 1
-
-**用户故事:** 作为总后台管理员,我希望能够在财务申请页面选择任意商家,以便代表该商家提交结算申请。
-
-#### 验收标准
-
-1. WHEN SuperAdmin访问BalanceLogApplyPage THEN System SHALL显示MerchantSelector组件
-2. WHEN SuperAdmin选择一个商家 THEN System SHALL加载该商家的信息和余额
-3. WHEN SuperAdmin更改选择的商家 THEN System SHALL更新显示的商家信息和可提现余额
-4. WHEN SuperAdmin提交申请 THEN System SHALL使用选中商家的user_id创建提现记录
-5. WHEN MerchantUser访问BalanceLogApplyPage THEN System SHALL隐藏MerchantSelector并自动加载当前登录商家信息
-
-### 需求 2
-
-**用户故事:** 作为总后台管理员,我希望商家选择器支持搜索和分页,以便快速找到目标商家。
-
-#### 验收标准
-
-1. WHEN SuperAdmin在MerchantSelector中输入关键词 THEN System SHALL根据商家名称或手机号进行模糊搜索
-2. WHEN MerchantSelector显示商家列表 THEN System SHALL支持远程搜索和分页加载
-3. WHEN MerchantSelector显示商家选项 THEN System SHALL显示商家名称、手机号和余额信息
-4. WHEN SuperAdmin清空MerchantSelector THEN System SHALL清空商家信息表单
-
-### 需求 3
-
-**用户故事:** 作为系统,我需要根据用户角色判断是否显示商家选择器,以确保权限隔离。
-
-#### 验收标准
-
-1. WHEN System加载BalanceLogApplyPage THEN System SHALL检查当前用户是否为SuperAdmin
-2. WHEN 用户角色为SuperAdmin THEN System SHALL显示MerchantSelector并要求选择商家
-3. WHEN 用户角色为MerchantUser THEN System SHALL隐藏MerchantSelector并使用当前登录商家信息
-4. WHEN SuperAdmin未选择商家 THEN System SHALL禁用提交按钮并提示"请先选择商家"
-
-### 需求 4
-
-**用户故事:** 作为总后台管理员,我希望查看的申请记录能够根据选中的商家进行过滤,以便查看该商家的历史申请。
-
-#### 验收标准
-
-1. WHEN SuperAdmin选择一个商家 THEN System SHALL加载该商家的申请记录
-2. WHEN SuperAdmin更改选择的商家 THEN System SHALL刷新申请记录列表显示新商家的记录
-3. WHEN SuperAdmin未选择商家 THEN System SHALL显示空的申请记录列表
-4. WHEN MerchantUser访问页面 THEN System SHALL只显示当前登录商家的申请记录
-
-### 需求 5
-
-**用户故事:** 作为开发者,我需要后端API支持商家列表查询,以便前端实现商家选择功能。
-
-#### 验收标准
-
-1. WHEN 前端请求商家列表 THEN StoreAPI SHALL返回包含商家ID、名称、手机号和余额的数据
-2. WHEN 前端传入搜索关键词 THEN StoreAPI SHALL根据商家名称或手机号进行模糊匹配
-3. WHEN 前端传入分页参数 THEN StoreAPI SHALL返回分页后的商家列表
-4. WHEN 前端请求商家详情 THEN StoreAPI SHALL返回指定商家的完整信息包括余额

+ 0 - 82
.kiro/specs/admin-merchant-selector/tasks.md

@@ -1,82 +0,0 @@
-# 实施计划
-
-- [ ] 1. 后端 API 开发 - 商家列表接口增强
-  - 修改 `app/Services/Common/StoreService.php` 的 `getDataList()` 方法
-  - 添加余额字段到返回数据中(从关联的 Member 表获取)
-  - 确保支持按商家名称和手机号模糊搜索
-  - 确保支持分页参数
-  - _需求: 5.1, 5.2, 5.3_
-
-- [ ] 2. 前端组件开发 - 角色判断逻辑
-  - 在 `addons/admin/src/views/financial/accountLogApply.vue` 中实现 `checkUserRole()` 方法
-  - 根据 `user.store_id` 判断是否为超级管理员(store_id === 0 或 null)
-  - 设置 `isSuperAdmin` 数据字段
-  - _需求: 3.1, 3.2, 3.3_
-
-- [ ] 3. 前端组件开发 - 商家选择器 UI
-  - 添加商家选择器 el-select 组件(使用 v-if="isSuperAdmin" 条件渲染)
-  - 配置远程搜索、可清空、可过滤等属性
-  - 实现选项显示格式:商家名称、手机号、余额
-  - 添加必要的数据字段:storeOptions, storeSearchLoading
-  - _需求: 1.1, 1.5, 2.3_
-
-- [ ] 4. 前端功能开发 - 商家搜索
-  - 实现 `searchStores(query)` 方法
-  - 调用 `/store/index` API 获取商家列表
-  - 处理加载状态和错误情况
-  - _需求: 2.1, 2.2_
-
-- [ ] 5. 前端功能开发 - 商家选择处理
-  - 实现 `handleStoreChange(storeId)` 方法
-  - 选择商家后加载商家信息(user_id, name, real_name, phone, balance)
-  - 更新 storeInfo 数据对象
-  - 调用 `loadRecords()` 加载该商家的申请记录
-  - 处理清空选择的情况
-  - _需求: 1.2, 1.3, 4.1, 4.2_
-
-- [ ] 6. 前端功能开发 - 商家用户自动加载
-  - 实现 `loadStoreInfoForMerchant()` 方法
-  - 调用 `/store/currentInfo` API 获取当前登录商家信息
-  - 自动设置 `applyForm.store_id`
-  - 加载商家信息和申请记录
-  - _需求: 1.5, 3.3_
-
-- [ ] 7. 前端功能开发 - 表单验证增强
-  - 在 applyRules 中添加 store_id 验证规则
-  - 超级管理员必须选择商家才能提交
-  - 使用自定义 validator 函数
-  - _需求: 3.4_
-
-- [ ] 8. 前端功能开发 - 提交申请逻辑调整
-  - 修改 `submitApply()` 方法
-  - 确保使用选中商家的 user_id 提交申请
-  - 验证提现金额不超过商家余额
-  - _需求: 1.4_
-
-- [ ] 9. 前端功能开发 - 申请记录过滤
-  - 修改 `loadRecords()` 方法
-  - 超级管理员:根据选中商家的 user_id 过滤记录
-  - 商家用户:只显示自己的记录
-  - 未选择商家时显示空列表
-  - _需求: 4.1, 4.2, 4.3, 4.4_
-
-- [ ] 10. 生命周期集成
-  - 在 `mounted()` 钩子中调用 `checkUserRole()`
-  - 根据角色执行相应的初始化逻辑
-  - 超级管理员:初始加载商家列表
-  - 商家用户:自动加载当前商家信息
-  - _需求: 1.1, 1.5_
-
-- [ ] 11. 单元测试 - 前端组件测试
-  - 测试 `checkUserRole()` 方法的角色判断逻辑
-  - 测试商家选择器的显示/隐藏逻辑
-  - 测试 `handleStoreChange()` 的数据更新
-  - 测试表单验证规则
-
-- [ ] 12. 单元测试 - 后端服务测试
-  - 测试 StoreService 的余额字段返回
-  - 测试商家列表的搜索和分页功能
-  - 测试权限验证逻辑
-
-- [ ] 13. 检查点 - 确保所有测试通过
-  - 确保所有测试通过,如有问题请询问用户

+ 35 - 32
addons/admin/src/views/store/store.vue

@@ -57,12 +57,12 @@
 					</el-col>
 					<el-col :md="12" :sm="12">
 						<div class="ele-form-actions">
-						<el-button type="primary" @click="$refs.table.reload()" icon="el-icon-search"
-						class="ele-btn-icon">查询</el-button>
-						<el-button @click="handleReset">重置</el-button>
-						 <el-button type="success" @click="showAdd" icon="el-icon-plus"
-									v-if="permission.includes('sys:store:add')">新增商家</el-button>
-							</div>
+							<el-button type="primary" @click="$refs.table.reload()" icon="el-icon-search"
+								class="ele-btn-icon">查询</el-button>
+							<el-button @click="handleReset">重置</el-button>
+							<el-button type="success" @click="showAdd" icon="el-icon-plus"
+								v-if="permission.includes('sys:store:add')">新增商家</el-button>
+						</div>
 					</el-col>
 				</el-row>
 			</el-form>
@@ -114,7 +114,7 @@
 					<el-table-column label="操作" width="280px" align="center" :resizable="false" fixed="right">
 						<template slot-scope="{row}">
 							<el-link @click="showInfo(row)" icon="el-icon-view" type="primary" :underline="false"
-								v-if="permission.includes('store:store:index')">详情</el-link>
+								v-if="permission.includes('store:store:view')">详情</el-link>
 							<el-link @click="showEdit(row)" icon="el-icon-edit" type="warning" :underline="false"
 								v-if="permission.includes('sys:store:edit')">编辑</el-link>
 							<el-link @click="showConfirm(row)" icon="el-icon-check" type="success" :underline="false"
@@ -169,7 +169,8 @@
 						</el-col>
 						<el-col :sm="12">
 							<el-form-item label="商家分类:" prop="category_id">
-								<el-select v-if="dialogMode !== 'detail'" v-model="form.category_id" placeholder="请选择商家分类" clearable filterable size="medium" style="width: 100%">
+								<el-select v-if="dialogMode !== 'detail'" v-model="form.category_id" placeholder="请选择商家分类" clearable
+									filterable size="medium" style="width: 100%">
 									<el-option v-for="item in categoryOptions" :key="item.id" :label="item.name" :value="item.id" />
 								</el-select>
 								<span v-else class="detail-text">{{ form.category_name || '未分类' }}</span>
@@ -178,7 +179,8 @@
 						<el-col :sm="12">
 							<el-form-item label="佣金比例:" prop="bonus_rate">
 								<div v-if="dialogMode !== 'detail'" class="bonus-rate-input">
-									<el-input-number v-model="form.bonus_rate" :min="0" :max="100" :precision="2" placeholder="请输入佣金比例" size="medium" class="rate-number" />
+									<el-input-number v-model="form.bonus_rate" :min="0" :max="100" :precision="2" placeholder="请输入佣金比例"
+										size="medium" class="rate-number" />
 									<span class="unit-text">%</span>
 								</div>
 								<span v-else class="detail-text bonus-rate">{{ form.bonus_rate || 0 }}%</span>
@@ -200,8 +202,7 @@
 									</div>
 								</div>
 								<div v-else-if="form.logo" class="image-preview">
-									<el-image :src="form.logo" class="preview-image"
-										:preview-src-list="[form.logo]" />
+									<el-image :src="form.logo" class="preview-image" :preview-src-list="[form.logo]" />
 								</div>
 								<span v-else class="detail-text empty-text">未上传</span>
 							</el-form-item>
@@ -229,8 +230,7 @@
 						<el-col :sm="24" v-if="dialogMode === 'detail' && form.other_photo">
 							<el-form-item label="其他证件照:">
 								<div class="image-preview">
-									<el-image :src="form.other_photo" class="preview-image"
-										:preview-src-list="[form.other_photo]" />
+									<el-image :src="form.other_photo" class="preview-image" :preview-src-list="[form.other_photo]" />
 								</div>
 							</el-form-item>
 						</el-col>
@@ -244,7 +244,8 @@
 			</div>
 			<div slot="footer" class="dialog-footer">
 				<el-button @click="showDialog = false" size="medium">{{ dialogMode === 'detail' ? '关闭' : '取消' }}</el-button>
-				<el-button v-if="dialogMode !== 'detail'" type="primary" @click="saveStore" size="medium" :loading="saveLoading">
+				<el-button v-if="dialogMode !== 'detail'" type="primary" @click="saveStore" size="medium"
+					:loading="saveLoading">
 					<i class="el-icon-check" style="margin-right: 5px;"></i>
 					确定
 				</el-button>
@@ -554,16 +555,16 @@ export default {
 				if (valid) {
 					this.saveLoading = true;
 					const url = this.dialogMode === 'add' ? '/store/add' : '/store/edit';
-					
+
 					// 确保所有字段都被包含,包括空字符串
 					const formData = {
 						...this.form,
 						// 确保 address字段始终存在,即使是空字符串
 						address: this.form.address || ''
 					};
-					
+
 					console.log('提交的表单数据:', formData);
-					
+
 					this.$http.post(url, formData).then(res => {
 						this.saveLoading = false;
 						if (res.data.code === 0) {
@@ -777,20 +778,20 @@ export default {
 		padding: 0;
 		max-height: 75vh;
 		overflow-y: auto;
-		
+
 		&::-webkit-scrollbar {
 			width: 8px;
 		}
-		
+
 		&::-webkit-scrollbar-track {
 			background: #f1f1f1;
 			border-radius: 4px;
 		}
-		
+
 		&::-webkit-scrollbar-thumb {
 			background: #c1c1c1;
 			border-radius: 4px;
-			
+
 			&:hover {
 				background: #a8a8a8;
 			}
@@ -834,7 +835,7 @@ export default {
 	.el-row {
 		margin-left: -12px !important;
 		margin-right: -12px !important;
-		
+
 		.el-col {
 			padding-left: 12px !important;
 			padding-right: 12px !important;
@@ -844,6 +845,7 @@ export default {
 
 /* 表单样式 */
 .store-form {
+
 	// 确保表单项之间有足够的间距
 	::v-deep .el-form-item {
 		margin-bottom: 28px !important;
@@ -869,9 +871,10 @@ export default {
 			align-items: center;
 		}
 
-		.el-input, .el-select {
+		.el-input,
+		.el-select {
 			width: 100%;
-			
+
 			::v-deep .el-input__inner {
 				border-radius: 6px;
 				border: 1.5px solid #e4e7ed;
@@ -895,7 +898,7 @@ export default {
 
 		.el-input-number {
 			width: 100%;
-			
+
 			&:not(.rate-number) {
 				::v-deep .el-input__inner {
 					border-radius: 6px;
@@ -916,7 +919,7 @@ export default {
 	// 为每个栏目添加间距
 	.el-row {
 		margin-bottom: 0;
-		
+
 		.el-col {
 			padding-bottom: 0;
 		}
@@ -967,17 +970,17 @@ export default {
 	.rate-number {
 		flex: 1;
 		margin-right: 0;
-		
+
 		::v-deep .el-input__inner {
 			border-radius: 6px 0 0 6px !important;
 			border-right: none !important;
 		}
-		
+
 		::v-deep .el-input-number__increase,
 		::v-deep .el-input-number__decrease {
 			border-radius: 0;
 		}
-		
+
 		::v-deep .el-input-number__increase {
 			border-radius: 0;
 			border-right: none;
@@ -1181,11 +1184,11 @@ export default {
 
 	.dialog-content {
 		padding: 20px 16px;
-		
+
 		.el-row {
 			margin-left: -8px !important;
 			margin-right: -8px !important;
-			
+
 			.el-col {
 				padding-left: 8px !important;
 				padding-right: 8px !important;
@@ -1196,7 +1199,7 @@ export default {
 	.store-form {
 		::v-deep .el-form-item {
 			margin-bottom: 20px !important;
-			
+
 			.el-form-item__label {
 				width: 100px !important;
 				margin-bottom: 6px;

+ 3 - 1
app/Http/Controllers/Admin/Backend.php

@@ -106,7 +106,9 @@ class Backend extends BaseController
             $userInfo = $adminModel->getInfo($this->userId);
             $this->userInfo = $userInfo;
             $storeModel = new StoreModel();
-            $storeInfo = $storeModel->getInfoByUserId($userId);
+            // 通过 user.user_id(member ID)查询关联的商家
+            $memberId = $userInfo['user_id'] ?? 0;
+            $storeInfo = $memberId > 0 ? $storeModel->getInfoByUserId($memberId) : null;
             $this->storeId = $storeInfo ? $storeInfo['id'] : 0;
             $this->memberId = $storeInfo ? $storeInfo['user_id'] : 0;
         }

+ 2 - 2
app/Models/BaseModel.php

@@ -265,7 +265,7 @@ class BaseModel extends CacheModel
 
             // 添加人
             if (isset($info['create_user']) && !empty($info['create_user'])) {
-                $info['create_user_name'] = $adminAll[$info['create_user']]['realname'];
+                $info['create_user_name'] = isset($adminAll[$info['create_user']]) ? $adminAll[$info['create_user']]['realname'] : '未知';
             }
 
             // 添加时间
@@ -275,7 +275,7 @@ class BaseModel extends CacheModel
 
             // 更新人
             if (isset($info['update_user']) && !empty($info['update_user'])) {
-                $info['update_user_name'] = $adminAll[$info['update_user']]['realname'];
+                $info['update_user_name'] = isset($adminAll[$info['update_user']]) ? $adminAll[$info['update_user']]['realname'] : '未知';
             }
 
             // 更新时间

+ 1 - 1
app/Models/UserModel.php

@@ -34,7 +34,7 @@ class UserModel extends BaseModel
      */
     public function getInfo($id)
     {
-        $info = parent::getInfo($id); // TODO: Change the autogenerated stub
+        $info = parent::getInfo($id);
         if ($info) {
             // 头像
             if ($info['avatar']) {

+ 52 - 22
app/Services/Common/StoreService.php

@@ -229,7 +229,14 @@ class StoreService extends BaseService
             $data['address'] = '';
         }
 
-        return parent::edit($data);
+        $result = parent::edit($data);
+
+        // 编辑成功后清除商家缓存
+        if ($result['code'] == 0) {
+            RedisService::keyDel("caches:storeId:id*");
+        }
+
+        return $result;
     }
 
     /**
@@ -249,8 +256,8 @@ class StoreService extends BaseService
             return false;
         }
 
-        $info = $this->model->where(['id' => $id, 'mark' => 1])->first();
-        if (!$info) {
+        $storesModel = $this->model->where(['id' => $id, 'mark' => 1])->first();
+        if (!$storesModel) {
             $this->error = '商家信息不存在';
             return false;
         }
@@ -261,14 +268,17 @@ class StoreService extends BaseService
             // 使用事务确保数据一致性
             DB::beginTransaction();
             try {
-                // 设置默认密码
-                $password = '123456';
+                // 密码使用手机号后6位
+                $password = substr($storesModel->phone, -6);
 
                 // 使用商家手机号作为管理账号用户名
-                $username = $this->generateUsername($info);
+                $username = $this->generateUsername($storesModel);
+
+                // 保存原始的member ID(shop.user_id应该指向member.id)
+                $memberId = $storesModel->user_id;
 
                 // 检查是否已存在相同手机号的管理员账号
-                $existingUser = UserModel::where('mobile', $info->phone)
+                $existingUser = UserModel::where('mobile', $storesModel->phone)
                     ->where('mark', 1)
                     ->first();
 
@@ -276,10 +286,11 @@ class StoreService extends BaseService
                     // 如果存在相同手机号的账号,更新该账号
                     $adminUserId = $existingUser->id;
                     UserModel::where('id', $adminUserId)->update([
+                        'user_id' => $memberId, // 确保user.user_id指向member.id
                         'username' => $username,
                         'password' => get_password($password . $username),
-                        'realname' => $info->real_name,
-                        'mobile' => $info->phone,
+                        'realname' => $storesModel->real_name,
+                        'mobile' => $storesModel->phone,
                         'status' => 1,
                         'update_user' => $userId,
                         'update_time' => time()
@@ -287,12 +298,12 @@ class StoreService extends BaseService
                 } else {
                     // 创建新的管理账户
                     $userData = [
-                        'user_id' => $info->user_id, // 绑定商户用户ID(member表的ID)
+                        'user_id' => $memberId, // 绑定会员ID(member表的ID)
                         'username' => $username,
                         'nickname' => $username,
                         'password' => get_password($password . $username),
-                        'realname' => $info->real_name,
-                        'mobile' => $info->phone,
+                        'realname' => $storesModel->real_name,
+                        'mobile' => $storesModel->phone,
                         'status' => 1,
                         'create_user' => $userId,
                         'create_time' => time(),
@@ -304,9 +315,6 @@ class StoreService extends BaseService
                     $adminUserId = UserModel::insertGetId($userData);
                 }
 
-                // 更新商家表的user_id字段
-                $info->user_id = $adminUserId;
-
                 // 删除已存在的用户角色关系
                 DB::table('user_role')->where('user_id', $adminUserId)->delete();
 
@@ -316,16 +324,19 @@ class StoreService extends BaseService
                     'role_id' => 6
                 ]);
 
-                // 更新商家状态为已审核
+                // 更新商家状态为已审核,保持user_id为member ID
                 $updateData = [
                     'status' => 1,
-                    'user_id' => $adminUserId,
+                    'user_id' => $memberId, // 保持shop.user_id指向member.id
                     'confirm_remark' => $remark ?: '审核通过',
                     'update_time' => time()
                 ];
 
                 $this->model->where('id', $id)->update($updateData);
 
+                // 清除商家缓存
+                RedisService::keyDel("caches:storeId:id*");
+
                 DB::commit();
 
                 $this->error = '审核通过,管理账号:' . $username . ',密码:' . $password;
@@ -334,8 +345,8 @@ class StoreService extends BaseService
                 DB::rollBack();
                 Log::error('商家审核失败,创建管理员账号出错:' . $e->getMessage(), [
                     'store_id' => $id,
-                    'store_name' => $info->name,
-                    'phone' => $info->phone,
+                    'store_name' => $storesModel->name,
+                    'phone' => $storesModel->phone,
                     'exception' => $e->getTraceAsString()
                 ]);
                 $this->error = '审核失败,创建管理员账号失败:' . $e->getMessage();
@@ -541,18 +552,21 @@ class StoreService extends BaseService
             // 使用事务确保数据一致性
             DB::beginTransaction();
             try {
-                // 更新 user 表状态(stores.user_id = user.id
-                UserModel::where('id', $info->user_id)->update([
+                // 更新 user 表状态(通过 user.user_id = shop.user_id 查找
+                UserModel::where('user_id', $info->user_id)->update([
                     'status' => $userStatus,
                     'update_time' => time()
                 ]);
 
-                // 更新 member 表状态(user.id = member.id
+                // 更新 member 表状态(通过 member.id = shop.user_id 查找
                 MemberModel::where('id', $info->user_id)->update([
                     'status' => $memberStatus,
                     'update_time' => time()
                 ]);
 
+                // 清除商家缓存
+                RedisService::keyDel("caches:storeId:id*");
+
                 DB::commit();
             } catch (\Exception $e) {
                 DB::rollBack();
@@ -582,4 +596,20 @@ class StoreService extends BaseService
 
         return $list;
     }
+
+    /**
+     * 删除商家(重写父类方法以清除缓存)
+     * @return array
+     */
+    public function delete()
+    {
+        $result = parent::delete();
+
+        // 删除成功后清除商家缓存
+        if ($result['code'] == 0) {
+            RedisService::keyDel("caches:storeId:id*");
+        }
+
+        return $result;
+    }
 }

+ 32 - 13
app/Services/Common/UserService.php

@@ -70,11 +70,12 @@ class UserService extends BaseService
         // 为每个用户添加商家信息
         if ($result['code'] == 0 && isset($result['data']) && is_array($result['data'])) {
             foreach ($result['data'] as &$user) {
-                // 查询该用户对应的商家信息
-                $store = \App\Models\StoreModel::where('user_id', $user['id'])
+                // 查询该用户对应的商家信息(通过 user.user_id = shop.user_id 关联)
+                $memberId = $user['user_id'] ?? 0;
+                $store = $memberId > 0 ? \App\Models\StoreModel::where('user_id', $memberId)
                     ->where('mark', 1)
                     ->select(['id', 'name', 'real_name', 'phone'])
-                    ->first();
+                    ->first() : null;
 
                 $user['store_info'] = $store ? $store->toArray() : null;
             }
@@ -93,6 +94,10 @@ class UserService extends BaseService
     {
         // 请求参数
         $data = request()->all();
+
+        // 强制保持 mark=1,防止前端误传 mark=0
+        $data['mark'] = 1;
+
         // 用户名
         $username = trim($data['username']);
         // 密码
@@ -136,8 +141,15 @@ class UserService extends BaseService
                 ->where('mark', 1)
                 ->first();
 
-            // 如果商家已被关联,且不是当前用户,先解除旧关联
-            if ($existingStore && $existingStore->user_id != ($data['id'] ?? 0)) {
+            // 获取当前用户的member ID
+            $currentMemberId = 0;
+            if (isset($data['id']) && $data['id']) {
+                $currentUser = \App\Models\UserModel::where('id', $data['id'])->first();
+                $currentMemberId = $currentUser ? $currentUser->user_id : 0;
+            }
+
+            // 如果商家已被关联,且不是当前用户的member ID,先解除旧关联
+            if ($existingStore && $existingStore->user_id != $currentMemberId) {
                 // 解除旧用户的商家关联
                 \App\Models\StoreModel::where('id', $data['store_id'])
                     ->where('mark', 1)
@@ -177,9 +189,10 @@ class UserService extends BaseService
         $userInfo['roles'] = [];
         $userInfo['authorities'] = [];
 
-        // 获取用户关联的商户ID
+        // 获取用户关联的商户ID(通过 user.user_id = shop.user_id 关联)
         $storeModel = new \App\Models\StoreModel();
-        $storeInfo = $storeModel->getInfoByUserId($userId);
+        $memberId = $userInfo['user_id'] ?? 0;
+        $storeInfo = $memberId > 0 ? $storeModel->getInfoByUserId($memberId) : null;
         $userInfo['store_id'] = $storeInfo ? $storeInfo['id'] : 0;
 
         // 权限节点列表
@@ -303,25 +316,31 @@ class UserService extends BaseService
 
     /**
      * 更新商家关联
-     * @param int $userId 用户ID
+     * @param int $userId 用户ID(user表主键)
      * @param int|null $storeId 商家ID
      */
     private function updateStoreRelation($userId, $storeId)
     {
         try {
-            // 先清除该用户之前的商家关联
-            \App\Models\StoreModel::where('user_id', $userId)
+            // 获取用户的member ID(user.user_id)
+            $user = \App\Models\UserModel::where('id', $userId)->first();
+            if (!$user) {
+                return;
+            }
+            $memberId = $user->user_id;
+
+            // 先清除该member之前的商家关联
+            \App\Models\StoreModel::where('user_id', $memberId)
                 ->where('mark', 1)
                 ->update(['user_id' => 0]);
 
-            // 如果选择了商家,更新商家的 user_id
+            // 如果选择了商家,更新商家的 user_id 为 member ID
             if ($storeId) {
                 \App\Models\StoreModel::where('id', $storeId)
                     ->where('mark', 1)
-                    ->update(['user_id' => $userId]);
+                    ->update(['user_id' => $memberId]);
             }
         } catch (\Exception $e) {
-            \Log::error('更新商家关联失败:' . $e->getMessage());
         }
     }
 }

+ 19 - 22
database/seeders/StoreTestDataSeeder.php

@@ -157,8 +157,24 @@ class StoreTestDataSeeder extends Seeder
         foreach ($testData as $index => $data) {
             DB::beginTransaction();
             try {
-                // 1. 先插入 user 表(后台用户)
+                // 1. 先插入 member 表(会员表,获取 member.id)
+                $memberId = DB::table('member')->insertGetId([
+                    'openid' => $data['member']['openid'],
+                    'mobile' => $data['member']['mobile'],
+                    'user_type' => $data['member']['user_type'],
+                    'password' => $data['member']['password'],
+                    'realname' => $data['member']['realname'],
+                    'nickname' => $data['member']['nickname'],
+                    'gender' => $data['member']['gender'],
+                    'status' => $data['member']['status'],
+                    'create_time' => $now,
+                    'update_time' => $now,
+                    'mark' => 1,
+                ]);
+
+                // 2. 插入 user 表(管理员表,user.user_id 关联 member.id)
                 $userId = DB::table('user')->insertGetId([
+                    'user_id' => $memberId, // 关联会员ID
                     'realname' => $data['user']['realname'],
                     'nickname' => $data['user']['nickname'],
                     'gender' => $data['user']['gender'],
@@ -175,28 +191,9 @@ class StoreTestDataSeeder extends Seeder
                     'mark' => 1,
                 ]);
 
-                // 2. 插入 member 表(小程序用户,id 与 user.id 相同)
-                // 注意:需要确保 member 表的 id 字段允许手动指定
-                DB::statement('SET FOREIGN_KEY_CHECKS=0;'); // 临时禁用外键检查(如果需要)
-                DB::table('member')->insert([
-                    'id' => $userId, // 使用 user.id 作为 member.id
-                    'openid' => $data['member']['openid'],
-                    'mobile' => $data['member']['mobile'],
-                    'user_type' => $data['member']['user_type'],
-                    'password' => $data['member']['password'],
-                    'realname' => $data['member']['realname'],
-                    'nickname' => $data['member']['nickname'],
-                    'gender' => $data['member']['gender'],
-                    'status' => $data['member']['status'],
-                    'create_time' => $now,
-                    'update_time' => $now,
-                    'mark' => 1,
-                ]);
-                DB::statement('SET FOREIGN_KEY_CHECKS=1;'); // 恢复外键检查
-
-                // 3. 插入 stores 表(商家,user_id 关联 user.id)
+                // 3. 插入 stores 表(商家表,store.user_id 关联 member.id)
                 DB::table('stores')->insert([
-                    'user_id' => $userId,
+                    'user_id' => $memberId, // 关联会员ID,确保 user.user_id = member.id = store.user_id
                     'name' => $data['store']['name'],
                     'real_name' => $data['store']['real_name'],
                     'phone' => $data['store']['phone'],