Forráskód Böngészése

feat:完善财务模块基础页面构建

叶静 1 hónapja
szülő
commit
5eeed98aa4
100 módosított fájl, 2849 hozzáadás és 904 törlés
  1. 3 13
      .promptx/pouch.json
  2. 2 2
      .promptx/resource/project.registry.json
  3. BIN
      public/static/images/chat/chat.mp3
  4. BIN
      public/static/images/chat/head.png
  5. BIN
      public/static/images/chat/loading.png
  6. BIN
      public/static/images/chat/notice.mp3
  7. BIN
      public/static/images/chat/warning.png
  8. BIN
      public/static/images/index/wechat0.png
  9. BIN
      public/static/images/index/wechat1.png
  10. BIN
      public/static/images/install/bg.png
  11. BIN
      public/static/images/install/img-01.png
  12. BIN
      public/static/images/install/logo.png
  13. BIN
      public/static/images/platform/wechat/miniProgram.png
  14. BIN
      public/static/images/platform/wechat/officialAccount.png
  15. BIN
      public/static/images/platform/wechat/openPlatform.png
  16. BIN
      public/static/images/shop/config/H5.png
  17. BIN
      public/static/images/shop/config/H5BG.png
  18. BIN
      public/static/images/shop/config/WechatMiniProgram.png
  19. BIN
      public/static/images/shop/config/WechatMiniProgramBG.png
  20. BIN
      public/static/images/shop/config/WechatOfficialAccount.png
  21. BIN
      public/static/images/shop/config/WechatOfficialAccountBG.png
  22. BIN
      public/static/images/shop/config/app.png
  23. BIN
      public/static/images/shop/config/appBG.png
  24. BIN
      public/static/images/shop/config/icon-right.png
  25. BIN
      public/static/images/shop/config/upgrade-config.png
  26. BIN
      public/static/images/shop/decorate/avatar.png
  27. BIN
      public/static/images/shop/decorate/black.png
  28. BIN
      public/static/images/shop/decorate/coupon.png
  29. BIN
      public/static/images/shop/decorate/couponCard.png
  30. BIN
      public/static/images/shop/decorate/couponCardStyle.png
  31. BIN
      public/static/images/shop/decorate/floatMenu.png
  32. BIN
      public/static/images/shop/decorate/golden.png
  33. BIN
      public/static/images/shop/decorate/goodsCard.png
  34. BIN
      public/static/images/shop/decorate/goodsShelves.png
  35. BIN
      public/static/images/shop/decorate/green.png
  36. BIN
      public/static/images/shop/decorate/groupon.png
  37. BIN
      public/static/images/shop/decorate/guidePage.png
  38. BIN
      public/static/images/shop/decorate/header-WechatMiniProgram.png
  39. BIN
      public/static/images/shop/decorate/header-WechatOfficialAccount.png
  40. BIN
      public/static/images/shop/decorate/header-android.png
  41. BIN
      public/static/images/shop/decorate/header-diypage.png
  42. BIN
      public/static/images/shop/decorate/header-ios.png
  43. BIN
      public/static/images/shop/decorate/hotzone.png
  44. BIN
      public/static/images/shop/decorate/imageBanner.png
  45. BIN
      public/static/images/shop/decorate/imageBlock.png
  46. BIN
      public/static/images/shop/decorate/imageCube.png
  47. BIN
      public/static/images/shop/decorate/ios-bar.png
  48. BIN
      public/static/images/shop/decorate/lineBlock.png
  49. BIN
      public/static/images/shop/decorate/livePlayer.png
  50. BIN
      public/static/images/shop/decorate/menuButton.png
  51. BIN
      public/static/images/shop/decorate/menuGrid.png
  52. BIN
      public/static/images/shop/decorate/menuList.png
  53. BIN
      public/static/images/shop/decorate/mplive-1.png
  54. BIN
      public/static/images/shop/decorate/mplive-2.png
  55. BIN
      public/static/images/shop/decorate/mplive-3.png
  56. BIN
      public/static/images/shop/decorate/mplive.png
  57. BIN
      public/static/images/shop/decorate/noticeBlock.png
  58. BIN
      public/static/images/shop/decorate/orange.png
  59. BIN
      public/static/images/shop/decorate/orderCard.png
  60. BIN
      public/static/images/shop/decorate/orderCardStyle.png
  61. BIN
      public/static/images/shop/decorate/page.png
  62. BIN
      public/static/images/shop/decorate/picker.png
  63. BIN
      public/static/images/shop/decorate/popupImage.png
  64. BIN
      public/static/images/shop/decorate/preview_bg.png
  65. BIN
      public/static/images/shop/decorate/purple.png
  66. BIN
      public/static/images/shop/decorate/qrcode.png
  67. BIN
      public/static/images/shop/decorate/richtext.png
  68. BIN
      public/static/images/shop/decorate/score.png
  69. BIN
      public/static/images/shop/decorate/scoreGoods.png
  70. BIN
      public/static/images/shop/decorate/searchBlock.png
  71. BIN
      public/static/images/shop/decorate/seckill.png
  72. BIN
      public/static/images/shop/decorate/splashScreen.png
  73. BIN
      public/static/images/shop/decorate/subscribeWechatOfficialAccount.png
  74. BIN
      public/static/images/shop/decorate/tabbar.png
  75. BIN
      public/static/images/shop/decorate/titleBlock.png
  76. BIN
      public/static/images/shop/decorate/userCard.png
  77. BIN
      public/static/images/shop/decorate/videoPlayer.png
  78. BIN
      public/static/images/shop/decorate/walletCard.png
  79. BIN
      public/static/images/shop/decorate/walletCardStyle.png
  80. BIN
      public/static/images/shop/decorate/yellow.png
  81. BIN
      public/static/images/shop/goods/entity.png
  82. BIN
      public/static/images/shop/goods/sales1.png
  83. BIN
      public/static/images/shop/goods/sales2.png
  84. BIN
      public/static/images/shop/goods/stock1.png
  85. BIN
      public/static/images/shop/goods/stock2.png
  86. BIN
      public/static/images/shop/goods/virtual.png
  87. 329 0
      src/app/shop/admin/finance/commission/detail.vue
  88. 161 121
      src/app/shop/admin/finance/commission/index.vue
  89. 331 0
      src/app/shop/admin/finance/recharge/detail.vue
  90. 136 122
      src/app/shop/admin/finance/recharge/index.vue
  91. 110 0
      src/app/shop/admin/finance/withdraw/audit.vue
  92. 457 0
      src/app/shop/admin/finance/withdraw/detail.vue
  93. 159 162
      src/app/shop/admin/finance/withdraw/index.vue
  94. 155 210
      src/app/shop/admin/goods/goods/select.vue
  95. 94 63
      src/app/shop/admin/marketing/group/edit.vue
  96. 323 141
      src/app/shop/admin/marketing/group/index.vue
  97. 10 0
      src/assets/css/sa-global.scss
  98. 64 70
      src/sheep/components/sa-table/sa-search/sa-search-simple.global.vue
  99. 211 0
      src/sheep/mock/commission.js
  100. 304 0
      src/sheep/mock/group.js

+ 3 - 13
.promptx/pouch.json

@@ -1,26 +1,16 @@
 {
-  "currentState": "memory_saved",
+  "currentState": "initialized",
   "stateHistory": [
     {
       "from": "initial",
       "command": "init",
-      "timestamp": "2025-07-09T02:57:45.095Z",
+      "timestamp": "2025-07-14T01:54:37.215Z",
       "args": [
         {
           "workingDirectory": "d:\\work\\bandhu-buy\\admin"
         }
       ]
-    },
-    {
-      "from": "initialized",
-      "command": "remember",
-      "timestamp": "2025-07-11T01:27:25.481Z",
-      "args": [
-        "列表页面标准样式规范:基于 src/app/shop/admin/goods/goods/index.vue 的样式风格,包括:1. 使用 sa-search-simple 组件进行搜索;2. Tab 使用 sa-tabs 类名;3. 标题区域使用 sa-title sa-flex sa-row-between 布局;4. 表格使用 sa-table-wrap 和 sa-table 类名;5. 按钮使用 sa-button-refresh 类名;6. 整体容器使用 panel-block 类名;7. 间距和排版保持一致的 CSS 变量使用;8. 表格内容样式使用统一的字体大小和颜色变量。",
-        "--tags",
-        "UI样式 列表页面 标准规范"
-      ]
     }
   ],
-  "lastUpdated": "2025-07-11T01:27:25.528Z"
+  "lastUpdated": "2025-07-14T01:54:37.286Z"
 }

+ 2 - 2
.promptx/resource/project.registry.json

@@ -4,8 +4,8 @@
   "metadata": {
     "version": "2.0.0",
     "description": "project 级资源注册表",
-    "createdAt": "2025-07-11T01:27:25.516Z",
-    "updatedAt": "2025-07-11T01:27:25.517Z",
+    "createdAt": "2025-07-14T01:54:37.268Z",
+    "updatedAt": "2025-07-14T01:54:37.270Z",
     "resourceCount": 0
   },
   "resources": [],

BIN
public/static/images/chat/chat.mp3


BIN
public/static/images/chat/head.png


BIN
public/static/images/chat/loading.png


BIN
public/static/images/chat/notice.mp3


BIN
public/static/images/chat/warning.png


BIN
public/static/images/index/wechat0.png


BIN
public/static/images/index/wechat1.png


BIN
public/static/images/install/bg.png


BIN
public/static/images/install/img-01.png


BIN
public/static/images/install/logo.png


BIN
public/static/images/platform/wechat/miniProgram.png


BIN
public/static/images/platform/wechat/officialAccount.png


BIN
public/static/images/platform/wechat/openPlatform.png


BIN
public/static/images/shop/config/H5.png


BIN
public/static/images/shop/config/H5BG.png


BIN
public/static/images/shop/config/WechatMiniProgram.png


BIN
public/static/images/shop/config/WechatMiniProgramBG.png


BIN
public/static/images/shop/config/WechatOfficialAccount.png


BIN
public/static/images/shop/config/WechatOfficialAccountBG.png


BIN
public/static/images/shop/config/app.png


BIN
public/static/images/shop/config/appBG.png


BIN
public/static/images/shop/config/icon-right.png


BIN
public/static/images/shop/config/upgrade-config.png


BIN
public/static/images/shop/decorate/avatar.png


BIN
public/static/images/shop/decorate/black.png


BIN
public/static/images/shop/decorate/coupon.png


BIN
public/static/images/shop/decorate/couponCard.png


BIN
public/static/images/shop/decorate/couponCardStyle.png


BIN
public/static/images/shop/decorate/floatMenu.png


BIN
public/static/images/shop/decorate/golden.png


BIN
public/static/images/shop/decorate/goodsCard.png


BIN
public/static/images/shop/decorate/goodsShelves.png


BIN
public/static/images/shop/decorate/green.png


BIN
public/static/images/shop/decorate/groupon.png


BIN
public/static/images/shop/decorate/guidePage.png


BIN
public/static/images/shop/decorate/header-WechatMiniProgram.png


BIN
public/static/images/shop/decorate/header-WechatOfficialAccount.png


BIN
public/static/images/shop/decorate/header-android.png


BIN
public/static/images/shop/decorate/header-diypage.png


BIN
public/static/images/shop/decorate/header-ios.png


BIN
public/static/images/shop/decorate/hotzone.png


BIN
public/static/images/shop/decorate/imageBanner.png


BIN
public/static/images/shop/decorate/imageBlock.png


BIN
public/static/images/shop/decorate/imageCube.png


BIN
public/static/images/shop/decorate/ios-bar.png


BIN
public/static/images/shop/decorate/lineBlock.png


BIN
public/static/images/shop/decorate/livePlayer.png


BIN
public/static/images/shop/decorate/menuButton.png


BIN
public/static/images/shop/decorate/menuGrid.png


BIN
public/static/images/shop/decorate/menuList.png


BIN
public/static/images/shop/decorate/mplive-1.png


BIN
public/static/images/shop/decorate/mplive-2.png


BIN
public/static/images/shop/decorate/mplive-3.png


BIN
public/static/images/shop/decorate/mplive.png


BIN
public/static/images/shop/decorate/noticeBlock.png


BIN
public/static/images/shop/decorate/orange.png


BIN
public/static/images/shop/decorate/orderCard.png


BIN
public/static/images/shop/decorate/orderCardStyle.png


BIN
public/static/images/shop/decorate/page.png


BIN
public/static/images/shop/decorate/picker.png


BIN
public/static/images/shop/decorate/popupImage.png


BIN
public/static/images/shop/decorate/preview_bg.png


BIN
public/static/images/shop/decorate/purple.png


BIN
public/static/images/shop/decorate/qrcode.png


BIN
public/static/images/shop/decorate/richtext.png


BIN
public/static/images/shop/decorate/score.png


BIN
public/static/images/shop/decorate/scoreGoods.png


BIN
public/static/images/shop/decorate/searchBlock.png


BIN
public/static/images/shop/decorate/seckill.png


BIN
public/static/images/shop/decorate/splashScreen.png


BIN
public/static/images/shop/decorate/subscribeWechatOfficialAccount.png


BIN
public/static/images/shop/decorate/tabbar.png


BIN
public/static/images/shop/decorate/titleBlock.png


BIN
public/static/images/shop/decorate/userCard.png


BIN
public/static/images/shop/decorate/videoPlayer.png


BIN
public/static/images/shop/decorate/walletCard.png


BIN
public/static/images/shop/decorate/walletCardStyle.png


BIN
public/static/images/shop/decorate/yellow.png


BIN
public/static/images/shop/goods/entity.png


BIN
public/static/images/shop/goods/sales1.png


BIN
public/static/images/shop/goods/sales2.png


BIN
public/static/images/shop/goods/stock1.png


BIN
public/static/images/shop/goods/stock2.png


BIN
public/static/images/shop/goods/virtual.png


+ 329 - 0
src/app/shop/admin/finance/commission/detail.vue

@@ -0,0 +1,329 @@
+<template>
+  <el-container class="commission-detail">
+    <el-main>
+      <!-- 基本信息 -->
+      <div class="basic-info sa-m-b-26">
+        <h3 class="sa-m-b-20">基本信息</h3>
+        <el-row :gutter="20">
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">佣金单号:</span>
+              <span class="value">{{ commissionDetail.commission_no || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">佣金状态:</span>
+              <el-tag :type="getStatusType(commissionDetail.status)">
+                {{ commissionDetail.status_text || '--' }}
+              </el-tag>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">佣金类型:</span>
+              <span class="value">{{ commissionDetail.type_text || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">用户名:</span>
+              <span class="value">{{ commissionDetail.username || '--' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="sa-m-t-12">
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">手机号:</span>
+              <span class="value">{{ commissionDetail.phone || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">佣金金额:</span>
+              <span class="value">৳{{ commissionDetail.amount || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">佣金说明:</span>
+              <span class="value">{{ commissionDetail.description || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">订单号:</span>
+              <span class="value">{{ commissionDetail.order_no || '--' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="sa-m-t-12">
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">用户余额:</span>
+              <span class="value">৳{{ commissionDetail.user_balance || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">佣金余额:</span>
+              <span class="value">৳{{ commissionDetail.commission_balance || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">累计佣金:</span>
+              <span class="value">৳{{ commissionDetail.total_commission || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">发放时间:</span>
+              <span class="value">{{ commissionDetail.create_time || '--' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+
+      <!-- 操作日志 -->
+      <div class="operation-logs sa-m-b-26 mt-40px">
+        <h3 class="sa-m-b-20">操作日志</h3>
+        <div class="sa-table-wrap">
+          <el-table
+            class="sa-table"
+            :data="operationLogs.data"
+            v-loading="operationLogs.loading"
+            stripe
+            border
+          >
+            <template #empty>
+              <sa-empty />
+            </template>
+            <el-table-column prop="time" label="时间" min-width="160" align="center">
+              <template #default="scope">
+                {{ scope.row.time || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="operator" label="操作人" min-width="120" align="center">
+              <template #default="scope">
+                {{ scope.row.operator || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="action" label="事项" min-width="200">
+              <template #default="scope">
+                {{ scope.row.action || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="remark" label="备注" min-width="300">
+              <template #default="scope">
+                {{ scope.row.remark || '--' }}
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+
+        <!-- 分页 -->
+        <div class="sa-m-t-16" v-if="operationLogs.total > 0">
+          <sa-pagination
+            :pageData="operationLogs.pageData"
+            @updateFn="getOperationLogs"
+            layout="total, prev, pager, next"
+          />
+        </div>
+      </div>
+    </el-main>
+  </el-container>
+</template>
+
+<script setup>
+  import { onMounted, reactive, ref } from 'vue';
+  import { commissionMockData } from '@/sheep/mock/commission';
+  import { usePagination } from '@/sheep/hooks';
+
+  const props = defineProps(['modal']);
+  const emit = defineEmits(['modalCallBack']);
+
+  const commissionDetail = ref({});
+  const loading = ref(false);
+
+  // 操作日志分页
+  const { pageData: logPageData } = usePagination();
+  const operationLogs = reactive({
+    data: [],
+    loading: false,
+    total: 0,
+    pageData: logPageData,
+  });
+
+  // 获取状态类型
+  function getStatusType(status) {
+    const statusMap = {
+      processing: 'warning',
+      settled: 'success',
+      unsettled: 'danger',
+    };
+    return statusMap[status] || 'info';
+  }
+
+  // 获取佣金详情
+  async function getCommissionDetail() {
+    if (!props.modal?.params?.id) return;
+
+    loading.value = true;
+    try {
+      const result = commissionMockData.getDetail(props.modal.params.id);
+      if (result.error === 0) {
+        commissionDetail.value = result.data;
+      }
+    } catch (error) {
+      console.error('获取佣金详情失败:', error);
+    } finally {
+      loading.value = false;
+    }
+  }
+
+  // 获取操作日志
+  function getOperationLogs(page) {
+    if (page) operationLogs.pageData.page = page;
+    operationLogs.loading = true;
+
+    try {
+      // 模拟操作日志数据
+      const mockLogs = [
+        {
+          time: '2025/06/06 12:30:30',
+          operator: 'Aamir Khan',
+          action: '创建佣金记录',
+          remark: '--',
+        },
+        {
+          time: '2025/06/06 12:36:30',
+          operator: 'system',
+          action: '佣金状态更新为处理中',
+          remark: '--',
+        },
+        {
+          time: '2025/06/06 12:40:30',
+          operator: 'admin',
+          action: '佣金结算完成',
+          remark: '正常结算',
+        },
+      ];
+
+      operationLogs.data = mockLogs;
+      operationLogs.total = mockLogs.length;
+    } catch (error) {
+      console.error('获取操作日志失败:', error);
+    } finally {
+      operationLogs.loading = false;
+    }
+  }
+
+  onMounted(() => {
+    getCommissionDetail();
+    getOperationLogs();
+  });
+</script>
+
+<style lang="scss" scoped>
+  .commission-detail {
+    .basic-info {
+      h3 {
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--sa-title);
+      }
+
+      .info-item {
+        display: flex;
+        align-items: center;
+        margin-bottom: 8px;
+
+        .label {
+          flex-shrink: 0;
+          color: var(--sa-subfont);
+          font-size: 14px;
+          min-width: 80px;
+        }
+
+        .value {
+          color: var(--sa-subtitle);
+          font-size: 14px;
+          font-weight: 500;
+        }
+      }
+    }
+
+    .operation-logs {
+      h3 {
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--sa-title);
+      }
+
+      .sa-table {
+        border-radius: 8px;
+        overflow: hidden;
+        border: 1px solid #dcdfe6;
+
+        :deep(.el-table) {
+          border: none;
+          border-radius: 0;
+        }
+
+        :deep(.el-table__header-wrapper) {
+          background: var(--sa-table-header-bg, #f5f7fa);
+
+          .el-table__header {
+            background: var(--sa-table-header-bg, #f5f7fa);
+
+            th {
+              background: var(--sa-table-header-bg, #f5f7fa) !important;
+              font-weight: 600;
+              color: var(--sa-title, #303133);
+              border-bottom: 1px solid #dcdfe6;
+              border-right: 1px solid #dcdfe6;
+
+              &:first-child {
+                border-left: none;
+              }
+
+              &:last-child {
+                border-right: none;
+              }
+            }
+          }
+        }
+
+        :deep(.el-table__body-wrapper) {
+          .el-table__body {
+            tr {
+              &:hover {
+                background: #f5f7fa !important;
+              }
+
+              td {
+                border-bottom: 1px solid #dcdfe6;
+                border-right: 1px solid #dcdfe6;
+                border-left: none;
+
+                &:first-child {
+                  border-left: none;
+                }
+
+                &:last-child {
+                  border-right: none;
+                }
+              }
+
+              &:last-child td {
+                border-bottom: none;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 161 - 121
src/app/shop/admin/finance/commission/index.vue

@@ -15,7 +15,7 @@
         <div class="label sa-flex">佣金管理</div>
         <div>
           <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
-          <el-button icon="Plus" type="primary" @click="addRow">新建</el-button>
+          <el-button icon="Download" type="primary" @click="exportRecords">导出记录</el-button>
         </div>
       </div>
     </el-header>
@@ -25,86 +25,78 @@
           height="100%"
           class="sa-table"
           :data="table.data"
-          @selection-change="changeSelection"
           @sort-change="fieldFilter"
-          @row-dblclick="editRow"
+          @row-dblclick="viewDetail"
           row-key="id"
           stripe
         >
           <template #empty>
             <sa-empty />
           </template>
-          <el-table-column type="selection" width="48" align="center"></el-table-column>
-          <el-table-column prop="id" label="ID" min-width="100" sortable="custom">
+          <el-table-column prop="commission_no" label="佣金单号" min-width="180" sortable="custom">
+            <template #default="scope">
+              <span class="sa-table-line-1">{{ scope.row.commission_no || '-' }}</span>
+            </template>
           </el-table-column>
-          <el-table-column label="用户" min-width="120">
+          <el-table-column label="用户" min-width="120">
             <template #default="scope">
-              <span class="sa-table-line-1">
+              <el-link type="primary" :underline="true" @click="viewUserDetail(scope.row)">
                 {{ scope.row.username || '-' }}
-              </span>
+              </el-link>
+            </template>
+          </el-table-column>
+          <el-table-column prop="phone" label="手机号" min-width="130" align="center">
+            <template #default="scope">
+              {{ scope.row.phone || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="佣金金额" min-width="120">
+          <el-table-column label="佣金金额" min-width="120" align="center">
             <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
           </el-table-column>
-          <el-table-column label="佣金类型" min-width="120">
+          <el-table-column label="佣金说明" min-width="200">
             <template #default="scope">
-              <el-tag :type="scope.row.type === 'order' ? 'primary' : 'success'">
-                {{ scope.row.type_text || '-' }}
-              </el-tag>
+              <span class="sa-table-line-1">{{ scope.row.description || '-' }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="来源订单" min-width="150">
+          <el-table-column label="订单" min-width="150">
             <template #default="scope">
-              <span class="sa-table-line-1">
-                {{ scope.row.source_order_no || '-' }}
-              </span>
+              <el-link
+                v-if="scope.row.order_no"
+                type="primary"
+                :underline="true"
+                @click="viewOrderDetail(scope.row)"
+              >
+                {{ scope.row.order_no }}
+              </el-link>
+              <span v-else>--</span>
             </template>
           </el-table-column>
-          <el-table-column label="佣金比例" min-width="100">
-            <template #default="scope"> {{ scope.row.commission_rate || 0 }}% </template>
-          </el-table-column>
-          <el-table-column label="状态" min-width="100">
+          <el-table-column label="状态" min-width="100" align="center">
             <template #default="scope">
               <el-tag :type="getStatusType(scope.row.status)">
                 {{ scope.row.status_text || '-' }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="获得时间" min-width="160">
+          <el-table-column prop="create_time" label="发放时间" min-width="160" sortable="custom">
             <template #default="scope">
               {{ scope.row.create_time || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="结算时间" min-width="160">
+          <el-table-column label="到账时间" min-width="160">
             <template #default="scope">
-              {{ scope.row.settle_time || '-' }}
+              {{ scope.row.settle_time || '--' }}
             </template>
           </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="120">
+          <el-table-column fixed="right" label="操作" min-width="80">
             <template #default="scope">
-              <el-button class="is-link" type="primary" @click="editRow(scope.row)">查看</el-button>
-              <el-button
-                v-if="scope.row.status === 'pending'"
-                class="is-link"
-                type="success"
-                @click="settleCommission(scope.row)"
-              >
-                结算
-              </el-button>
+              <el-button link type="primary" @click="viewDetail(scope.row)">详情</el-button>
             </template>
           </el-table-column>
         </el-table>
       </div>
     </el-main>
     <sa-view-bar>
-      <template #left>
-        <sa-batch-handle
-          :batchHandleTools="batchHandleTools"
-          :selectedLeng="table.selected.length"
-          @batchHandle="batchHandle"
-        ></sa-batch-handle>
-      </template>
       <template #right>
         <sa-pagination :pageData="pageData" @updateFn="getData" />
       </template>
@@ -113,11 +105,12 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../finance.service';
-  import { ElMessageBox, ElMessage } from 'element-plus';
-  import { useModal } from '@/sheep/hooks';
-  import { usePagination } from '@/sheep/hooks';
-  import commissionEdit from './edit.vue';
+  import { ElMessage } from 'element-plus';
+  import { useModal, usePagination } from '@/sheep/hooks';
+  import { commissionMockData } from '@/sheep/mock/commission';
+  import UserDetail from '../../user/list/detail.vue';
+  import CommissionDetail from './detail.vue';
+  import OrderDetail from '../../order/order/detail.vue';
   const { pageData } = usePagination();
 
   // 搜索字段配置
@@ -126,7 +119,31 @@
       type: 'input',
       label: '用户名',
       placeholder: '请输入用户名',
-      width: 200,
+      width: 150,
+    },
+    phone: {
+      type: 'input',
+      label: '手机号',
+      placeholder: '请输入手机号',
+      width: 150,
+    },
+    commission_no: {
+      type: 'input',
+      label: '佣金单号',
+      placeholder: '请输入佣金单号',
+      width: 180,
+    },
+    status: {
+      type: 'select',
+      label: '佣金状态',
+      placeholder: '请选择状态',
+      width: 120,
+      options: [
+        { label: '全部', value: 'all' },
+        { label: '处理中', value: 'processing' },
+        { label: '已到账', value: 'settled' },
+        { label: '未到账', value: 'unsettled' },
+      ],
     },
     type: {
       type: 'select',
@@ -134,16 +151,30 @@
       placeholder: '请选择类型',
       width: 150,
       options: [
-        { label: '订单佣金', value: 'order' },
-        { label: '推荐佣金', value: 'referral' },
-        { label: '团队佣金', value: 'team' },
+        { label: '邀请奖励', value: 'invite_reward' },
+        { label: '拼团奖励', value: 'group_reward' },
+        { label: '开团奖励', value: 'leader_reward' },
+        { label: '签到奖励', value: 'checkin_reward' },
+        { label: '拼团奖励-下线', value: 'group_sub_reward' },
+        { label: '开团奖励-下线', value: 'leader_sub_reward' },
       ],
     },
+    date_range: {
+      type: 'daterange',
+      label: '时间',
+      placeholder: '请选择时间范围',
+      width: 240,
+    },
   });
+
   // 默认搜索值
   const defaultSearchValues = reactive({
     username: '',
+    phone: '',
+    commission_no: '',
+    status: 'all',
     type: '',
+    date_range: [],
   });
   // 列表
   const table = reactive({
@@ -157,32 +188,48 @@
   // 获取状态类型
   function getStatusType(status) {
     const statusMap = {
-      pending: 'warning',
+      processing: 'warning',
       settled: 'success',
-      frozen: 'danger',
+      unsettled: 'danger',
     };
     return statusMap[status] || 'info';
   }
 
-  // 获取
+  // 获取数据
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.commission.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      order: table.order,
-      ...searchParams,
-      sort: table.sort,
-    });
-    console.log('API 响应:', error, data);
-    if (error === 0) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
+
+    try {
+      // 处理时间范围参数
+      let params = {
+        page: pageData.page,
+        list_rows: pageData.list_rows,
+        order: table.order,
+        sort: table.sort,
+        ...searchParams,
+      };
+
+      // 处理日期范围
+      if (searchParams.date_range && searchParams.date_range.length === 2) {
+        params.start_time = searchParams.date_range[0];
+        params.end_time = searchParams.date_range[1];
+        delete params.date_range;
+      }
+
+      const result = commissionMockData.getList(params);
+      if (result.error === 0) {
+        table.data = result.data.data;
+        pageData.page = result.data.current_page;
+        pageData.list_rows = result.data.per_page;
+        pageData.total = result.data.total;
+      }
+    } catch (error) {
+      console.error('获取数据失败:', error);
+      ElMessage.error('获取数据失败');
+    } finally {
+      loading.value = false;
     }
-    loading.value = false;
   }
   // table 字段排序
   function fieldFilter({ prop, order }) {
@@ -190,80 +237,73 @@
     table.sort = prop;
     getData();
   }
-  //table批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
-  // 分页/批量操作
-  const batchHandleTools = [
-    {
-      type: 'settle',
-      label: '批量结算',
-      auth: 'shop.admin.finance.commission.settle',
-      class: 'success',
-    },
-  ];
-  function addRow() {
+  // 查看详情
+  function viewDetail(row) {
     useModal(
-      commissionEdit,
-      { title: '新建佣金', type: 'add' },
+      CommissionDetail,
+      {
+        title: '佣金详情',
+        type: 'view',
+        id: row.id,
+        size: '80%',
+      },
       {
         confirm: () => {
-          getData();
+          // 详情页面通常不需要确认回调
         },
       },
     );
   }
-  function editRow(row) {
+
+  // 查看用户详情
+  function viewUserDetail(row) {
     useModal(
-      commissionEdit,
+      UserDetail,
       {
-        title: '查看佣金',
+        title: '用户详情',
         type: 'view',
-        id: row.id,
+        id: row.user_id,
       },
       {
         confirm: () => {
-          getData();
+          // 用户详情页面通常不需要确认回调
         },
       },
     );
   }
 
-  // 结算佣金
-  async function settleCommission(row) {
-    ElMessageBox.confirm('确认结算该笔佣金?', '提示', {
-      confirmButtonText: '确定',
-      cancelButtonText: '取消',
-      type: 'warning',
-    }).then(async () => {
-      const { error } = await api.commission.settle(row.id);
-      if (error === 0) {
-        ElMessage.success('结算成功');
-        getData();
-      }
-    });
+  // 查看订单详情
+  function viewOrderDetail(row) {
+    if (!row.order_no) return;
+    useModal(
+      OrderDetail,
+      {
+        title: '订单详情',
+        type: 'view',
+        id: row.order_no,
+      },
+      {
+        confirm: () => {
+          // 订单详情页面通常不需要确认回调
+        },
+      },
+    );
   }
 
-  async function batchHandle(type) {
-    let ids = [];
-    table.selected.forEach((row) => {
-      ids.push(row.id);
-    });
-    switch (type) {
-      case 'settle':
-        ElMessageBox.confirm('确认结算选中的佣金?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning',
-        }).then(async () => {
-          const { error } = await api.commission.batchSettle(ids.join(','));
-          if (error === 0) {
-            ElMessage.success('批量结算成功');
-            getData();
-          }
-        });
-        break;
+  // 导出记录
+  function exportRecords() {
+    try {
+      const result = commissionMockData.export({});
+      if (result.error === 0) {
+        ElMessage.success('导出成功');
+        // 这里可以触发文件下载
+        console.log('导出文件:', result.data);
+      } else {
+        ElMessage.error(result.msg);
+      }
+    } catch (error) {
+      console.error('导出失败:', error);
+      ElMessage.error('导出失败');
     }
   }
 

+ 331 - 0
src/app/shop/admin/finance/recharge/detail.vue

@@ -0,0 +1,331 @@
+<template>
+  <el-container class="recharge-detail">
+    <el-main>
+      <!-- 基本信息 -->
+      <div class="basic-info sa-m-b-26">
+        <h3 class="sa-m-b-20">基本信息</h3>
+        <el-row :gutter="20">
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">充值单号:</span>
+              <span class="value">{{ rechargeDetail.order_no || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">充值状态:</span>
+              <el-tag :type="getStatusType(rechargeDetail.status)">
+                {{ rechargeDetail.status_text || '--' }}
+              </el-tag>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">充值银行:</span>
+              <span class="value">{{ rechargeDetail.bank_name || 'BKASH' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">用户名:</span>
+              <span class="value">{{ rechargeDetail.username || '--' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="sa-m-t-12">
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">账户名称:</span>
+              <span class="value">{{ rechargeDetail.account_name || 'Aamir Khan' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">手机号:</span>
+              <span class="value">{{ rechargeDetail.phone || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">充值渠道:</span>
+              <el-tag :type="rechargeDetail.channel === 'TKPAY' ? 'info' : 'success'">
+                {{ rechargeDetail.channel_text || '--' }}
+              </el-tag>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">充值账户:</span>
+              <span class="value">{{ rechargeDetail.recharge_account || '8806422510008' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="sa-m-t-12">
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">账户余额:</span>
+              <span class="value">৳{{ rechargeDetail.account_balance || '1,000' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">币种:</span>
+              <span class="value">{{ rechargeDetail.currency || 'BDT' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">收益余额:</span>
+              <span class="value">৳{{ rechargeDetail.profit_balance || '1,000' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">充值余额:</span>
+              <span class="value">৳{{ rechargeDetail.recharge_balance || '300' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+
+      <!-- 操作日志 -->
+      <div class="operation-logs sa-m-b-26 mt-40px">
+        <h3 class="sa-m-b-20">操作日志</h3>
+        <div class="sa-table-wrap">
+          <el-table
+            class="sa-table"
+            :data="operationLogs.data"
+            v-loading="operationLogs.loading"
+            stripe
+            border
+          >
+            <template #empty>
+              <sa-empty />
+            </template>
+            <el-table-column prop="time" label="时间" min-width="160" align="center">
+              <template #default="scope">
+                {{ scope.row.time || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="operator" label="操作人" min-width="120" align="center">
+              <template #default="scope">
+                {{ scope.row.operator || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="action" label="事项" min-width="200">
+              <template #default="scope">
+                {{ scope.row.action || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="remark" label="备注" min-width="300">
+              <template #default="scope">
+                {{ scope.row.remark || '--' }}
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+
+        <!-- 分页 -->
+        <div class="sa-m-t-16" v-if="operationLogs.total > 0">
+          <sa-pagination
+            :pageData="operationLogs.pageData"
+            @updateFn="getOperationLogs"
+            layout="total, prev, pager, next"
+          />
+        </div>
+      </div>
+    </el-main>
+  </el-container>
+</template>
+
+<script setup>
+  import { onMounted, reactive, ref } from 'vue';
+  import { rechargeMockData } from '@/sheep/mock/recharge';
+  import { usePagination } from '@/sheep/hooks';
+
+  const props = defineProps(['modal']);
+  const emit = defineEmits(['modalCallBack']);
+
+  const rechargeDetail = ref({});
+  const loading = ref(false);
+
+  // 操作日志分页
+  const { pageData: logPageData } = usePagination();
+  const operationLogs = reactive({
+    data: [],
+    loading: false,
+    total: 0,
+    pageData: logPageData,
+  });
+
+  // 获取状态类型
+  function getStatusType(status) {
+    const statusMap = {
+      processing: 'warning',
+      success: 'success',
+      failed: 'danger',
+    };
+    return statusMap[status] || 'info';
+  }
+
+  // 获取充值详情
+  async function getRechargeDetail() {
+    if (!props.modal?.params?.id) return;
+
+    loading.value = true;
+    try {
+      const result = rechargeMockData.getDetail(props.modal.params.id);
+      if (result.error === 0) {
+        rechargeDetail.value = result.data;
+      }
+    } catch (error) {
+      console.error('获取充值详情失败:', error);
+    } finally {
+      loading.value = false;
+    }
+  }
+
+  // 获取操作日志
+  function getOperationLogs(page) {
+    if (page) operationLogs.pageData.page = page;
+    operationLogs.loading = true;
+
+    try {
+      // 模拟操作日志数据
+      const mockLogs = [
+        {
+          time: '2025/06/06 12:30:30',
+          operator: 'Aamir Khan',
+          action: '提交充值订单',
+          remark: '--',
+        },
+        {
+          time: '2025/06/06 12:36:30',
+          operator: 'Aamir Khan',
+          action: '提交充值凭证',
+          remark: '--',
+        },
+        {
+          time: '2025/06/06 12:36:30',
+          operator: 'system',
+          action: '充值订单审核通过',
+          remark: '--',
+        },
+      ];
+
+      operationLogs.data = mockLogs;
+      operationLogs.total = mockLogs.length;
+    } catch (error) {
+      console.error('获取操作日志失败:', error);
+    } finally {
+      operationLogs.loading = false;
+    }
+  }
+
+  onMounted(() => {
+    getRechargeDetail();
+    getOperationLogs();
+  });
+</script>
+
+<style lang="scss" scoped>
+  .recharge-detail {
+    .basic-info {
+      h3 {
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--sa-title);
+      }
+
+      .info-item {
+        display: flex;
+        align-items: center;
+        margin-bottom: 8px;
+
+        .label {
+          flex-shrink: 0;
+          color: var(--sa-subfont);
+          font-size: 14px;
+          min-width: 80px;
+        }
+
+        .value {
+          color: var(--sa-subtitle);
+          font-size: 14px;
+          font-weight: 500;
+        }
+      }
+    }
+
+    .operation-logs {
+      h3 {
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--sa-title);
+      }
+
+      .sa-table {
+        border-radius: 8px;
+        overflow: hidden;
+        border: 1px solid #dcdfe6;
+
+        :deep(.el-table) {
+          border: none;
+          border-radius: 0;
+        }
+
+        :deep(.el-table__header-wrapper) {
+          background: var(--sa-table-header-bg, #f5f7fa);
+
+          .el-table__header {
+            background: var(--sa-table-header-bg, #f5f7fa);
+
+            th {
+              background: var(--sa-table-header-bg, #f5f7fa) !important;
+              font-weight: 600;
+              color: var(--sa-title, #303133);
+              border-bottom: 1px solid #dcdfe6;
+              border-right: 1px solid #dcdfe6;
+
+              &:first-child {
+                border-left: none;
+              }
+
+              &:last-child {
+                border-right: none;
+              }
+            }
+          }
+        }
+
+        :deep(.el-table__body-wrapper) {
+          .el-table__body {
+            tr {
+              &:hover {
+                background: #f5f7fa !important;
+              }
+
+              td {
+                border-bottom: 1px solid #dcdfe6;
+                border-right: 1px solid #dcdfe6;
+                border-left: none;
+
+                &:first-child {
+                  border-left: none;
+                }
+
+                &:last-child {
+                  border-right: none;
+                }
+              }
+
+              &:last-child td {
+                border-bottom: none;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 136 - 122
src/app/shop/admin/finance/recharge/index.vue

@@ -15,7 +15,7 @@
         <div class="label sa-flex">充值管理</div>
         <div>
           <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
-          <el-button icon="Plus" type="primary" @click="addRow">新建</el-button>
+          <el-button icon="Download" type="primary" @click="exportRecords">导出记录</el-button>
         </div>
       </div>
     </el-header>
@@ -25,83 +25,72 @@
           height="100%"
           class="sa-table"
           :data="table.data"
-          @selection-change="changeSelection"
           @sort-change="fieldFilter"
-          @row-dblclick="editRow"
+          @row-dblclick="viewDetail"
           row-key="id"
           stripe
         >
           <template #empty>
             <sa-empty />
           </template>
-          <el-table-column type="selection" width="48" align="center"></el-table-column>
-          <el-table-column prop="id" label="ID" min-width="100" sortable="custom">
+          <el-table-column prop="order_no" label="充值订单号" min-width="180" sortable="custom">
+            <template #default="scope">
+              <span class="sa-table-line-1">{{ scope.row.order_no || '-' }}</span>
+            </template>
           </el-table-column>
-          <el-table-column label="用户" min-width="120">
+          <el-table-column label="用户" min-width="120">
             <template #default="scope">
-              <span class="sa-table-line-1">
+              <el-link type="primary" :underline="true" @click="viewUserDetail(scope.row)">
                 {{ scope.row.username || '-' }}
-              </span>
+              </el-link>
             </template>
           </el-table-column>
-          <el-table-column label="充值金额" min-width="120">
-            <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
+          <el-table-column prop="phone" label="手机号" min-width="130" align="center">
+            <template #default="scope">
+              {{ scope.row.phone || '-' }}
+            </template>
           </el-table-column>
-          <el-table-column label="支付方式" min-width="120">
+          <el-table-column label="充值渠道" min-width="100" align="center">
             <template #default="scope">
-              <el-tag :type="scope.row.payment_method === 'alipay' ? 'primary' : 'success'">
-                {{ scope.row.payment_method_text || '-' }}
+              <el-tag :type="scope.row.channel === 'TKPAY' ? 'info' : 'success'">
+                {{ scope.row.channel_text || '-' }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="订单号" min-width="180">
+          <el-table-column label="币种" min-width="80" align="center">
             <template #default="scope">
-              <span class="sa-table-line-1">
-                {{ scope.row.order_no || '-' }}
-              </span>
+              {{ scope.row.currency || 'BDT' }}
             </template>
           </el-table-column>
-          <el-table-column label="状态" min-width="100">
+          <el-table-column label="金额" min-width="120" align="center">
+            <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
+          </el-table-column>
+          <el-table-column label="状态" min-width="100" align="center">
             <template #default="scope">
               <el-tag :type="getStatusType(scope.row.status)">
                 {{ scope.row.status_text || '-' }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="充值时间" min-width="160">
+          <el-table-column prop="create_time" label="下单时间" min-width="160" sortable="custom">
             <template #default="scope">
               {{ scope.row.create_time || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="成时间" min-width="160">
+          <el-table-column label="成时间" min-width="160">
             <template #default="scope">
-              {{ scope.row.finish_time || '-' }}
+              {{ scope.row.success_time || '--' }}
             </template>
           </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="120">
+          <el-table-column fixed="right" label="操作" min-width="80">
             <template #default="scope">
-              <el-button class="is-link" type="primary" @click="editRow(scope.row)">查看</el-button>
-              <el-button
-                v-if="scope.row.status === 'pending'"
-                class="is-link"
-                type="success"
-                @click="confirmRecharge(scope.row)"
-              >
-                确认
-              </el-button>
+              <el-button link type="primary" @click="viewDetail(scope.row)">详情</el-button>
             </template>
           </el-table-column>
         </el-table>
       </div>
     </el-main>
     <sa-view-bar>
-      <template #left>
-        <sa-batch-handle
-          :batchHandleTools="batchHandleTools"
-          :selectedLeng="table.selected.length"
-          @batchHandle="batchHandle"
-        ></sa-batch-handle>
-      </template>
       <template #right>
         <sa-pagination :pageData="pageData" @updateFn="getData" />
       </template>
@@ -110,11 +99,11 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../finance.service';
-  import { ElMessageBox, ElMessage } from 'element-plus';
-  import { useModal } from '@/sheep/hooks';
-  import { usePagination } from '@/sheep/hooks';
-  import rechargeEdit from './edit.vue';
+  import { ElMessage } from 'element-plus';
+  import { useModal, usePagination } from '@/sheep/hooks';
+  import { rechargeMockData } from '@/sheep/mock/recharge';
+  import UserDetail from '../../user/list/detail.vue';
+  import RechargeDetail from './detail.vue';
   const { pageData } = usePagination();
 
   // 搜索字段配置
@@ -123,24 +112,58 @@
       type: 'input',
       label: '用户名',
       placeholder: '请输入用户名',
-      width: 200,
+      width: 150,
+    },
+    phone: {
+      type: 'input',
+      label: '手机号',
+      placeholder: '请输入手机号',
+      width: 150,
+    },
+    order_no: {
+      type: 'input',
+      label: '充值订单号',
+      placeholder: '请输入订单号',
+      width: 180,
     },
     status: {
       type: 'select',
-      label: '状态',
+      label: '充值状态',
       placeholder: '请选择状态',
-      width: 150,
+      width: 120,
+      options: [
+        { label: '全部', value: 'all' },
+        { label: '处理中', value: 'processing' },
+        { label: '充值成功', value: 'success' },
+        { label: '充值失败', value: 'failed' },
+      ],
+    },
+    channel: {
+      type: 'select',
+      label: '充值通道',
+      placeholder: '请选择通道',
+      width: 120,
       options: [
-        { label: '待处理', value: 'pending' },
-        { label: '已完成', value: 'completed' },
-        { label: '已失败', value: 'failed' },
+        { label: 'TKPAY', value: 'TKPAY' },
+        { label: '3QPAY', value: '3QPAY' },
       ],
     },
+    date_range: {
+      type: 'daterange',
+      label: '下单时间',
+      placeholder: '请选择时间范围',
+      width: 240,
+    },
   });
+
   // 默认搜索值
   const defaultSearchValues = reactive({
     username: '',
-    status: '',
+    phone: '',
+    order_no: '',
+    status: 'all',
+    channel: '',
+    date_range: [],
   });
   // 列表
   const table = reactive({
@@ -154,32 +177,48 @@
   // 获取状态类型
   function getStatusType(status) {
     const statusMap = {
-      pending: 'warning',
-      completed: 'success',
+      processing: 'warning',
+      success: 'success',
       failed: 'danger',
     };
     return statusMap[status] || 'info';
   }
 
-  // 获取
+  // 获取数据
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.recharge.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      order: table.order,
-      ...searchParams,
-      sort: table.sort,
-    });
-    console.log('API 响应:', error, data);
-    if (error === 0) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
+
+    try {
+      // 处理时间范围参数
+      let params = {
+        page: pageData.page,
+        list_rows: pageData.list_rows,
+        order: table.order,
+        sort: table.sort,
+        ...searchParams,
+      };
+
+      // 处理日期范围
+      if (searchParams.date_range && searchParams.date_range.length === 2) {
+        params.start_time = searchParams.date_range[0];
+        params.end_time = searchParams.date_range[1];
+        delete params.date_range;
+      }
+
+      const result = rechargeMockData.getList(params);
+      if (result.error === 0) {
+        table.data = result.data.data;
+        pageData.page = result.data.current_page;
+        pageData.list_rows = result.data.per_page;
+        pageData.total = result.data.total;
+      }
+    } catch (error) {
+      console.error('获取数据失败:', error);
+      ElMessage.error('获取数据失败');
+    } finally {
+      loading.value = false;
     }
-    loading.value = false;
   }
   // table 字段排序
   function fieldFilter({ prop, order }) {
@@ -187,80 +226,55 @@
     table.sort = prop;
     getData();
   }
-  //table批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
-  // 分页/批量操作
-  const batchHandleTools = [
-    {
-      type: 'confirm',
-      label: '批量确认',
-      auth: 'shop.admin.finance.recharge.confirm',
-      class: 'success',
-    },
-  ];
-  function addRow() {
+  // 查看详情
+  function viewDetail(row) {
     useModal(
-      rechargeEdit,
-      { title: '新建充值', type: 'add' },
+      RechargeDetail,
+      {
+        title: '充值详情',
+        type: 'view',
+        id: row.id,
+        size: '80%',
+      },
       {
         confirm: () => {
-          getData();
+          // 详情页面通常不需要确认回调
         },
       },
     );
   }
-  function editRow(row) {
+
+  // 查看用户详情
+  function viewUserDetail(row) {
     useModal(
-      rechargeEdit,
+      UserDetail,
       {
-        title: '查看充值',
+        title: '用户详情',
         type: 'view',
-        id: row.id,
+        id: row.user_id,
       },
       {
         confirm: () => {
-          getData();
+          // 用户详情页面通常不需要确认回调
         },
       },
     );
   }
 
-  // 确认充值
-  async function confirmRecharge(row) {
-    ElMessageBox.confirm('确认该笔充值记录?', '提示', {
-      confirmButtonText: '确定',
-      cancelButtonText: '取消',
-      type: 'warning',
-    }).then(async () => {
-      const { error } = await api.recharge.confirm(row.id);
-      if (error === 0) {
-        ElMessage.success('确认成功');
-        getData();
+  // 导出记录
+  function exportRecords() {
+    try {
+      const result = rechargeMockData.export({});
+      if (result.error === 0) {
+        ElMessage.success('导出成功');
+        // 这里可以触发文件下载
+        console.log('导出文件:', result.data);
+      } else {
+        ElMessage.error(result.msg);
       }
-    });
-  }
-
-  async function batchHandle(type) {
-    let ids = [];
-    table.selected.forEach((row) => {
-      ids.push(row.id);
-    });
-    switch (type) {
-      case 'confirm':
-        ElMessageBox.confirm('确认选中的充值记录?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning',
-        }).then(async () => {
-          const { error } = await api.recharge.batchConfirm(ids.join(','));
-          if (error === 0) {
-            ElMessage.success('批量确认成功');
-            getData();
-          }
-        });
-        break;
+    } catch (error) {
+      console.error('导出失败:', error);
+      ElMessage.error('导出失败');
     }
   }
 

+ 110 - 0
src/app/shop/admin/finance/withdraw/audit.vue

@@ -0,0 +1,110 @@
+<template>
+  <el-container>
+    <el-main>
+      <el-form ref="formRef" :model="formData" label-width="100px" :rules="rules">
+        <!-- 审核通过时显示通道选择 -->
+        <el-form-item v-if="auditType === 'approve'" label="选择通道:" prop="channel">
+          <el-select v-model="formData.channel" placeholder="请选择提款通道" style="width: 100%">
+            <el-option label="TKPAY" value="TKPAY" />
+            <el-option label="3QPAY" value="3QPAY" />
+          </el-select>
+        </el-form-item>
+
+        <!-- 审核备注 -->
+        <el-form-item :label="auditType === 'approve' ? '审核备注:' : '审核备注:'" prop="remark">
+          <el-input
+            v-model="formData.remark"
+            type="textarea"
+            :rows="auditType === 'approve' ? 3 : 4"
+            :placeholder="
+              auditType === 'approve' ? '请输入审核备注(可选)' : '请输入拒绝原因(限100字)'
+            "
+            :maxlength="auditType === 'approve' ? 200 : 100"
+            show-word-limit
+          />
+        </el-form-item>
+      </el-form>
+    </el-main>
+    <el-footer class="sa-footer--submit">
+      <el-button @click="handleCancel">取消</el-button>
+      <el-button type="primary" @click="handleConfirm" :loading="loading">提交</el-button>
+    </el-footer>
+  </el-container>
+</template>
+
+<script setup>
+  import { reactive, ref, computed } from 'vue';
+  import { ElMessage } from 'element-plus';
+
+  const props = defineProps(['modal']);
+  const emit = defineEmits(['modalCallBack']);
+
+  const formRef = ref();
+  const loading = ref(false);
+
+  // 审核类型:approve(通过) 或 reject(拒绝)
+  const auditType = computed(() => props.modal?.params?.type || 'approve');
+
+  // 表单数据
+  const formData = reactive({
+    channel: '',
+    remark: '',
+  });
+
+  // 表单验证规则
+  const rules = computed(() => {
+    const baseRules = {};
+
+    if (auditType.value === 'approve') {
+      baseRules.channel = [{ required: true, message: '请选择提款通道', trigger: 'change' }];
+    } else {
+      baseRules.remark = [
+        { required: true, message: '请输入拒绝原因', trigger: 'blur' },
+        { min: 1, max: 100, message: '拒绝原因长度在1到100个字符', trigger: 'blur' },
+      ];
+    }
+
+    return baseRules;
+  });
+
+  // 取消操作
+  function handleCancel() {
+    emit('modalCallBack', { action: 'cancel' });
+  }
+
+  // 确认操作
+  async function handleConfirm() {
+    if (!formRef.value) return;
+
+    try {
+      const valid = await formRef.value.validate();
+      if (!valid) return;
+
+      loading.value = true;
+
+      // 模拟API调用
+      await new Promise((resolve) => setTimeout(resolve, 1000));
+
+      const successMessage = auditType.value === 'approve' ? '审核通过成功' : '审核拒绝成功';
+      ElMessage.success(successMessage);
+
+      // 返回审核结果
+      emit('modalCallBack', {
+        action: 'confirm',
+        data: {
+          type: auditType.value,
+          channel: formData.channel,
+          remark: formData.remark,
+        },
+      });
+    } catch (error) {
+      console.error('审核操作失败:', error);
+      const errorMessage = auditType.value === 'approve' ? '审核通过失败' : '审核拒绝失败';
+      ElMessage.error(errorMessage);
+    } finally {
+      loading.value = false;
+    }
+  }
+</script>
+
+<style lang="scss" scoped></style>

+ 457 - 0
src/app/shop/admin/finance/withdraw/detail.vue

@@ -0,0 +1,457 @@
+<template>
+  <el-container class="withdraw-detail">
+    <el-main>
+      <!-- 操作按钮 -->
+      <div class="operation-buttons sa-m-b-26" v-if="withdrawDetail.status === 'processing'">
+        <el-button type="success" @click="handleApprove">审核通过</el-button>
+        <el-button type="danger" @click="handleReject">审核拒绝</el-button>
+      </div>
+
+      <!-- 基本信息 -->
+      <div class="basic-info sa-m-b-26">
+        <h3 class="sa-m-b-20">基本信息</h3>
+        <el-row :gutter="20">
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">提款单号:</span>
+              <span class="value">{{ withdrawDetail.order_no || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">提款状态:</span>
+              <el-tag :type="getStatusType(withdrawDetail.status)">
+                {{ withdrawDetail.status_text || '--' }}
+              </el-tag>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">收款银行:</span>
+              <span class="value">{{ withdrawDetail.bank_name || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">用户名:</span>
+              <span class="value">{{ withdrawDetail.username || '--' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="sa-m-t-12">
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">提款类型:</span>
+              <el-tag :type="withdrawDetail.withdraw_type === 'bank' ? 'info' : 'success'">
+                {{ withdrawDetail.withdraw_type_text || '--' }}
+              </el-tag>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">账户名称:</span>
+              <span class="value">{{ withdrawDetail.account_name || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">手机号:</span>
+              <span class="value">{{ withdrawDetail.phone || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">提款渠道:</span>
+              <el-tag type="info">
+                {{ withdrawDetail.withdraw_channel_text || '--' }}
+              </el-tag>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="sa-m-t-12">
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">收款账户:</span>
+              <span class="value">{{ withdrawDetail.account_number || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">账户余额:</span>
+              <span class="value">৳{{ withdrawDetail.account_balance || '0' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">币种:</span>
+              <span class="value">{{ withdrawDetail.currency || 'BDT' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">收益余额:</span>
+              <span class="value">৳{{ withdrawDetail.profit_balance || '0' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="sa-m-t-12">
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">提款金额:</span>
+              <span class="value">৳{{ withdrawDetail.amount || '0' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">提款手续费:</span>
+              <span class="value">৳{{ withdrawDetail.fee || '0' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">申请时间:</span>
+              <span class="value">{{ withdrawDetail.create_time || '--' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">成功时间:</span>
+              <span class="value">{{ withdrawDetail.success_time || '--' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+
+      <!-- 操作日志 -->
+      <div class="operation-logs sa-m-b-26 mt-40px">
+        <h3 class="sa-m-b-20">操作日志</h3>
+        <div class="sa-table-wrap">
+          <el-table
+            class="sa-table"
+            :data="operationLogs.data"
+            v-loading="operationLogs.loading"
+            stripe
+            border
+          >
+            <template #empty>
+              <sa-empty />
+            </template>
+            <el-table-column prop="time" label="时间" min-width="160" align="center">
+              <template #default="scope">
+                {{ scope.row.time || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="operator" label="操作人" min-width="120" align="center">
+              <template #default="scope">
+                {{ scope.row.operator || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="action" label="事项" min-width="200">
+              <template #default="scope">
+                {{ scope.row.action || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="remark" label="备注" min-width="300">
+              <template #default="scope">
+                {{ scope.row.remark || '--' }}
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+
+        <!-- 分页 -->
+        <div class="sa-m-t-16" v-if="operationLogs.total > 0">
+          <sa-pagination
+            :pageData="operationLogs.pageData"
+            @updateFn="getOperationLogs"
+            layout="total, prev, pager, next"
+          />
+        </div>
+      </div>
+    </el-main>
+  </el-container>
+</template>
+
+<script setup>
+  import { onMounted, reactive, ref } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import { withdrawMockData } from '@/sheep/mock/withdraw';
+  import { useModal, usePagination } from '@/sheep/hooks';
+  import WithdrawAudit from './audit.vue';
+
+  const props = defineProps(['modal']);
+  const emit = defineEmits(['modalCallBack']);
+
+  const withdrawDetail = ref({});
+  const loading = ref(false);
+
+  // 操作日志分页
+  const { pageData: logPageData } = usePagination();
+  const operationLogs = reactive({
+    data: [],
+    loading: false,
+    total: 0,
+    pageData: logPageData,
+  });
+
+  // 获取状态类型
+  function getStatusType(status) {
+    const statusMap = {
+      processing: 'warning',
+      success: 'success',
+      failed: 'danger',
+    };
+    return statusMap[status] || 'info';
+  }
+
+  // 获取提款详情
+  async function getWithdrawDetail() {
+    if (!props.modal?.params?.id) return;
+
+    loading.value = true;
+    try {
+      const result = withdrawMockData.getDetail(props.modal.params.id);
+      if (result.error === 0) {
+        withdrawDetail.value = result.data;
+      }
+    } catch (error) {
+      console.error('获取提款详情失败:', error);
+    } finally {
+      loading.value = false;
+    }
+  }
+
+  // 获取操作日志
+  function getOperationLogs(page) {
+    if (page) operationLogs.pageData.page = page;
+    operationLogs.loading = true;
+
+    try {
+      // 模拟操作日志数据
+      const mockLogs = [
+        {
+          time: '2025/01/15 10:30:00',
+          operator: 'John Doe',
+          action: '提交提款申请',
+          remark: '--',
+        },
+        {
+          time: '2025/01/15 10:35:00',
+          operator: 'John Doe',
+          action: '上传银行凭证',
+          remark: '--',
+        },
+        {
+          time: '2025/01/15 11:00:00',
+          operator: 'system',
+          action: '提款申请审核中',
+          remark: '--',
+        },
+        {
+          time: '2025/01/15 11:30:00',
+          operator: 'admin',
+          action: '提款申请审核通过',
+          remark: '审核通过,开始处理',
+        },
+      ];
+
+      operationLogs.data = mockLogs;
+      operationLogs.total = mockLogs.length;
+    } catch (error) {
+      console.error('获取操作日志失败:', error);
+    } finally {
+      operationLogs.loading = false;
+    }
+  }
+
+  // 审核通过
+  function handleApprove() {
+    useModal(
+      WithdrawAudit,
+      {
+        width: '500px',
+        title: '提款审核通过',
+        type: 'approve',
+      },
+      {
+        confirm: (result) => {
+          if (result.action === 'confirm') {
+            handleAuditResult(result.data);
+          }
+        },
+      },
+    );
+  }
+
+  // 审核拒绝
+  function handleReject() {
+    useModal(
+      WithdrawAudit,
+      {
+        width: '500px',
+        title: '提款审核拒绝',
+        type: 'reject',
+      },
+      {
+        confirm: (result) => {
+          if (result.action === 'confirm') {
+            handleAuditResult(result.data);
+          }
+        },
+      },
+    );
+  }
+
+  // 处理审核结果
+  function handleAuditResult(data) {
+    if (data.type === 'approve') {
+      // 更新状态为成功
+      withdrawDetail.value.status = 'success';
+      withdrawDetail.value.status_text = '提款成功';
+      withdrawDetail.value.success_time = new Date().toLocaleString();
+
+      // 添加操作日志
+      operationLogs.data.unshift({
+        time: new Date().toLocaleString(),
+        operator: 'admin',
+        action: '审核通过',
+        remark: `通过渠道:${data.channel}${data.remark ? ',备注:' + data.remark : ''}`,
+      });
+    } else if (data.type === 'reject') {
+      // 更新状态为失败
+      withdrawDetail.value.status = 'failed';
+      withdrawDetail.value.status_text = '提款失败';
+
+      // 添加操作日志
+      operationLogs.data.unshift({
+        time: new Date().toLocaleString(),
+        operator: 'admin',
+        action: '审核拒绝',
+        remark: data.remark,
+      });
+    }
+
+    // 通知父组件刷新
+    emit('modalCallBack');
+  }
+
+  onMounted(() => {
+    getWithdrawDetail();
+    getOperationLogs();
+  });
+</script>
+
+<style lang="scss" scoped>
+  .withdraw-detail {
+    .operation-buttons {
+      text-align: center;
+      padding: 20px 0;
+      border-bottom: 1px solid #ebeef5;
+
+      .el-button {
+        margin: 0 10px;
+        padding: 12px 30px;
+        font-size: 14px;
+        font-weight: 500;
+      }
+    }
+
+    .basic-info {
+      h3 {
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--sa-title);
+      }
+
+      .info-item {
+        display: flex;
+        align-items: center;
+        margin-bottom: 8px;
+
+        .label {
+          flex-shrink: 0;
+          color: var(--sa-subfont);
+          font-size: 14px;
+          min-width: 80px;
+        }
+
+        .value {
+          color: var(--sa-subtitle);
+          font-size: 14px;
+          font-weight: 500;
+        }
+      }
+    }
+
+    .operation-logs {
+      h3 {
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--sa-title);
+      }
+
+      .sa-table {
+        border-radius: 8px;
+        overflow: hidden;
+        border: 1px solid #dcdfe6;
+
+        :deep(.el-table) {
+          border: none;
+          border-radius: 0;
+        }
+
+        :deep(.el-table__header-wrapper) {
+          background: var(--sa-table-header-bg, #f5f7fa);
+
+          .el-table__header {
+            background: var(--sa-table-header-bg, #f5f7fa);
+
+            th {
+              background: var(--sa-table-header-bg, #f5f7fa) !important;
+              font-weight: 600;
+              color: var(--sa-title, #303133);
+              border-bottom: 1px solid #dcdfe6;
+              border-right: 1px solid #dcdfe6;
+
+              &:first-child {
+                border-left: none;
+              }
+
+              &:last-child {
+                border-right: none;
+              }
+            }
+          }
+        }
+
+        :deep(.el-table__body-wrapper) {
+          .el-table__body {
+            tr {
+              &:hover {
+                background: #f5f7fa !important;
+              }
+
+              td {
+                border-bottom: 1px solid #dcdfe6;
+                border-right: 1px solid #dcdfe6;
+                border-left: none;
+
+                &:first-child {
+                  border-left: none;
+                }
+
+                &:last-child {
+                  border-right: none;
+                }
+              }
+
+              &:last-child td {
+                border-bottom: none;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 159 - 162
src/app/shop/admin/finance/withdraw/index.vue

@@ -4,17 +4,18 @@
       <!-- 简化搜索组件 -->
       <div class="search-container">
         <sa-search-simple
+          key="withdraw-search"
           :searchFields="searchFields"
           :defaultValues="defaultSearchValues"
           @search="(val) => getData(1, val)"
           @reset="getData(1)"
-        >
-        </sa-search-simple>
+        />
       </div>
       <div class="sa-title sa-flex sa-row-between">
         <div class="label sa-flex">提款管理</div>
         <div>
           <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
+          <el-button icon="Download" type="primary" @click="exportRecords">导出记录</el-button>
         </div>
       </div>
     </el-header>
@@ -24,85 +25,94 @@
           height="100%"
           class="sa-table"
           :data="table.data"
-          @selection-change="changeSelection"
           @sort-change="fieldFilter"
-          @row-dblclick="editRow"
+          @row-dblclick="viewDetail"
           row-key="id"
           stripe
         >
           <template #empty>
             <sa-empty />
           </template>
-          <el-table-column type="selection" width="48" align="center"></el-table-column>
-          <el-table-column prop="id" label="ID" min-width="100" sortable="custom">
+          <el-table-column prop="order_no" label="提款订单号" min-width="180" sortable="custom">
+            <template #default="scope">
+              <span class="sa-table-line-1">{{ scope.row.order_no || '-' }}</span>
+            </template>
           </el-table-column>
-          <el-table-column label="用户" min-width="120">
+          <el-table-column label="用户" min-width="120">
             <template #default="scope">
-              <span class="sa-table-line-1">
+              <el-link type="primary" :underline="true" @click="viewUserDetail(scope.row)">
                 {{ scope.row.username || '-' }}
-              </span>
+              </el-link>
             </template>
           </el-table-column>
-          <el-table-column label="提款金额" min-width="120">
-            <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
-          </el-table-column>
-          <el-table-column label="手续费" min-width="100">
-            <template #default="scope"> ৳{{ scope.row.fee || 0 }} </template>
+          <el-table-column prop="phone" label="手机号" min-width="130" align="center">
+            <template #default="scope">
+              {{ scope.row.phone || '-' }}
+            </template>
           </el-table-column>
-          <el-table-column label="实际到账" min-width="120">
-            <template #default="scope"> ৳{{ scope.row.actual_amount || 0 }} </template>
+          <el-table-column label="提款类型" min-width="100" align="center">
+            <template #default="scope">
+              <el-tag :type="scope.row.withdraw_type === 'bank' ? 'info' : 'success'">
+                {{ scope.row.withdraw_type_text || '-' }}
+              </el-tag>
+            </template>
           </el-table-column>
-          <el-table-column label="提款方式" min-width="120">
+          <el-table-column label="提款渠道" min-width="100" align="center">
             <template #default="scope">
-              <el-tag :type="scope.row.withdraw_method === 'bank' ? 'primary' : 'success'">
-                {{ scope.row.withdraw_method_text || '-' }}
+              <el-tag type="info">
+                {{ scope.row.withdraw_channel_text || '-' }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="状态" min-width="100">
+          <el-table-column label="币种" min-width="80" align="center">
+            <template #default="scope">
+              {{ scope.row.currency || 'BDT' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="提款金额" min-width="120" align="center">
+            <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
+          </el-table-column>
+          <el-table-column label="状态" min-width="100" align="center">
             <template #default="scope">
               <el-tag :type="getStatusType(scope.row.status)">
                 {{ scope.row.status_text || '-' }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="申请时间" min-width="160">
+          <el-table-column label="收款银行" min-width="150" align="center">
+            <template #default="scope">
+              {{ scope.row.bank_name || '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="收款账户名称" min-width="130" align="center">
+            <template #default="scope">
+              {{ scope.row.account_name || '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="收款账户" min-width="160" align="center">
+            <template #default="scope">
+              {{ scope.row.account_number || '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="create_time" label="下单时间" min-width="160" sortable="custom">
             <template #default="scope">
               {{ scope.row.create_time || '-' }}
             </template>
           </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="150">
+          <el-table-column label="成功时间" min-width="160">
             <template #default="scope">
-              <el-button class="is-link" type="primary" @click="editRow(scope.row)">查看</el-button>
-              <el-button
-                v-if="scope.row.status === 'pending'"
-                class="is-link"
-                type="success"
-                @click="approveWithdraw(scope.row)"
-              >
-                通过
-              </el-button>
-              <el-button
-                v-if="scope.row.status === 'pending'"
-                class="is-link"
-                type="danger"
-                @click="rejectWithdraw(scope.row)"
-              >
-                拒绝
-              </el-button>
+              {{ scope.row.success_time || '--' }}
+            </template>
+          </el-table-column>
+          <el-table-column fixed="right" label="操作" min-width="80">
+            <template #default="scope">
+              <el-button link type="primary" @click="viewDetail(scope.row)">详情</el-button>
             </template>
           </el-table-column>
         </el-table>
       </div>
     </el-main>
     <sa-view-bar>
-      <template #left>
-        <sa-batch-handle
-          :batchHandleTools="batchHandleTools"
-          :selectedLeng="table.selected.length"
-          @batchHandle="batchHandle"
-        ></sa-batch-handle>
-      </template>
       <template #right>
         <sa-pagination :pageData="pageData" @updateFn="getData" />
       </template>
@@ -111,11 +121,11 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../finance.service';
-  import { ElMessageBox, ElMessage } from 'element-plus';
-  import { useModal } from '@/sheep/hooks';
-  import { usePagination } from '@/sheep/hooks';
-  import withdrawEdit from './edit.vue';
+  import { ElMessage } from 'element-plus';
+  import { useModal, usePagination } from '@/sheep/hooks';
+  import { withdrawMockData } from '@/sheep/mock/withdraw';
+  import UserDetail from '../../user/list/detail.vue';
+  import WithdrawDetail from './detail.vue';
   const { pageData } = usePagination();
 
   // 搜索字段配置
@@ -124,25 +134,47 @@
       type: 'input',
       label: '用户名',
       placeholder: '请输入用户名',
-      width: 200,
+      width: 150,
+    },
+    phone: {
+      type: 'input',
+      label: '手机号',
+      placeholder: '请输入手机号',
+      width: 150,
+    },
+    order_no: {
+      type: 'input',
+      label: '提款订单号',
+      placeholder: '请输入订单号',
+      width: 180,
     },
     status: {
       type: 'select',
-      label: '状态',
+      label: '提款状态',
       placeholder: '请选择状态',
-      width: 150,
+      width: 120,
       options: [
-        { label: '待审核', value: 'pending' },
-        { label: '已通过', value: 'approved' },
-        { label: '已拒绝', value: 'rejected' },
-        { label: '已完成', value: 'completed' },
+        { label: '全部状态', value: 'all' },
+        { label: '处理中', value: 'processing' },
+        { label: '提款成功', value: 'success' },
+        { label: '提款失败', value: 'failed' },
       ],
     },
+    create_time: {
+      type: 'daterange',
+      label: '下单时间',
+      placeholder: '请选择时间范围',
+      width: 240,
+    },
   });
+
   // 默认搜索值
   const defaultSearchValues = reactive({
     username: '',
-    status: '',
+    phone: '',
+    order_no: '',
+    status: 'all',
+    create_time: [],
   });
   // 列表
   const table = reactive({
@@ -156,33 +188,48 @@
   // 获取状态类型
   function getStatusType(status) {
     const statusMap = {
-      pending: 'warning',
-      approved: 'primary',
-      rejected: 'danger',
-      completed: 'success',
+      processing: 'warning',
+      success: 'success',
+      failed: 'danger',
     };
     return statusMap[status] || 'info';
   }
 
-  // 获取
+  // 获取数据
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.withdraw.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      order: table.order,
-      ...searchParams,
-      sort: table.sort,
-    });
-    console.log('API 响应:', error, data);
-    if (error === 0) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
+
+    try {
+      // 处理时间范围参数
+      let params = {
+        page: pageData.page,
+        list_rows: pageData.list_rows,
+        order: table.order,
+        sort: table.sort,
+        ...searchParams,
+      };
+
+      // 处理日期范围
+      if (searchParams.create_time && searchParams.create_time.length === 2) {
+        params.start_time = searchParams.create_time[0];
+        params.end_time = searchParams.create_time[1];
+        delete params.create_time;
+      }
+
+      const result = withdrawMockData.getList(params);
+      if (result.error === 0) {
+        table.data = result.data.data;
+        pageData.page = result.data.current_page;
+        pageData.list_rows = result.data.per_page;
+        pageData.total = result.data.total;
+      }
+    } catch (error) {
+      console.error('获取数据失败:', error);
+      ElMessage.error('获取数据失败');
+    } finally {
+      loading.value = false;
     }
-    loading.value = false;
   }
   // table 字段排序
   function fieldFilter({ prop, order }) {
@@ -190,106 +237,56 @@
     table.sort = prop;
     getData();
   }
-  //table批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
-  // 分页/批量操作
-  const batchHandleTools = [
-    {
-      type: 'approve',
-      label: '批量通过',
-      auth: 'shop.admin.finance.withdraw.approve',
-      class: 'success',
-    },
-    {
-      type: 'reject',
-      label: '批量拒绝',
-      auth: 'shop.admin.finance.withdraw.reject',
-      class: 'danger',
-    },
-  ];
-
-  function editRow(row) {
+  // 查看详情
+  function viewDetail(row) {
     useModal(
-      withdrawEdit,
+      WithdrawDetail,
       {
-        title: '查看提款',
+        title: '提款详情',
         type: 'view',
         id: row.id,
+        size: '80%',
       },
       {
         confirm: () => {
+          // 审核操作后刷新列表
           getData();
         },
       },
     );
   }
 
-  // 通过提款
-  async function approveWithdraw(row) {
-    ElMessageBox.confirm('确认通过该笔提款申请?', '提示', {
-      confirmButtonText: '确定',
-      cancelButtonText: '取消',
-      type: 'warning',
-    }).then(async () => {
-      const { error } = await api.withdraw.approve(row.id);
-      if (error === 0) {
-        ElMessage.success('通过成功');
-        getData();
-      }
-    });
+  // 查看用户详情
+  function viewUserDetail(row) {
+    useModal(
+      UserDetail,
+      {
+        title: '用户详情',
+        type: 'view',
+        id: row.user_id,
+      },
+      {
+        confirm: () => {
+          // 用户详情页面通常不需要确认回调
+        },
+      },
+    );
   }
 
-  // 拒绝提款
-  async function rejectWithdraw(row) {
-    ElMessageBox.prompt('请输入拒绝原因', '拒绝提款', {
-      confirmButtonText: '确定',
-      cancelButtonText: '取消',
-      inputPattern: /.+/,
-      inputErrorMessage: '拒绝原因不能为空',
-    }).then(async ({ value }) => {
-      const { error } = await api.withdraw.reject(row.id, { reason: value });
-      if (error === 0) {
-        ElMessage.success('拒绝成功');
-        getData();
+  // 导出记录
+  function exportRecords() {
+    try {
+      const result = withdrawMockData.export({});
+      if (result.error === 0) {
+        ElMessage.success('导出成功');
+        // 这里可以触发文件下载
+        console.log('导出文件:', result.data);
+      } else {
+        ElMessage.error(result.msg);
       }
-    });
-  }
-
-  async function batchHandle(type) {
-    let ids = [];
-    table.selected.forEach((row) => {
-      ids.push(row.id);
-    });
-    switch (type) {
-      case 'approve':
-        ElMessageBox.confirm('确认通过选中的提款申请?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning',
-        }).then(async () => {
-          const { error } = await api.withdraw.batchApprove(ids.join(','));
-          if (error === 0) {
-            ElMessage.success('批量通过成功');
-            getData();
-          }
-        });
-        break;
-      case 'reject':
-        ElMessageBox.prompt('请输入拒绝原因', '批量拒绝', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          inputPattern: /.+/,
-          inputErrorMessage: '拒绝原因不能为空',
-        }).then(async ({ value }) => {
-          const { error } = await api.withdraw.batchReject(ids.join(','), { reason: value });
-          if (error === 0) {
-            ElMessage.success('批量拒绝成功');
-            getData();
-          }
-        });
-        break;
+    } catch (error) {
+      console.error('导出失败:', error);
+      ElMessage.error('导出失败');
     }
   }
 

+ 155 - 210
src/app/shop/admin/goods/goods/select.vue

@@ -1,61 +1,12 @@
 <template>
   <el-container class="goods-select">
-    <el-aside class="select-aside">
-      <el-select
-        class="sa-m-b-16"
-        v-model="categoryLevel"
-        placeholder="Select"
-        @change="getCategoryDetail"
-      >
-        <el-option
-          v-for="item in category.data"
-          :key="item.id"
-          :label="item.name"
-          :value="item.id"
-        />
-      </el-select>
-      <el-tree
-        :data="category.detail"
-        empty-text=""
-        :props="{
-          children: 'children',
-          label: 'name',
-        }"
-        :indent="0"
-        :accordion="true"
-      >
-        <template #default="{ node, data }">
-          <span
-            class="custom-tree-node sa-flex"
-            :class="data.id == searchData.category_ids ? 'is-active' : ''"
-            :style="{ 'padding-left': node.level * 16 + 'px' }"
-            @click.stop="changeCategoryIds(data)"
-          >
-            <div class="sa-table-line-1">{{ data.name }}</div>
-            <el-icon
-              :class="['sa-m-l-8', node.expanded ? 'expand-arrow-up' : 'expand-arrow-down']"
-              v-if="data.children && data.children.length"
-              @click.stop="node.expanded = !node.expanded"
-            >
-              <ArrowDown />
-            </el-icon>
-          </span>
-        </template>
-      </el-tree>
-    </el-aside>
     <el-container>
-      <el-header class="goods-search sa-flex sa-flex-wrap sa-row-between">
-        <div class="sa-flex sa-flex-wrap goods-price">
-          <span class="sa-m-r-12">商品价格</span>
-          <el-input class="sa-w-88" v-model="searchData.price.min" placeholder="最低价格" />
-          <span class="sa-m-l-12 sa-m-r-12">至</span>
-          <el-input class="sa-w-88" v-model="searchData.price.max" placeholder="最高价格" />
-        </div>
-        <el-input
-          class="sa-w-180"
-          v-model="searchData.keyword"
-          prefix-icon="Search"
-          placeholder="请输入搜索内容"
+      <el-header class="goods-search">
+        <sa-search-simple
+          :searchFields="searchFields"
+          :defaultValues="defaultSearchValues"
+          @search="(val) => getData(1, val)"
+          @reset="getData(1)"
         />
       </el-header>
       <el-main v-loading="loading">
@@ -75,37 +26,54 @@
             type="selection"
             width="48"
           ></el-table-column>
-          <el-table-column prop="id" label="ID" min-width="88" />
-          <el-table-column label="商品信息" min-width="310">
-            <template #default="scoped">
-              <div class="sa-flex">
-                <sa-image :url="scoped.row.image" size="40"></sa-image>
-                <div class="sa-m-l-8">
-                  <div class="title sa-m-b-6 sa-table-line-1">
-                    {{ scoped.row.title }}
-                  </div>
-                  <div class="price">¥{{ scoped.row.price.join('~¥') }}</div>
+          <el-table-column prop="id" label="商品编号" min-width="120" align="center" />
+          <el-table-column label="商品信息" min-width="300">
+            <template #default="scope">
+              <div class="sa-flex sa-row-center">
+                <el-image
+                  :src="scope.row.image"
+                  style="width: 60px; height: 60px; margin-right: 12px"
+                  fit="cover"
+                />
+                <div>
+                  <div class="goods-title">{{ scope.row.title }}</div>
                 </div>
               </div>
             </template>
           </el-table-column>
-          <el-table-column prop="stock" label="库存" min-width="120"></el-table-column>
+          <el-table-column label="商品价格" min-width="120" align="center">
+            <template #default="scope">
+              <div v-if="scope.row.price && Array.isArray(scope.row.price)">
+                ৳{{ scope.row.price[0]?.replace(/[¥¥]/g, '').replace(/,/g, '') || '0' }}
+              </div>
+              <div v-else>
+                ৳{{
+                  scope.row.current_price?.replace(/[¥¥]/g, '').replace(/,/g, '') ||
+                  scope.row.original_price?.replace(/[¥¥]/g, '').replace(/,/g, '') ||
+                  '0'
+                }}
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column prop="stock" label="剩余库存" min-width="100" align="center">
+            <template #default="scope">
+              {{ scope.row.stock || 0 }}
+            </template>
+          </el-table-column>
           <el-table-column v-if="!modal.params.multiple" label="操作" width="80">
             <template #default="scope">
               <template v-if="modal.params.ftype == 'score_shop'">
                 <span v-if="scope.row.is_score_shop">已参加</span>
                 <el-button
                   v-if="!scope.row.is_score_shop"
-                  class="is-link"
+                  link
                   type="primary"
                   @click="singleSelect(scope.row.id)"
                   >参加</el-button
                 >
               </template>
               <template v-else>
-                <el-button class="is-link" type="primary" @click="singleSelect(scope.row.id)"
-                  >选择</el-button
-                >
+                <el-button link type="primary" @click="singleSelect(scope.row.id)">选择</el-button>
               </template>
             </template>
           </el-table-column>
@@ -134,121 +102,76 @@
    * @param {Number} max - 多选时最大数量(0 代表限制数量)
    * @param {String} ftype - 打开来源(score_shop)
    */
-  import { nextTick, onMounted, reactive, ref, watch } from 'vue';
+  import { nextTick, onMounted, reactive, ref } from 'vue';
   import { api } from '../goods.service';
-  import { api as categoryApi } from '@/app/shop/admin/goods/category/category.service';
   import { ElMessage } from 'element-plus';
-  import { composeFilter } from '@/sheep/utils';
-  import { useDebounceFn } from '@vueuse/core';
   import { usePagination } from '@/sheep/hooks';
-  import { cloneDeep } from 'lodash';
 
   const { pageData } = usePagination();
 
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps(['modal']);
 
-  // 分类
-  const categoryLevel = ref('');
-  const category = reactive({
-    data: [],
-    detail: [],
+  // 搜索字段配置
+  const searchFields = reactive({
+    title: {
+      type: 'input',
+      label: '商品名称',
+      placeholder: '请输入名称或编号',
+      width: 200,
+    },
   });
 
-  async function getCategory() {
-    const { data } = await categoryApi.select();
-    category.data = data;
-    category.data.unshift({
-      children: [],
-      id: 'all',
-      name: '全部分类',
-    });
-    if (data.length > 0) {
-      categoryLevel.value = data[0].id;
-      getCategoryDetail();
-    }
-  }
-
-  async function getCategoryDetail() {
-    const data = category.data.find((item) => item.id == categoryLevel.value);
-    category.detail = data.children || [];
-    searchData.category_ids = data.id;
-  }
-
-  function changeCategoryIds(data) {
-    searchData.category_ids = data.id;
-  }
-
-  const searchData = reactive({
-    category_ids: '',
-    keyword: '',
-    price: {
-      min: '',
-      max: '',
-    },
+  // 默认搜索值
+  const defaultSearchValues = reactive({
+    title: '',
   });
 
   const loading = ref(true);
   const table = reactive({
     list: [],
-    // selected: [],
-    ids: props.modal.params.ids,
+    ids: props.modal.params.ids || [],
+    selectedGoods: [], // 存储选中的商品完整数据
   });
 
-  async function getData() {
+  async function getData(page, searchParams = {}) {
+    if (page) pageData.page = page;
     loading.value = true;
-    let tempSearch = cloneDeep(searchData);
-    tempSearch.category_ids = tempSearch.category_ids + '';
-    if (tempSearch.price.min && tempSearch.price.max) {
-      tempSearch.price = `${tempSearch.price.min} - ${tempSearch.price.max}`;
-    }
-    // activity_type 搜索
-    if (props.modal.params.activity_type) {
-      tempSearch.activity_type = props.modal.params.activity_type;
-    }
-    // id 搜索
-    if (props.modal.params.goods_ids) {
-      tempSearch.goods = { field: 'id', value: props.modal.params.goods_ids };
-    }
 
-    let search = composeFilter(tempSearch, {
-      keyword: '=',
-      price: 'between',
-    });
-    const { data } = await api.goods.select({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      ...search,
-      data_type: props.modal.params.ftype || '',
-    });
-    table.list = data.data;
-    pageData.page = data.current_page;
-    pageData.list_rows = data.per_page;
-    pageData.total = data.total;
+    try {
+      const params = {
+        page: pageData.page,
+        list_rows: pageData.list_rows,
+        ...searchParams,
+      };
 
-    nextTick(() => {
-      table.list.forEach((l) => {
-        if (table.ids?.includes(l.id)) {
-          multipleTableRef.value?.toggleRowSelection(l, true);
-          toggleRowSelection('row', [l], l);
-        }
-      });
-    });
+      const { error, data } = await api.goods.list(params);
+      if (error === 0) {
+        table.list = data.data;
+        pageData.page = data.current_page;
+        pageData.list_rows = data.per_page;
+        pageData.total = data.total;
 
-    loading.value = false;
+        nextTick(() => {
+          setDefaultSelected();
+        });
+      }
+    } catch (error) {
+      console.error('获取商品列表失败:', error);
+    } finally {
+      loading.value = false;
+    }
   }
 
-  const debouncedFn = useDebounceFn(getData, 500);
-  watch(
-    () => searchData,
-    () => {
-      pageData.page = 1;
-      debouncedFn();
-    },
-    {
-      deep: true,
-    },
-  );
+  // 设置默认选中
+  function setDefaultSelected() {
+    table.list.forEach((item) => {
+      if (table.ids?.includes(item.id)) {
+        multipleTableRef.value?.toggleRowSelection(item, true);
+        toggleRowSelection('row', [item], item);
+      }
+    });
+  }
 
   const multipleTableRef = ref();
   function selectRow(selection, row) {
@@ -264,6 +187,8 @@
       }
     }
     toggleRowSelection('row', selection, row);
+    // 更新选中的商品数据
+    updateSelectedGoods();
   }
   function selectAll(selection) {
     if (
@@ -286,6 +211,8 @@
       }
     }
     toggleRowSelection('all', selection);
+    // 更新选中的商品数据
+    updateSelectedGoods();
   }
 
   function toggleRowSelection(type, selection, row) {
@@ -309,71 +236,76 @@
     }
   }
 
-  async function singleSelect(id) {
-    await getGoodsList(id);
-    emit('modalCallBack', {
-      event: 'confirm',
-      data: tempGoods.list[0],
-    });
+  // 更新选中的商品数据
+  function updateSelectedGoods() {
+    table.selectedGoods = table.list
+      .filter((item) => table.ids.includes(item.id))
+      .map((item) => ({
+        id: item.id,
+        title: item.title,
+        image: item.image,
+        price: item.price || item.current_price || item.original_price,
+        stock: item.stock,
+      }));
   }
 
-  async function confirm() {
-    await getGoodsList(table.ids.join(','));
-    emit('modalCallBack', {
-      event: 'confirm',
-      data: tempGoods.list,
-    });
+  function singleSelect(id) {
+    // 找到对应的商品数据
+    const selectedItem = table.list.find((item) => item.id === id);
+    if (selectedItem) {
+      const goodsData = {
+        id: selectedItem.id,
+        title: selectedItem.title,
+        image: selectedItem.image,
+        price: selectedItem.price || selectedItem.current_price || selectedItem.original_price,
+        stock: selectedItem.stock,
+      };
+
+      emit('modalCallBack', {
+        event: 'confirm',
+        data: goodsData,
+      });
+    }
   }
 
-  const tempGoods = reactive({
-    list: [],
-  });
+  function confirm() {
+    // 直接使用选中的商品数据
+    const selectedGoodsData =
+      table.selectedGoods.length > 0
+        ? table.selectedGoods
+        : table.list
+            .filter((item) => table.ids.includes(item.id))
+            .map((item) => ({
+              id: item.id,
+              title: item.title,
+              image: item.image,
+              price: item.price || item.current_price || item.original_price,
+              stock: item.stock,
+            }));
 
-  async function getGoodsList(ids) {
-    const { data } = await api.goods.select(
-      {
-        search: JSON.stringify({ id: [ids, 'in'] }),
-      },
-      'select',
-    );
-    tempGoods.list = data;
+    emit('modalCallBack', {
+      event: 'confirm',
+      data: selectedGoodsData,
+    });
   }
 
   onMounted(() => {
-    getCategory();
+    getData();
   });
 </script>
 <style lang="scss" scoped>
   .goods-select {
-    .select-aside {
-      --el-aside-width: 180px;
-      border-right: 1px solid var(--sa-border);
-      padding: 20px;
-      :deep() {
-        .el-tree-node {
-          .el-tree-node__content {
-            height: auto;
-            .el-tree-node__expand-icon {
-              display: none;
-            }
-          }
-        }
-      }
-      .custom-tree-node {
-        width: 100%;
-        height: 32px;
-        line-height: 32px;
-        border-radius: 4px;
-        &.is-active {
-          background: var(--t-bg-active);
-          color: var(--el-color-primary);
-        }
-      }
-    }
     .goods-search {
       --el-header-height: auto;
       padding-top: var(--sa-padding);
     }
+
+    .sa-footer--submit {
+      height: auto;
+      padding: 16px;
+      border-top: 1px solid var(--sa-border);
+      background: #fff;
+    }
     .title {
       height: 16px;
       line-height: 16px;
@@ -387,6 +319,19 @@
       font-weight: 400;
       color: #ff4d4f;
     }
+
+    .goods-title {
+      font-size: 14px;
+      color: #333;
+      line-height: 1.4;
+      word-break: break-word;
+      display: -webkit-box;
+      -webkit-box-orient: vertical;
+      -webkit-line-clamp: 2;
+      line-clamp: 2;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
     .sa-footer--submit {
       display: flex;
       align-items: center;

+ 94 - 63
src/app/shop/admin/marketing/group/edit.vue

@@ -1,62 +1,58 @@
 <template>
   <el-container>
     <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
+      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="140px">
         <el-form-item label="活动名称" prop="title">
-          <el-input v-model="form.model.title" placeholder="请填写活动名称"></el-input>
-        </el-form-item>
-        <el-form-item label="商品ID" prop="goods_id">
-          <el-input v-model="form.model.goods_id" placeholder="请填写商品ID"></el-input>
-        </el-form-item>
-        <el-form-item label="拼团人数" prop="people_num">
-          <el-input-number
-            v-model="form.model.people_num"
-            :min="2"
-            :max="100"
-            placeholder="拼团人数"
-          />
-          <span style="margin-left: 10px">人</span>
-        </el-form-item>
-        <el-form-item label="拼团价格" prop="group_price">
-          <el-input-number
-            v-model="form.model.group_price"
-            :min="0"
-            :precision="2"
-            placeholder="拼团价格"
-          />
-          <span style="margin-left: 10px">৳</span>
+          <el-input v-model="form.model.title" placeholder="请输入活动名称"></el-input>
         </el-form-item>
+
         <el-form-item label="开始时间" prop="start_time">
           <el-date-picker
             v-model="form.model.start_time"
-            type="datetime"
-            placeholder="选择开始时间"
-            format="YYYY-MM-DD HH:mm:ss"
-            value-format="YYYY-MM-DD HH:mm:ss"
+            type="date"
+            placeholder="请选择开始时间"
+            format="YYYY-MM-DD"
+            value-format="YYYY-MM-DD"
+            style="width: 100%"
           />
         </el-form-item>
+
         <el-form-item label="结束时间" prop="end_time">
           <el-date-picker
             v-model="form.model.end_time"
-            type="datetime"
-            placeholder="选择结束时间"
-            format="YYYY-MM-DD HH:mm:ss"
-            value-format="YYYY-MM-DD HH:mm:ss"
+            type="date"
+            placeholder="请选择结束时间"
+            format="YYYY-MM-DD"
+            value-format="YYYY-MM-DD"
+            style="width: 100%"
           />
         </el-form-item>
-        <el-form-item label="状态" prop="status">
-          <el-select v-model="form.model.status" placeholder="请选择状态">
-            <el-option label="启用" value="active"></el-option>
-            <el-option label="禁用" value="disabled"></el-option>
+
+        <el-form-item label="成团默认人数" prop="people_num">
+          <el-select
+            v-model="form.model.people_num"
+            placeholder="请选择成团默认人数"
+            style="width: 100%"
+          >
+            <el-option label="5人团" :value="5"></el-option>
+            <el-option label="10人团" :value="10"></el-option>
+            <el-option label="20人团" :value="20"></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="活动描述" prop="description">
-          <el-input
-            v-model="form.model.description"
-            type="textarea"
-            :rows="3"
-            placeholder="请填写活动描述"
-          ></el-input>
+
+        <el-form-item label="开团倒计时结束" prop="countdown_hours">
+          <el-select
+            v-model="form.model.countdown_hours"
+            placeholder="请选择开团倒计时结束"
+            style="width: 100%"
+          >
+            <el-option label="12小时" :value="12"></el-option>
+            <el-option label="24小时" :value="24"></el-option>
+            <el-option label="48小时" :value="48"></el-option>
+            <el-option label="72小时" :value="72"></el-option>
+            <el-option label="168小时" :value="168"></el-option>
+          </el-select>
+          <div class="form-tip mx-10px">开团后倒计时多久结束拼团活动</div>
         </el-form-item>
       </el-form>
     </el-main>
@@ -69,7 +65,9 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from '../marketing.service';
+  import { ElMessage } from 'element-plus';
+  import { groupMockData } from '@/sheep/mock/group';
+
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {
@@ -81,43 +79,67 @@
   const form = reactive({
     model: {
       title: '',
-      goods_id: '',
-      people_num: 2,
-      group_price: 0,
       start_time: '',
       end_time: '',
-      status: 'active',
-      description: '',
+      people_num: '',
+      countdown_hours: '',
     },
     rules: {
-      title: [{ required: true, message: '请填写活动名称', trigger: 'blur' }],
-      goods_id: [{ required: true, message: '请填写商品ID', trigger: 'blur' }],
-      people_num: [{ required: true, message: '请填写拼团人数', trigger: 'blur' }],
-      group_price: [{ required: true, message: '请填写拼团价格', trigger: 'blur' }],
+      title: [{ required: true, message: '请输入活动名称', trigger: 'blur' }],
       start_time: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
       end_time: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
-      status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+      people_num: [{ required: true, message: '请选择成团默认人数', trigger: 'change' }],
+      countdown_hours: [{ required: true, message: '请选择开团倒计时结束', trigger: 'change' }],
     },
   });
   const loading = ref(false);
+
   // 获取详情
   async function getDetail(id) {
     loading.value = true;
-    const { error, data } = await api.group.detail(id);
-    error === 0 && (form.model = data);
-    loading.value = false;
+    try {
+      // 使用mock数据
+      const result = groupMockData.getDetail(id);
+      if (result.error === 0) {
+        form.model = { ...result.data };
+      } else {
+        ElMessage.error(result.msg);
+      }
+    } catch (error) {
+      console.error('获取详情失败:', error);
+      ElMessage.error('获取详情失败');
+    } finally {
+      loading.value = false;
+    }
   }
+
   // 表单关闭时提交
   async function confirm() {
     unref(formRef).validate(async (valid) => {
       if (!valid) return;
-      let submitForm = cloneDeep(form.model);
-      const { error } =
-        props.modal.params.type == 'add'
-          ? await api.group.add(submitForm)
-          : await api.group.edit(props.modal.params.id, submitForm);
-      if (error == 0) {
-        emit('modalCallBack', { event: 'confirm' });
+
+      loading.value = true;
+      try {
+        let submitForm = cloneDeep(form.model);
+        let result;
+
+        if (props.modal.params.type == 'add') {
+          result = groupMockData.add(submitForm);
+        } else {
+          result = groupMockData.edit(props.modal.params.id, submitForm);
+        }
+
+        if (result.error === 0) {
+          ElMessage.success(result.msg);
+          emit('modalCallBack', { event: 'confirm' });
+        } else {
+          ElMessage.error(result.msg);
+        }
+      } catch (error) {
+        console.error('提交失败:', error);
+        ElMessage.error('提交失败');
+      } finally {
+        loading.value = false;
       }
     });
   }
@@ -130,3 +152,12 @@
     init();
   });
 </script>
+
+<style lang="scss" scoped>
+  .form-tip {
+    margin-top: 8px;
+    font-size: 12px;
+    color: #909399;
+    line-height: 1.4;
+  }
+</style>

+ 323 - 141
src/app/shop/admin/marketing/group/index.vue

@@ -1,133 +1,191 @@
 <template>
   <el-container class="group-view panel-block">
-    <el-header class="sa-header">
-      <!-- 简化搜索组件 -->
-      <div class="search-container">
-        <sa-search-simple
-          :searchFields="searchFields"
-          :defaultValues="defaultSearchValues"
-          @search="(val) => getData(1, val)"
-          @reset="getData(1)"
-        >
-        </sa-search-simple>
-      </div>
-      <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">拼团活动</div>
-        <div>
-          <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
-          <el-button icon="Plus" type="primary" @click="addRow">新建</el-button>
-        </div>
-      </div>
-    </el-header>
-    <el-main class="sa-p-0">
-      <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
-        <el-table
-          height="100%"
-          class="sa-table"
-          :data="table.data"
-          @selection-change="changeSelection"
-          @sort-change="fieldFilter"
-          @row-dblclick="editRow"
-          row-key="id"
-          stripe
-        >
-          <template #empty>
-            <sa-empty />
-          </template>
-          <el-table-column type="selection" width="48" align="center"></el-table-column>
-          <el-table-column prop="id" label="ID" min-width="100" sortable="custom">
-          </el-table-column>
-          <el-table-column label="活动名称" min-width="150">
-            <template #default="scope">
-              <span class="sa-table-line-1">
-                {{ scope.row.title || '-' }}
-              </span>
-            </template>
-          </el-table-column>
-          <el-table-column label="商品名称" min-width="150">
-            <template #default="scope">
-              <span class="sa-table-line-1">
-                {{ scope.row.goods_title || '-' }}
-              </span>
-            </template>
-          </el-table-column>
-          <el-table-column label="拼团人数" min-width="100">
-            <template #default="scope"> {{ scope.row.people_num || '-' }}人 </template>
-          </el-table-column>
-          <el-table-column label="拼团价格" min-width="120">
-            <template #default="scope"> ৳{{ scope.row.group_price || '-' }} </template>
-          </el-table-column>
-          <el-table-column label="状态" min-width="100">
-            <template #default="scope">
-              <el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'">
-                {{ scope.row.status_text || '-' }}
-              </el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column label="开始时间" min-width="160">
-            <template #default="scope">
-              {{ scope.row.start_time || '-' }}
-            </template>
-          </el-table-column>
-          <el-table-column label="结束时间" min-width="160">
-            <template #default="scope">
-              {{ scope.row.end_time || '-' }}
-            </template>
-          </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="120">
-            <template #default="scope">
-              <el-button class="is-link" type="primary" @click="editRow(scope.row)">编辑</el-button>
-              <el-popconfirm
-                width="fit-content"
-                confirm-button-text="确认"
-                cancel-button-text="取消"
-                title="确认删除这条记录?"
-                @confirm="deleteApi(scope.row.id)"
-              >
-                <template #reference>
-                  <el-button class="is-link" type="danger"> 删除 </el-button>
+    <el-container class="group-index-main">
+      <el-container>
+        <el-header class="sa-header">
+          <!-- 简化搜索组件 -->
+          <div class="search-container">
+            <sa-search-simple
+              :searchFields="searchFields"
+              :defaultValues="defaultSearchValues"
+              @search="(val) => getData(1, val)"
+              @reset="getData(1)"
+            >
+            </sa-search-simple>
+          </div>
+          <el-tabs class="sa-tabs" v-model="currentStatus" @tab-change="handleTabChange">
+            <el-tab-pane :label="`全部(${statusCount.all})`" name="all"></el-tab-pane>
+            <el-tab-pane :label="`待开始(${statusCount.pending})`" name="pending"></el-tab-pane>
+            <el-tab-pane :label="`进行中(${statusCount.active})`" name="active"></el-tab-pane>
+            <el-tab-pane :label="`已结束(${statusCount.ended})`" name="ended"></el-tab-pane>
+          </el-tabs>
+          <div class="sa-title sa-flex sa-row-between">
+            <div class="label sa-flex">
+              <span class="left">拼团活动列表</span>
+            </div>
+            <div>
+              <el-button
+                class="sa-button-refresh"
+                icon="RefreshRight"
+                @click="getData()"
+              ></el-button>
+              <el-button icon="Plus" type="primary" @click="addRow">添加活动</el-button>
+            </div>
+          </div>
+        </el-header>
+        <el-main class="sa-p-0">
+          <div class="sa-table-wrap" v-loading="loading">
+            <el-table
+              height="100%"
+              class="sa-table"
+              :data="table.data"
+              @selection-change="changeSelection"
+              @sort-change="fieldFilter"
+              row-key="id"
+              stripe
+            >
+              <template #empty>
+                <sa-empty></sa-empty>
+              </template>
+              <el-table-column type="selection" width="48" align="center"></el-table-column>
+              <el-table-column
+                sortable="custom"
+                prop="activity_no"
+                label="活动编号"
+                align="center"
+              ></el-table-column>
+              <el-table-column label="活动标题">
+                <template #default="scope">
+                  <span class="sa-table-line-1">
+                    {{ scope.row.title || '--' }}
+                  </span>
                 </template>
-              </el-popconfirm>
-            </template>
-          </el-table-column>
-        </el-table>
-      </div>
-    </el-main>
-    <sa-view-bar>
-      <template #left>
-        <sa-batch-handle
-          :batchHandleTools="batchHandleTools"
-          :selectedLeng="table.selected.length"
-          @batchHandle="batchHandle"
-        ></sa-batch-handle>
-      </template>
-      <template #right>
-        <sa-pagination :pageData="pageData" @updateFn="getData" />
-      </template>
-    </sa-view-bar>
+              </el-table-column>
+              <el-table-column label="活动状态" align="center">
+                <template #default="scope">
+                  <el-tag :type="statusList.color[scope.row.status]">
+                    {{ scope.row.status_text || '--' }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column sortable="custom" prop="start_time" label="开始时间" align="center">
+                <template #default="scope">
+                  {{ scope.row.start_time || '--' }}
+                </template>
+              </el-table-column>
+              <el-table-column sortable="custom" prop="end_time" label="结束时间" align="center">
+                <template #default="scope">
+                  {{ scope.row.end_time || '--' }}
+                </template>
+              </el-table-column>
+              <el-table-column label="商品数量" align="center">
+                <template #default="scope">
+                  <el-tag type="info" size="small"> {{ scope.row.goods_count || 0 }}个商品 </el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="预警通知" align="center">
+                <template #default="scope">
+                  {{ scope.row.warning_notice || '--' }}
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" fixed="right">
+                <template #default="scope">
+                  <div class="sa-flex">
+                    <el-button link type="primary" @click="editRow(scope.row)">编辑</el-button>
+                    <el-button link class="sa-m-l-12" type="success" @click="setGoods(scope.row)"
+                      >设置商品</el-button
+                    >
+                    <el-popconfirm
+                      width="fit-content"
+                      confirm-button-text="确认"
+                      cancel-button-text="取消"
+                      title="确认删除这条记录?"
+                      @confirm="deleteRow(scope.row.id)"
+                    >
+                      <template #reference>
+                        <el-button link class="sa-m-l-12" type="danger"> 删除 </el-button>
+                      </template>
+                    </el-popconfirm>
+                  </div>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </el-main>
+      </el-container>
+    </el-container>
+    <el-footer class="group-index-footer">
+      <sa-view-bar>
+        <template #left>
+          <sa-batch-handle
+            :batchHandleTools="batchHandleTools"
+            :selectedLeng="table.selected.length"
+            @batchHandle="batchHandle"
+          ></sa-batch-handle>
+        </template>
+        <template #right>
+          <sa-pagination :pageData="pageData" @updateFn="getData" />
+        </template>
+      </sa-view-bar>
+    </el-footer>
   </el-container>
 </template>
+<script>
+  export default {
+    name: 'shop.admin.marketing.group',
+  };
+</script>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../marketing.service';
-  import { ElMessageBox } from 'element-plus';
-  import { useModal } from '@/sheep/hooks';
-  import { usePagination } from '@/sheep/hooks';
+  import { ElMessage, ElMessageBox } from 'element-plus';
+  import { useModal, usePagination } from '@/sheep/hooks';
+  import { groupMockData } from '@/sheep/mock/group';
   import groupEdit from './edit.vue';
+  import GoodsSelect from '@/app/shop/admin/goods/goods/select.vue';
+
   const { pageData } = usePagination();
 
+  // 当前状态Tab
+  const currentStatus = ref('all');
+
+  // 状态统计
+  const statusCount = ref({
+    all: 0,
+    pending: 0,
+    active: 0,
+    ended: 0,
+  });
+
+  // 状态列表配置
+  const statusList = reactive({
+    color: {
+      all: '',
+      pending: 'warning',
+      active: 'success',
+      ended: 'info',
+    },
+  });
+
   // 搜索字段配置
   const searchFields = reactive({
     title: {
       type: 'input',
-      label: '活动名称',
-      placeholder: '请输入活动名称',
+      label: '活动标题',
+      placeholder: '请输入活动标题',
+      width: 200,
+    },
+    activity_no: {
+      type: 'input',
+      label: '活动编号',
+      placeholder: '请输入活动编号',
       width: 200,
     },
   });
+
   // 默认搜索值
   const defaultSearchValues = reactive({
     title: '',
+    activity_no: '',
   });
   // 列表
   const table = reactive({
@@ -137,26 +195,57 @@
     selected: [],
   });
   const loading = ref(true);
-  // 获取
+
+  // 获取数据
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.group.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      order: table.order,
-      ...searchParams,
-      sort: table.sort,
-    });
-    console.log('API 响应:', error, data);
-    if (error === 0) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
+
+    try {
+      // 使用mock数据
+      let mockData = [...groupMockData.list];
+
+      // 根据当前状态过滤数据
+      if (currentStatus.value !== 'all') {
+        mockData = mockData.filter((item) => item.status === currentStatus.value);
+      }
+
+      // 根据搜索条件过滤
+      if (searchParams.title) {
+        mockData = mockData.filter((item) =>
+          item.title.toLowerCase().includes(searchParams.title.toLowerCase()),
+        );
+      }
+      if (searchParams.activity_no) {
+        mockData = mockData.filter((item) =>
+          item.activity_no.toLowerCase().includes(searchParams.activity_no.toLowerCase()),
+        );
+      }
+
+      // 模拟分页
+      const total = mockData.length;
+      const start = (pageData.page - 1) * pageData.list_rows;
+      const end = start + pageData.list_rows;
+      const paginatedData = mockData.slice(start, end);
+
+      // 设置数据
+      table.data = paginatedData;
+      pageData.total = total;
+
+      // 更新状态统计
+      statusCount.value = { ...groupMockData.statusCount };
+    } catch (error) {
+      console.error('获取数据失败:', error);
+    } finally {
+      loading.value = false;
     }
-    loading.value = false;
   }
+
+  // Tab切换处理
+  const handleTabChange = (tabName) => {
+    currentStatus.value = tabName;
+    getData(1);
+  };
   // table 字段排序
   function fieldFilter({ prop, order }) {
     table.order = order == 'ascending' ? 'asc' : 'desc';
@@ -169,17 +258,17 @@
   }
   // 分页/批量操作
   const batchHandleTools = [
-    {
-      type: 'delete',
-      label: '删除',
-      auth: 'shop.admin.marketing.group.delete',
-      class: 'danger',
-    },
+    // {
+    //   type: 'delete',
+    //   label: '删除',
+    //   auth: 'shop.admin.marketing.group.delete',
+    //   class: 'danger',
+    // },
   ];
   function addRow() {
     useModal(
       groupEdit,
-      { title: '新建拼团', type: 'add' },
+      { title: '新建拼团活动', type: 'add' },
       {
         confirm: () => {
           getData();
@@ -187,11 +276,12 @@
       },
     );
   }
+
   function editRow(row) {
     useModal(
       groupEdit,
       {
-        title: '编辑拼团',
+        title: '编辑拼团活动',
         type: 'edit',
         id: row.id,
       },
@@ -202,10 +292,59 @@
       },
     );
   }
-  // 删除api 单独批量可以直接调用
-  async function deleteApi(id) {
-    await api.group.delete(id);
-    getData();
+
+  // 设置商品
+  function setGoods(row) {
+    useModal(
+      GoodsSelect,
+      {
+        title: '选择商品',
+        multiple: true,
+        ids: row.goods_ids || [], // 已选择的商品ID
+      },
+      {
+        confirm: (res) => {
+          // 更新活动的商品列表
+          updateActivityGoods(row.id, res.data);
+        },
+      },
+    );
+  }
+
+  // 更新活动商品
+  async function updateActivityGoods(activityId, goodsList) {
+    try {
+      // 这里可以调用API更新活动商品
+      // 暂时使用mock数据模拟
+      const goodsIds = goodsList.map((item) => item.id);
+      const result = groupMockData.updateGoods(activityId, goodsIds, goodsList);
+
+      if (result.error === 0) {
+        ElMessage.success('商品设置成功');
+        getData(); // 刷新列表
+      } else {
+        ElMessage.error(result.msg);
+      }
+    } catch (error) {
+      console.error('设置商品失败:', error);
+      ElMessage.error('设置商品失败');
+    }
+  }
+
+  // 删除单条记录
+  async function deleteRow(id) {
+    try {
+      const result = groupMockData.delete(id);
+      if (result.error === 0) {
+        ElMessage.success(result.msg);
+        getData();
+      } else {
+        ElMessage.error(result.msg);
+      }
+    } catch (error) {
+      console.error('删除失败:', error);
+      ElMessage.error('删除失败');
+    }
   }
   async function batchHandle(type) {
     let ids = [];
@@ -219,14 +358,29 @@
           cancelButtonText: '取消',
           type: 'warning',
         }).then(() => {
-          deleteApi(ids.join(','));
+          batchDeleteRows(ids);
         });
         break;
       default:
-        await api.group.edit(ids.join(','), {
-          status: type,
-        });
+        // 其他批量操作可以在这里添加
+        ElMessage.info('该功能暂未实现');
+    }
+  }
+
+  // 批量删除方法
+  async function batchDeleteRows(ids) {
+    try {
+      const result = groupMockData.batchDelete(ids);
+      if (result.error === 0) {
+        ElMessage.success(result.msg);
         getData();
+        table.selected = []; // 清空选中项
+      } else {
+        ElMessage.error(result.msg);
+      }
+    } catch (error) {
+      console.error('批量删除失败:', error);
+      ElMessage.error('批量删除失败');
     }
   }
 
@@ -236,13 +390,41 @@
 </script>
 <style lang="scss" scoped>
   .group-view {
+    height: 100%;
+
+    .group-index-main {
+      height: 100%;
+    }
+
+    .group-index-footer {
+      height: auto;
+    }
+
     .el-header {
       height: auto;
     }
+
     .el-main {
       .sa-table-wrap {
         height: 100%;
       }
     }
+
+    .search-container {
+      margin-bottom: 16px;
+    }
+
+    .sa-tabs {
+      margin-bottom: 16px;
+    }
+
+    .sa-table-line-1 {
+      display: -webkit-box;
+      -webkit-box-orient: vertical;
+      -webkit-line-clamp: 1;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      word-break: break-all;
+    }
   }
 </style>

+ 10 - 0
src/assets/css/sa-global.scss

@@ -496,3 +496,13 @@ button {
 .sa-color--info {
   color: var(--el-color-info);
 }
+
+.el-link.is-underline:after {
+  content: '';
+  position: absolute;
+  left: 0;
+  right: 0;
+  height: 0;
+  bottom: 0;
+  border-bottom: 1px solid var(--el-link-text-color);
+}

+ 64 - 70
src/sheep/components/sa-table/sa-search/sa-search-simple.global.vue

@@ -2,10 +2,70 @@
   <div class="sa-search-simple">
     <div class="search-form">
       <el-form ref="formRef" :model="formData" inline @submit.prevent="handleSearch">
-        <!-- 始终显示的搜索项 -->
+        <!-- 统一的搜索表单区域 -->
         <div class="search-form-main">
+          <!-- 始终显示的前3个搜索项 -->
           <template v-for="(field, key, index) in searchFields" :key="key">
-            <el-form-item v-if="isExpanded || index < 3" :prop="key" :label="field.label">
+            <el-form-item v-if="index < 3" :prop="key" :label="field.label">
+              <!-- 输入框类型 -->
+              <el-input
+                v-if="field.type === 'input'"
+                v-model="formData[key]"
+                :placeholder="field.placeholder || ''"
+                clearable
+                :style="{ width: field.width ? field.width + 'px' : '200px' }"
+              />
+
+              <!-- 下拉选择类型 -->
+              <el-select
+                v-else-if="field.type === 'select'"
+                v-model="formData[key]"
+                :placeholder="field.placeholder || field.label"
+                clearable
+                :style="{ width: field.width ? field.width + 'px' : '200px' }"
+              >
+                <el-option
+                  v-for="option in field.options"
+                  :key="option.value"
+                  :label="option.label"
+                  :value="option.value"
+                />
+              </el-select>
+
+              <!-- 级联选择类型 -->
+              <el-cascader
+                v-else-if="field.type === 'cascader'"
+                v-model="formData[key]"
+                :options="field.options"
+                :props="field.props"
+                :placeholder="field.placeholder || field.label"
+                clearable
+                :style="{ width: field.width ? field.width + 'px' : '300px' }"
+              />
+
+              <!-- 日期范围类型 -->
+              <el-date-picker
+                v-else-if="field.type === 'daterange'"
+                v-model="formData[key]"
+                type="datetimerange"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                format="YYYY-MM-DD HH:mm:ss"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :editable="false"
+                :style="{ width: field.width ? field.width + 'px' : '350px' }"
+              />
+            </el-form-item>
+          </template>
+
+          <!-- 展开时显示的额外搜索项 -->
+          <template
+            v-if="isExpanded"
+            v-for="(field, key, index) in searchFields"
+            :key="'expanded-' + key"
+          >
+            <el-form-item v-if="index >= 3" :prop="key" :label="field.label">
               <!-- 输入框类型 -->
               <el-input
                 v-if="field.type === 'input'"
@@ -73,65 +133,6 @@
             </el-button>
           </el-form-item>
         </div>
-
-        <!-- 折叠的高级搜索项 -->
-        <el-collapse-transition>
-          <div v-show="isExpanded && hasMoreThanThree" class="search-form-advanced">
-            <template v-for="(field, key, index) in searchFields" :key="'advanced-' + key">
-              <el-form-item v-if="index >= 3" :prop="key" :label="field.label">
-                <!-- 输入框类型 -->
-                <el-input
-                  v-if="field.type === 'input'"
-                  v-model="formData[key]"
-                  :placeholder="field.placeholder || ''"
-                  clearable
-                  :style="{ width: field.width ? field.width + 'px' : '200px' }"
-                />
-
-                <!-- 下拉选择类型 -->
-                <el-select
-                  v-else-if="field.type === 'select'"
-                  v-model="formData[key]"
-                  :placeholder="field.placeholder || field.label"
-                  clearable
-                  :style="{ width: field.width ? field.width + 'px' : '200px' }"
-                >
-                  <el-option
-                    v-for="option in field.options"
-                    :key="option.value"
-                    :label="option.label"
-                    :value="option.value"
-                  />
-                </el-select>
-
-                <!-- 级联选择类型 -->
-                <el-cascader
-                  v-else-if="field.type === 'cascader'"
-                  v-model="formData[key]"
-                  :options="field.options"
-                  :props="field.props"
-                  :placeholder="field.placeholder || field.label"
-                  clearable
-                  :style="{ width: field.width ? field.width + 'px' : '300px' }"
-                />
-
-                <!-- 日期范围类型 -->
-                <el-date-picker
-                  v-else-if="field.type === 'daterange'"
-                  v-model="formData[key]"
-                  type="datetimerange"
-                  value-format="YYYY-MM-DD HH:mm:ss"
-                  format="YYYY-MM-DD HH:mm:ss"
-                  range-separator="至"
-                  start-placeholder="开始日期"
-                  end-placeholder="结束日期"
-                  :editable="false"
-                  :style="{ width: field.width ? field.width + 'px' : '350px' }"
-                />
-              </el-form-item>
-            </template>
-          </div>
-        </el-collapse-transition>
       </el-form>
     </div>
   </div>
@@ -262,6 +263,7 @@
       :deep(.el-form-item) {
         margin-bottom: 16px;
         margin-right: 16px;
+        min-width: fit-content; // 确保表单项不会被过度压缩
       }
 
       .search-form-main {
@@ -271,13 +273,6 @@
         margin-bottom: 0;
       }
 
-      .search-form-advanced {
-        display: flex;
-        flex-wrap: wrap;
-        align-items: flex-end;
-        padding-top: 16px;
-      }
-
       .expand-toggle-btn {
         margin-left: 8px;
         transition: all 0.3s ease;
@@ -314,8 +309,7 @@
           margin-right: 12px;
         }
 
-        .search-form-main,
-        .search-form-advanced {
+        .search-form-main {
           flex-direction: column;
           align-items: stretch;
 

+ 211 - 0
src/sheep/mock/commission.js

@@ -0,0 +1,211 @@
+// 佣金记录mock数据
+export const commissionMockData = {
+  // 佣金记录列表
+  list: [
+    {
+      id: 1,
+      commission_no: 'COM202501150001',
+      username: 'john_doe',
+      phone: '13800138001',
+      amount: 150,
+      description: '邀请用户注册奖励',
+      order_no: 'ORD202501150001',
+      status: 'processing',
+      status_text: '处理中',
+      type: 'invite_reward',
+      type_text: '邀请奖励',
+      create_time: '2025-01-15 10:30:00',
+      settle_time: '',
+      user_id: 1001,
+    },
+    {
+      id: 2,
+      commission_no: 'COM202501150002',
+      username: 'jane_smith',
+      phone: '13800138002',
+      amount: 300,
+      description: '拼团成功奖励',
+      order_no: 'ORD202501150002',
+      status: 'settled',
+      status_text: '已到账',
+      type: 'group_reward',
+      type_text: '拼团奖励',
+      create_time: '2025-01-15 09:20:00',
+      settle_time: '2025-01-15 12:30:00',
+      user_id: 1002,
+    },
+    {
+      id: 3,
+      commission_no: 'COM202501150003',
+      username: 'mike_wilson',
+      phone: '13800138003',
+      amount: 200,
+      description: '开团成功奖励',
+      order_no: 'ORD202501150003',
+      status: 'settled',
+      status_text: '已到账',
+      type: 'leader_reward',
+      type_text: '开团奖励',
+      create_time: '2025-01-15 08:15:00',
+      settle_time: '2025-01-15 11:45:00',
+      user_id: 1003,
+    },
+    {
+      id: 4,
+      commission_no: 'COM202501150004',
+      username: 'sarah_jones',
+      phone: '13800138004',
+      amount: 50,
+      description: '每日签到奖励',
+      order_no: '',
+      status: 'unsettled',
+      status_text: '未到账',
+      type: 'checkin_reward',
+      type_text: '签到奖励',
+      create_time: '2025-01-15 07:00:00',
+      settle_time: '',
+      user_id: 1004,
+    },
+    {
+      id: 5,
+      commission_no: 'COM202501150005',
+      username: 'david_brown',
+      phone: '13800138005',
+      amount: 120,
+      description: '下线拼团奖励',
+      order_no: 'ORD202501150005',
+      status: 'processing',
+      status_text: '处理中',
+      type: 'group_sub_reward',
+      type_text: '拼团奖励-下线',
+      create_time: '2025-01-14 16:30:00',
+      settle_time: '',
+      user_id: 1005,
+    },
+    {
+      id: 6,
+      commission_no: 'COM202501150006',
+      username: 'lisa_davis',
+      phone: '13800138006',
+      amount: 80,
+      description: '下线开团奖励',
+      order_no: 'ORD202501150006',
+      status: 'settled',
+      status_text: '已到账',
+      type: 'leader_sub_reward',
+      type_text: '开团奖励-下线',
+      create_time: '2025-01-14 15:20:00',
+      settle_time: '2025-01-14 18:30:00',
+      user_id: 1006,
+    },
+  ],
+
+  // 获取列表数据
+  getList(params = {}) {
+    const {
+      page = 1,
+      list_rows = 10,
+      username = '',
+      phone = '',
+      commission_no = '',
+      status = 'all',
+      type = '',
+      start_time = '',
+      end_time = '',
+    } = params;
+
+    let filteredData = [...this.list];
+
+    // 搜索过滤
+    if (username) {
+      filteredData = filteredData.filter(item => 
+        item.username.toLowerCase().includes(username.toLowerCase())
+      );
+    }
+    if (phone) {
+      filteredData = filteredData.filter(item => 
+        item.phone.includes(phone)
+      );
+    }
+    if (commission_no) {
+      filteredData = filteredData.filter(item => 
+        item.commission_no.includes(commission_no)
+      );
+    }
+    if (status && status !== 'all') {
+      filteredData = filteredData.filter(item => item.status === status);
+    }
+    if (type) {
+      filteredData = filteredData.filter(item => item.type === type);
+    }
+
+    // 时间过滤
+    if (start_time && end_time) {
+      filteredData = filteredData.filter(item => {
+        const itemTime = new Date(item.create_time);
+        const startTime = new Date(start_time);
+        const endTime = new Date(end_time);
+        return itemTime >= startTime && itemTime <= endTime;
+      });
+    }
+
+    // 分页
+    const total = filteredData.length;
+    const startIndex = (page - 1) * list_rows;
+    const endIndex = startIndex + list_rows;
+    const data = filteredData.slice(startIndex, endIndex);
+
+    return {
+      error: 0,
+      msg: 'success',
+      data: {
+        data,
+        total,
+        per_page: list_rows,
+        current_page: page,
+        last_page: Math.ceil(total / list_rows),
+      },
+    };
+  },
+
+  // 获取详情数据
+  getDetail(id) {
+    const item = this.list.find(item => item.id == id);
+    if (!item) {
+      return {
+        error: 1,
+        msg: '记录不存在',
+        data: null,
+      };
+    }
+
+    return {
+      error: 0,
+      msg: 'success',
+      data: {
+        ...item,
+        // 详情页面额外字段
+        user_balance: '2,500',
+        commission_balance: '1,200',
+        total_commission: '5,800',
+        commission_rate: '5.0',
+        source_type: 'order',
+        source_type_text: '订单佣金',
+      },
+    };
+  },
+
+  // 导出数据
+  export(params = {}) {
+    const data = this.getList({ ...params, list_rows: 1000 });
+    return {
+      error: 0,
+      msg: '导出成功',
+      data: {
+        filename: `commission_export_${new Date().getTime()}.xlsx`,
+        url: '/download/commission_export.xlsx',
+        total: data.data.total,
+      },
+    };
+  },
+};

+ 304 - 0
src/sheep/mock/group.js

@@ -0,0 +1,304 @@
+// 拼团活动mock数据
+export const groupMockData = {
+  // 拼团活动列表
+  list: [
+    {
+      id: 1,
+      activity_no: 'PT202501001',
+      title: '新年特惠拼团活动',
+      status: 'pending',
+      status_text: '待开始',
+      start_time: '2025-01-20',
+      end_time: '2025-01-25',
+      people_num: 5,
+      countdown_hours: 24,
+      warning_notice: '活动即将开始',
+      goods_ids: [1, 2],
+      goods_list: [
+        { id: 1, title: 'BOLON Classic Aviator Polarized Sunglasses', price: 1000 },
+        { id: 2, title: 'Premium Wireless Bluetooth Headphones', price: 1500 },
+      ],
+      goods_count: 2,
+      created_at: '2025-01-15 10:00:00',
+      updated_at: '2025-01-15 10:00:00',
+    },
+    {
+      id: 2,
+      activity_no: 'PT202501002',
+      title: '春节大促拼团',
+      status: 'active',
+      status_text: '进行中',
+      start_time: '2025-01-10',
+      end_time: '2025-01-30',
+      people_num: 10,
+      countdown_hours: 48,
+      warning_notice: '活动进行中',
+      goods_ids: [3],
+      goods_list: [{ id: 3, title: 'Smart Watch Series 5', price: 2500 }],
+      goods_count: 1,
+      created_at: '2025-01-08 10:00:00',
+      updated_at: '2025-01-10 00:00:00',
+    },
+    {
+      id: 3,
+      activity_no: 'PT202501003',
+      title: '元宵节限时拼团',
+      status: 'active',
+      status_text: '进行中',
+      start_time: '2025-01-12',
+      end_time: '2025-01-18',
+      people_num: 20,
+      countdown_hours: 72,
+      warning_notice: '活动即将结束',
+      goods_ids: [],
+      goods_list: [],
+      goods_count: 0,
+      created_at: '2025-01-10 15:30:00',
+      updated_at: '2025-01-12 08:00:00',
+    },
+    {
+      id: 4,
+      activity_no: 'PT202412001',
+      title: '圣诞节拼团活动',
+      status: 'ended',
+      status_text: '已结束',
+      start_time: '2024-12-20',
+      end_time: '2024-12-26',
+      people_num: 5,
+      countdown_hours: 24,
+      warning_notice: '活动已结束',
+      created_at: '2024-12-18 10:00:00',
+      updated_at: '2024-12-26 23:59:59',
+    },
+    {
+      id: 5,
+      activity_no: 'PT202412002',
+      title: '年终清仓拼团',
+      status: 'ended',
+      status_text: '已结束',
+      start_time: '2024-12-28',
+      end_time: '2024-12-31',
+      people_num: 10,
+      countdown_hours: 48,
+      warning_notice: '活动已结束',
+      created_at: '2024-12-25 14:20:00',
+      updated_at: '2024-12-31 23:59:59',
+    },
+    {
+      id: 6,
+      activity_no: 'PT202501004',
+      title: '情人节浪漫拼团',
+      status: 'pending',
+      status_text: '待开始',
+      start_time: '2025-02-10',
+      end_time: '2025-02-15',
+      people_num: 5,
+      countdown_hours: 12,
+      warning_notice: '活动尚未开始',
+      created_at: '2025-01-15 16:45:00',
+      updated_at: '2025-01-15 16:45:00',
+    },
+    {
+      id: 7,
+      activity_no: 'PT202501005',
+      title: '开学季拼团优惠',
+      status: 'pending',
+      status_text: '待开始',
+      start_time: '2025-02-20',
+      end_time: '2025-03-05',
+      people_num: 20,
+      countdown_hours: 168,
+      warning_notice: '活动尚未开始',
+      created_at: '2025-01-16 09:15:00',
+      updated_at: '2025-01-16 09:15:00',
+    },
+    {
+      id: 8,
+      activity_no: 'PT202501006',
+      title: '女神节专属拼团',
+      status: 'active',
+      status_text: '进行中',
+      start_time: '2025-01-14',
+      end_time: '2025-01-22',
+      people_num: 10,
+      countdown_hours: 72,
+      warning_notice: '活动火热进行中',
+      created_at: '2025-01-12 11:30:00',
+      updated_at: '2025-01-14 00:00:00',
+    },
+  ],
+
+  // 状态统计
+  statusCount: {
+    all: 8,
+    pending: 3,
+    active: 3,
+    ended: 2,
+  },
+
+  // 获取详情
+  getDetail: (id) => {
+    const item = groupMockData.list.find((item) => item.id == id);
+    if (item) {
+      return {
+        error: 0,
+        msg: '获取成功',
+        data: { ...item },
+      };
+    } else {
+      return {
+        error: 1,
+        msg: '记录不存在',
+        data: null,
+      };
+    }
+  },
+
+  // 添加记录
+  add: (data) => {
+    const newId = Math.max(...groupMockData.list.map((item) => item.id)) + 1;
+    const newItem = {
+      id: newId,
+      activity_no: `PT${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(newId).padStart(3, '0')}`,
+      title: data.title,
+      status: 'pending',
+      status_text: '待开始',
+      start_time: data.start_time,
+      end_time: data.end_time,
+      people_num: data.people_num,
+      countdown_hours: data.countdown_hours,
+      warning_notice: '活动尚未开始',
+      created_at: new Date().toISOString().slice(0, 19).replace('T', ' '),
+      updated_at: new Date().toISOString().slice(0, 19).replace('T', ' '),
+    };
+
+    groupMockData.list.unshift(newItem);
+    groupMockData.statusCount.all++;
+    groupMockData.statusCount.pending++;
+
+    return {
+      error: 0,
+      msg: '添加成功',
+      data: newItem,
+    };
+  },
+
+  // 编辑记录
+  edit: (id, data) => {
+    const index = groupMockData.list.findIndex((item) => item.id == id);
+    if (index !== -1) {
+      const oldItem = groupMockData.list[index];
+      const updatedItem = {
+        ...oldItem,
+        title: data.title,
+        start_time: data.start_time,
+        end_time: data.end_time,
+        people_num: data.people_num,
+        countdown_hours: data.countdown_hours,
+        updated_at: new Date().toISOString().slice(0, 19).replace('T', ' '),
+      };
+
+      groupMockData.list[index] = updatedItem;
+
+      return {
+        error: 0,
+        msg: '更新成功',
+        data: updatedItem,
+      };
+    } else {
+      return {
+        error: 1,
+        msg: '记录不存在',
+        data: null,
+      };
+    }
+  },
+
+  // 删除记录
+  delete: (id) => {
+    const index = groupMockData.list.findIndex((item) => item.id == id);
+    if (index !== -1) {
+      const deletedItem = groupMockData.list[index];
+      groupMockData.list.splice(index, 1);
+
+      // 更新状态统计
+      groupMockData.statusCount.all--;
+      if (deletedItem.status === 'pending') {
+        groupMockData.statusCount.pending--;
+      } else if (deletedItem.status === 'active') {
+        groupMockData.statusCount.active--;
+      } else if (deletedItem.status === 'ended') {
+        groupMockData.statusCount.ended--;
+      }
+
+      return {
+        error: 0,
+        msg: '删除成功',
+        data: null,
+      };
+    } else {
+      return {
+        error: 1,
+        msg: '记录不存在',
+        data: null,
+      };
+    }
+  },
+
+  // 批量删除
+  batchDelete: (ids) => {
+    let deletedCount = 0;
+    ids.forEach((id) => {
+      const index = groupMockData.list.findIndex((item) => item.id == id);
+      if (index !== -1) {
+        const deletedItem = groupMockData.list[index];
+        groupMockData.list.splice(index, 1);
+        deletedCount++;
+
+        // 更新状态统计
+        groupMockData.statusCount.all--;
+        if (deletedItem.status === 'pending') {
+          groupMockData.statusCount.pending--;
+        } else if (deletedItem.status === 'active') {
+          groupMockData.statusCount.active--;
+        } else if (deletedItem.status === 'ended') {
+          groupMockData.statusCount.ended--;
+        }
+      }
+    });
+
+    return {
+      error: 0,
+      msg: `成功删除${deletedCount}条记录`,
+      data: null,
+    };
+  },
+
+  // 更新活动商品
+  updateGoods: (activityId, goodsIds, goodsList) => {
+    const index = groupMockData.list.findIndex((item) => item.id == activityId);
+    if (index !== -1) {
+      const updatedItem = {
+        ...groupMockData.list[index],
+        goods_ids: goodsIds,
+        goods_list: goodsList,
+        goods_count: goodsList.length,
+        updated_at: new Date().toISOString().slice(0, 19).replace('T', ' '),
+      };
+
+      groupMockData.list[index] = updatedItem;
+
+      return {
+        error: 0,
+        msg: `成功设置${goodsList.length}个商品`,
+        data: updatedItem,
+      };
+    } else {
+      return {
+        error: 1,
+        msg: '活动不存在',
+        data: null,
+      };
+    }
+  },
+};

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott