Răsfoiți Sursa

feat(lakala): 优化分账订单管理功能

- 新增用户信息展示列,包含用户ID、昵称、用户名和手机号
- 调整分账总金额列样式,右对齐并增大字体
- 扩展接收方商户信息列,增加分账商户号显示
- 移除冗余的用户ID和接收方编号列
- 操作列新增通过/驳回按钮及资金明细查看功能
- 实现驳回对话框,支持输入驳回原因
- 新增资金明细分页表格,展示用户账户变动记录
- 优化表格样式和用户体验,调整列宽和布局
- 后端关联用户模型,预加载用户基础信息
- 添加用户模型关联关系,支持按用户字段查询
- 数据列表默认按ID倒序排列
runphp 4 luni în urmă
părinte
comite
3e7b0941f3

+ 278 - 105
resource/admin/ProfitShareOrder.vue

@@ -47,6 +47,28 @@
         highlight-current-row
       >
         <el-table-column prop="id" label="ID" width="80" align="center" />
+        <el-table-column label="用户信息" width="200">
+          <template #default="{ row }">
+            <div class="user-info">
+              <div class="user-detail-item">
+                <span class="label">ID:</span>
+                <span class="value">{{ row.user_id }}</span>
+              </div>
+              <div class="user-detail-item">
+                <span class="label">昵称:</span>
+                <span class="value">{{ row.user?.nickname || '-' }}</span>
+              </div>
+              <div class="user-detail-item">
+                <span class="label">用户名:</span>
+                <span class="value">{{ row.user?.username || '-' }}</span>
+              </div>
+              <div class="user-detail-item">
+                <span class="label">手机:</span>
+                <span class="value">{{ row.user?.mobile || '-' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
         <el-table-column label="分账指令流水号" min-width="240">
           <template #default="{ row }">
             <div class="order-numbers">
@@ -61,17 +83,16 @@
             </div>
           </template>
         </el-table-column>
-        <el-table-column prop="user_id" label="用户ID" width="100" align="center" />
-        <el-table-column prop="merchant_no" label="分账商户号" width="160" />
-        <el-table-column prop="total_amt" label="分账总金额(元)" width="140" align="center">
+        <el-table-column prop="total_amt" label="分账总金额(元)" width="140" align="right">
           <template #default="{ row }">
-            <span style="font-weight: 600; color: #e6a23c; font-size: 15px">¥{{ formatAmountToYuan(row.total_amt) }}</span>
+            <span style="font-weight: 600; color: #e6a23c; font-size: 16px">¥{{ formatAmountToYuan(row.total_amt) }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="接收方商户信息" width="200">
+        <el-table-column label="接收方商户信息" width="220">
           <template #default="{ row }">
             <div class="merchant-info">
               <div class="merchant-no" :title="row.recv_merchant_no">商户号: {{ row.recv_merchant_no }}</div>
+              <div class="merchant-no" :title="row.merchant_no">分账商户号: {{ row.merchant_no }}</div>
               <div class="amount-details">
                 <div class="amount-row">
                   <span class="label">分账金额:</span>
@@ -103,7 +124,6 @@
             </div>
           </template>
         </el-table-column>
-        <el-table-column prop="recv_no" label="分账接收方编号" width="160" />
         <el-table-column prop="status" label="分账状态" width="120" align="center">
           <template #default="{ row }">
             <span :class="['status-badge', `status-${row.status.toLowerCase()}`]">{{ getStatusText(row.status) }}</span>
@@ -112,9 +132,22 @@
         <el-table-column prop="fail_reason" label="分账失败原因" min-width="180" />
         <el-table-column prop="create_time" label="创建时间" width="180" align="center" />
         <el-table-column prop="update_time" label="更新时间" width="180" align="center" />
-        <el-table-column label="操作" width="150" fixed="right">
+        <el-table-column label="操作" width="180" fixed="right">
           <template #default="{ row }">
-            <el-button type="primary" size="small" @click="handleView(row)">详情</el-button>
+            <div v-if="row.status === 'PENDING'">
+              <el-button type="success" size="small" @click="handleApprove(row)">通过</el-button>
+              <el-button type="danger" size="small" @click="handleReject(row)">驳回</el-button>
+            </div>
+            <div>
+              <el-button 
+                type="primary" 
+                size="small" 
+                link 
+                @click="showBalanceRecordDialog(row.user_id)"
+              >
+                资金明细
+              </el-button>
+            </div>
           </template>
         </el-table-column>
       </el-table>
@@ -132,70 +165,75 @@
       />
     </el-card>
 
-    <!-- 详情对话框 -->
+    <!-- 驳回对话框 -->
     <el-dialog
-      v-model="detailDialogVisible"
-      title="分账申请详情"
-      width="600px"
-      :before-close="handleCloseDetailDialog"
+      v-model="rejectDialogVisible"
+      title="驳回分账申请"
+      width="500px"
+      destroy-on-close
     >
-      <el-form
-        v-loading="detailLoading"
-        :model="detailData"
-        label-width="160px"
-        label-position="left"
-      >
-        <el-form-item label="商户分账指令流水号:">
-          <span>{{ detailData.out_separate_no }}</span>
-        </el-form-item>
-        <el-form-item label="分账指令流水号:">
-          <span>{{ detailData.separate_no }}</span>
-        </el-form-item>
-        <el-form-item label="用户ID:">
-          <span>{{ detailData.user_id }}</span>
-        </el-form-item>
-        <el-form-item label="分账总金额(元):">
-          <span>{{ formatAmountToYuan(detailData.total_amt) }}</span>
-        </el-form-item>
-        <el-form-item label="用户分账金额:">
-          <div class="amount-calculation detail">
-            <div class="main-amount">
-              <span>总金额:</span>
-              <span class="amount">¥{{ formatAmountToYuan(getTotalSeparateAmount(detailData)) }}</span>
-            </div>
-            <div class="fee-amount">
-              <span>- 手续费:</span>
-              <span class="amount">¥{{ formatAmountToYuan(detailData.fee_amt) }}</span>
-            </div>
-            <div class="divider"></div>
-            <div class="result-amount">
-              <span>实际分账:</span>
-              <span class="amount">¥{{ formatAmountToYuan(detailData.separate_value) }}</span>
-            </div>
-          </div>
-        </el-form-item>
-        <el-form-item label="商户分账金额(元):">
-          <span>{{ formatAmountToYuan(getMerchantShareAmount(detailData)) }}</span>
-        </el-form-item>
-        <el-form-item label="分账接收方编号:">
-          <span>{{ detailData.recv_no }}</span>
-        </el-form-item>
-        <el-form-item label="分账状态:">
-          <span>{{ getStatusText(detailData.status) }}</span>
-        </el-form-item>
-        <el-form-item v-if="detailData.fail_reason" label="分账失败原因:">
-          <span>{{ detailData.fail_reason }}</span>
-        </el-form-item>
-        <el-form-item label="创建时间:">
-          <span>{{ detailData.create_time }}</span>
-        </el-form-item>
-        <el-form-item label="更新时间:">
-          <span>{{ detailData.update_time }}</span>
+      <el-form label-width="100px">
+        <el-form-item label="驳回原因" required>
+          <el-input 
+            v-model="rejectForm.reason" 
+            type="textarea" 
+            :rows="3" 
+            placeholder="请输入驳回原因"
+          />
         </el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
-          <el-button @click="handleCloseDetailDialog">关闭</el-button>
+          <el-button @click="rejectDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitReject" :loading="rejectLoading">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+    
+    <!-- 资金明细对话框 -->
+    <el-dialog
+      v-model="balanceRecordDialogVisible"
+      title="资金明细"
+      width="800px"
+      @close="closeBalanceRecordDialog"
+    >
+      <el-table
+        v-loading="balanceRecordLoading"
+        :data="balanceRecords"
+        stripe
+        style="width: 100%"
+      >
+        <el-table-column prop="id" label="ID" width="80" />
+        <el-table-column prop="type_text" label="类型" width="100" />
+        <el-table-column prop="amount" label="金额" width="120" align="right">
+          <template #default="scope">
+            <span :class="scope.row.amount > 0 ? 'amount-increase' : 'amount-decrease'">
+              {{ scope.row.amount > 0 ? '+' : '' }}{{ scope.row.amount }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="balance" label="余额" width="120" align="right" />
+        <el-table-column prop="description" label="描述" show-overflow-tooltip />
+        <el-table-column prop="create_time" label="时间" width="160" />
+      </el-table>
+
+      <!-- 资金明细分页 -->
+      <div class="pagination-container" style="margin-top: 20px;">
+        <el-pagination
+          v-model:current-page="balanceRecordPagination.page"
+          v-model:page-size="balanceRecordPagination.limit"
+          :total="balanceRecordPagination.total"
+          :page-sizes="[10, 20, 50]"
+          background
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleBalanceRecordSizeChange"
+          @current-change="handleBalanceRecordCurrentChange"
+        />
+      </div>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="closeBalanceRecordDialog">关闭</el-button>
         </span>
       </template>
     </el-dialog>
@@ -215,7 +253,8 @@ export default {
   data() {
     return {
       loading: false,
-      detailLoading: false,
+      rejectLoading: false,
+      balanceRecordLoading: false,
       searchForm: {
         out_separate_no: '',
         status: ''
@@ -226,8 +265,21 @@ export default {
         limit: 10,
         total: 0
       },
-      detailDialogVisible: false,
-      detailData: {}
+      // 驳回对话框相关
+      rejectDialogVisible: false,
+      rejectForm: {
+        orderId: null,
+        reason: ''
+      },
+      // 资金明细对话框相关
+      balanceRecordDialogVisible: false,
+      balanceRecords: [],
+      balanceRecordPagination: {
+        page: 1,
+        limit: 10,
+        total: 0
+      },
+      currentUserId: null
     }
   },
   async created() {
@@ -299,29 +351,6 @@ export default {
       }
     },
 
-    // 获取详情数据
-    async fetchDetail(id) {
-      if (!this.axiosInstance) {
-        this.$message.error('无法获取请求实例')
-        return
-      }
-
-      this.detailLoading = true
-      try {
-        const res = await this.axiosInstance.get(`/lakala/profit_share_order/${id}`)
-        if (res.code === 200) {
-          this.detailData = res.data
-        } else {
-          this.$message.error(res.msg || res.message || '获取详情失败')
-        }
-      } catch (error) {
-        console.error('获取分账申请详情失败:', error)
-        this.$message.error('获取详情失败: ' + (error.message || '未知错误'))
-      } finally {
-        this.detailLoading = false
-      }
-    },
-    
     // 查询
     handleSearch() {
       this.pagination.page = 1
@@ -338,28 +367,134 @@ export default {
       this.fetchData()
     },
     
-    // 查看详情
-    async handleView(row) {
-      await this.fetchDetail(row.id)
-      this.detailDialogVisible = true
+    // 通过分账申请
+    async handleApprove(row) {
+      try {
+        await this.$confirm('确认通过该分账申请吗?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        });
+        
+        const res = await this.axiosInstance.post(`/lakala/profit_share_order/${row.id}/approve`);
+        if (res.code === 200) {
+          this.$message.success('操作成功');
+          this.fetchData(); // 刷新列表
+        } else {
+          this.$message.error(res.msg || res.message || '操作失败');
+        }
+      } catch (error) {
+        if (error !== 'cancel') {
+          console.error('通过分账申请失败:', error);
+          this.$message.error('操作失败: ' + (error.message || '未知错误'));
+        }
+      }
+    },
+
+    // 显示驳回对话框
+    handleReject(row) {
+      this.rejectForm.orderId = row.id;
+      this.rejectForm.reason = '';
+      this.rejectDialogVisible = true;
     },
 
-    // 关闭详情对话框
-    handleCloseDetailDialog() {
-      this.detailDialogVisible = false
-      this.detailData = {}
+    // 提交驳回
+    async submitReject() {
+      if (!this.rejectForm.reason) {
+        this.$message.warning('请输入驳回原因');
+        return;
+      }
+      
+      this.rejectLoading = true;
+      try {
+        const res = await this.axiosInstance.post(`/lakala/profit_share_order/${this.rejectForm.orderId}/reject`, {
+          reason: this.rejectForm.reason
+        });
+        
+        if (res.code === 200) {
+          this.$message.success('驳回成功');
+          this.rejectDialogVisible = false;
+          this.fetchData(); // 刷新列表
+        } else {
+          this.$message.error(res.msg || res.message || '驳回失败');
+        }
+      } catch (error) {
+        console.error('驳回分账申请失败:', error);
+        this.$message.error('驳回失败: ' + (error.message || '未知错误'));
+      } finally {
+        this.rejectLoading = false;
+      }
     },
     
     // 分页相关
     handleSizeChange(val) {
-      this.pagination.limit = val
-      this.pagination.page = 1
-      this.fetchData()
+      this.pagination.limit = val;
+      this.pagination.page = 1;
+      this.fetchData();
     },
     
     handleCurrentChange(val) {
-      this.pagination.page = val
-      this.fetchData()
+      this.pagination.page = val;
+      this.fetchData();
+    },
+    
+    // 显示资金明细对话框
+    showBalanceRecordDialog(userId) {
+      this.currentUserId = userId;
+      this.balanceRecordDialogVisible = true;
+      this.getBalanceRecords();
+    },
+
+    // 关闭资金明细对话框
+    closeBalanceRecordDialog() {
+      this.balanceRecordDialogVisible = false;
+      this.balanceRecords = [];
+      this.balanceRecordPagination.page = 1;
+      this.balanceRecordPagination.total = 0;
+      this.currentUserId = null;
+    },
+
+    // 获取资金明细
+    async getBalanceRecords() {
+      if (!this.axiosInstance) {
+        this.$message.error('无法获取请求实例');
+        return;
+      }
+      
+      try {
+        this.balanceRecordLoading = true;
+        
+        const params = {
+          user_id: this.currentUserId,
+          page: this.balanceRecordPagination.page,
+          limit: this.balanceRecordPagination.limit
+        };
+        
+        const res = await this.axiosInstance.get('/balpay/log', { params });
+        if (res.code === 200) {
+          this.balanceRecords = res.page?.data || [];
+          this.balanceRecordPagination.total = res.page?.total || 0;
+        } else {
+          this.$message.error(res.msg || '获取资金明细失败');
+        }
+      } catch (error) {
+        console.error('获取资金明细失败:', error);
+        this.$message.error('获取资金明细失败');
+      } finally {
+        this.balanceRecordLoading = false;
+      }
+    },
+
+    // 资金明细分页处理
+    handleBalanceRecordSizeChange(size) {
+      this.balanceRecordPagination.limit = size;
+      this.balanceRecordPagination.page = 1;
+      this.getBalanceRecords();
+    },
+
+    handleBalanceRecordCurrentChange(page) {
+      this.balanceRecordPagination.page = page;
+      this.getBalanceRecords();
     }
   }
 }
@@ -405,6 +540,30 @@ export default {
   text-align: right;
 }
 
+.user-info {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.user-detail-item {
+  display: flex;
+  font-size: 12px;
+}
+
+.user-detail-item .label {
+  width: 50px;
+  font-weight: 500;
+  color: #606266;
+  flex-shrink: 0;
+}
+
+.user-detail-item .value {
+  flex: 1;
+  color: #303133;
+  word-break: break-all;
+}
+
 .order-numbers {
   display: flex;
   flex-direction: column;
@@ -579,4 +738,18 @@ export default {
 ::v-deep(.el-form-item__label) {
   font-weight: 500;
 }
+
+.amount-increase {
+  color: #67c23a;
+}
+
+.amount-decrease {
+  color: #f56c6c;
+}
+
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 20px;
+}
 </style>

+ 5 - 0
src/Entity/ProfitShareOrderEntity.php

@@ -11,6 +11,7 @@ use SixShop\Lakala\Facade\Config;
 use SixShop\Lakala\Facade\LedgerService;
 use SixShop\Lakala\Model\ProfitShareOrderModel;
 use SixShop\Lakala\Model\ProfitShareReceiverModel;
+use think\db\Query;
 use think\Paginator;
 use function SixShop\Core\throw_logic_exception;
 
@@ -22,7 +23,11 @@ class ProfitShareOrderEntity extends BaseEntity
     public function getOrderList(array $params, array $pageAndLimit): Paginator
     {
         return $this->withSearch(['out_separate_no', 'status'], $params)
+            ->with(['user' => function (Query $query) {
+                $query->field(['id', 'nickname', 'avatar', 'username', 'mobile']);
+            }])
             ->append(['status_text'])
+            ->order('id', 'desc')
             ->paginate($pageAndLimit);
     }
 

+ 7 - 0
src/Model/ProfitShareOrderModel.php

@@ -2,11 +2,13 @@
 declare(strict_types=1);
 namespace SixShop\Lakala\Model;
 
+use app\model\User;
 use SixShop\Lakala\Enum\ProfitShareOrderCMDTypeEnum;
 use SixShop\Lakala\Enum\ProfitShareOrderStatusEnum;
 use SixShop\Lakala\Enum\ReceiverStatusEnum;
 use SixShop\Payment\Enum\NumberBizEnum;
 use think\Model;
+use think\model\relation\BelongsTo;
 
 class ProfitShareOrderModel extends Model
 {
@@ -46,4 +48,9 @@ class ProfitShareOrderModel extends Model
             $query->where('status', $value);
         }
     }
+
+    public function user(): BelongsTo
+    {
+        return $this->belongsTo(User::class, 'user_id', 'id');
+    }
 }