Răsfoiți Sursa

feat: 完善佣金管理 各模块导出功能

叶静 2 săptămâni în urmă
părinte
comite
2ab20dd252

+ 1 - 4
src/app/admin/store/account.js

@@ -78,10 +78,7 @@ const account = defineStore({
      * @description 获取用户菜单权限规则
      */
     async getRules() {
-      /* 测试 */
-      const { code, data } = await localAdmin.account.rules();
-      /* end */
-      // const { code, data } = await admin.account.queryMenu();
+      const { code, data } = await admin.account.queryMenu();
       if (code == 200) {
         this.menuRules = data; // 设置菜单规则
         // this.pageRules = data.permission; // 设置页面权限

+ 54 - 26
src/app/shop/admin/finance/commission/detail.vue

@@ -7,28 +7,28 @@
         <el-row :gutter="20">
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">佣金单号:</span>
-              <span class="value">{{ commissionDetail.commission_no || '--' }}</span>
+              <span class="label">交易号:</span>
+              <span class="value">{{ commissionDetail.tranNo || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">佣金状态:</span>
+              <span class="label">状态:</span>
               <el-tag :type="getStatusType(commissionDetail.status)">
-                {{ commissionDetail.status_text || '--' }}
+                {{ getStatusText(commissionDetail.status) }}
               </el-tag>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
               <span class="label">佣金类型:</span>
-              <span class="value">{{ commissionDetail.type_text || '--' }}</span>
+              <span class="value">{{ getBizTypeText(commissionDetail.bizType) }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
               <span class="label">用户名:</span>
-              <span class="value">{{ commissionDetail.username || '--' }}</span>
+              <span class="value">{{ commissionDetail.name || '--' }}</span>
             </div>
           </el-col>
         </el-row>
@@ -36,51 +36,51 @@
           <el-col :span="6">
             <div class="info-item">
               <span class="label">手机号:</span>
-              <span class="value">{{ commissionDetail.phone || '--' }}</span>
+              <span class="value">{{ commissionDetail.phoneNo || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">佣金金额:</span>
+              <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>
+              <span class="label">余额:</span>
+              <span class="value">৳{{ commissionDetail.balance || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
               <span class="label">订单号:</span>
-              <span class="value">{{ commissionDetail.order_no || '--' }}</span>
+              <span class="value">{{ commissionDetail.orderNo || '--' }}</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>
+              <span class="label">创建时间:</span>
+              <span class="value">{{ commissionDetail.createTime || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">佣金余额:</span>
-              <span class="value">৳{{ commissionDetail.commission_balance || '--' }}</span>
+              <span class="label">交易时间:</span>
+              <span class="value">{{ commissionDetail.transTime || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">累计佣金:</span>
-              <span class="value">৳{{ commissionDetail.total_commission || '--' }}</span>
+              <span class="label">结算时间:</span>
+              <span class="value">{{ commissionDetail.settleTime || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">发放时间:</span>
-              <span class="value">{{ commissionDetail.create_time || '--' }}</span>
+              <span class="label">备注:</span>
+              <span class="value">{{ commissionDetail.memo || '--' }}</span>
             </div>
           </el-col>
         </el-row>
@@ -138,7 +138,7 @@
 
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { commissionMockData } from '@/sheep/mock/commission';
+  import { api } from '../finance.service';
   import { usePagination } from '@/sheep/hooks';
 
   const props = defineProps(['modal']);
@@ -159,22 +159,50 @@
   // 获取状态类型
   function getStatusType(status) {
     const statusMap = {
-      processing: 'warning',
-      settled: 'success',
-      unsettled: 'danger',
+      1: 'warning', // 未结算
+      2: 'success', // 已结算
     };
     return statusMap[status] || 'info';
   }
 
+  // 获取状态文本
+  function getStatusText(status) {
+    const statusMap = {
+      1: '未结算',
+      2: '已结算',
+    };
+    return statusMap[status] || '未知';
+  }
+
+  // 获取佣金类型文本
+  function getBizTypeText(bizType) {
+    const typeMap = {
+      1001: '充值',
+      2001: '提现',
+      3001: '开团支付',
+      3002: '参团支付',
+      4001: '未成团退款',
+      4002: '成团退款',
+      5001: '开团红包收益',
+      5002: '参团红包收益',
+      5003: '签到红包收益',
+      5004: '下级红包佣金',
+      5005: '下下级红包佣金',
+      6001: '充值返点',
+      7001: '提现手续费',
+    };
+    return typeMap[bizType] || '未知类型';
+  }
+
   // 获取佣金详情
   async function getCommissionDetail() {
     if (!props.modal?.params?.id) return;
 
     loading.value = true;
     try {
-      const result = commissionMockData.getDetail(props.modal.params.id);
-      if (result.code == '200') {
-        commissionDetail.value = result.data;
+      const { code, data } = await api.commission.detail(props.modal.params.id);
+      if (code == '200') {
+        commissionDetail.value = data;
       }
     } catch (error) {
       console.error('获取佣金详情失败:', error);

+ 159 - 93
src/app/shop/admin/finance/commission/index.vue

@@ -6,8 +6,8 @@
         <sa-search-simple
           :searchFields="searchFields"
           :defaultValues="defaultSearchValues"
-          @search="(val) => getData(1, val)"
-          @reset="getData(1)"
+          @search="handleSearch"
+          @reset="handleReset"
         >
         </sa-search-simple>
       </div>
@@ -15,7 +15,15 @@
         <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>
+          <el-button
+            icon="Download"
+            type="primary"
+            :loading="exportLoading"
+            :disabled="exportLoading"
+            @click="exportRecords"
+          >
+            {{ exportLoading ? '导出中...' : '导出记录' }}
+          </el-button>
         </div>
       </div>
     </el-header>
@@ -26,47 +34,51 @@
           class="sa-table"
           :data="table.data"
           @sort-change="fieldFilter"
-          @row-dblclick="viewDetail"
           row-key="id"
           stripe
         >
           <template #empty>
             <sa-empty />
           </template>
-          <el-table-column prop="commission_no" label="佣金单号" min-width="180" sortable="custom">
+          <el-table-column prop="tranNo" label="佣金单号" min-width="180" sortable="custom">
             <template #default="scope">
-              <span class="sa-table-line-1">{{ scope.row.commission_no || '-' }}</span>
+              <span class="sa-table-line-1">{{ scope.row.tranNo || '-' }}</span>
             </template>
           </el-table-column>
           <el-table-column label="用户名" min-width="120">
             <template #default="scope">
               <el-link type="primary" :underline="true" @click="viewUserDetail(scope.row)">
-                {{ scope.row.username || '-' }}
+                {{ scope.row.name || '-' }}
               </el-link>
             </template>
           </el-table-column>
-          <el-table-column prop="phone" label="手机号" min-width="130" align="center">
+          <el-table-column prop="phoneNo" label="手机号" min-width="130" align="center">
             <template #default="scope">
-              {{ scope.row.phone || '-' }}
+              {{ scope.row.phoneNo || '-' }}
             </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="150">
+            <template #default="scope">
+              <span class="sa-table-line-1">{{ getBizTypeText(scope.row.bizType) }}</span>
+            </template>
+          </el-table-column>
           <el-table-column label="佣金说明" min-width="200">
             <template #default="scope">
-              <span class="sa-table-line-1">{{ scope.row.description || '-' }}</span>
+              <span class="sa-table-line-1">{{ scope.row.memo || '-' }}</span>
             </template>
           </el-table-column>
           <el-table-column label="订单号" min-width="150">
             <template #default="scope">
               <el-link
-                v-if="scope.row.order_no"
+                v-if="scope.row.orderNo"
                 type="primary"
                 :underline="true"
                 @click="viewOrderDetail(scope.row)"
               >
-                {{ scope.row.order_no }}
+                {{ scope.row.orderNo }}
               </el-link>
               <span v-else>--</span>
             </template>
@@ -74,23 +86,18 @@
           <el-table-column label="状态" min-width="100" align="center">
             <template #default="scope">
               <el-tag :type="getStatusType(scope.row.status)">
-                {{ scope.row.status_text || '-' }}
+                {{ getStatusText(scope.row.status) }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column prop="create_time" label="发放时间" min-width="160" sortable="custom">
+          <el-table-column prop="createTime" label="发放时间" min-width="160" sortable="custom">
             <template #default="scope">
-              {{ scope.row.create_time || '-' }}
+              {{ scope.row.createTime || '-' }}
             </template>
           </el-table-column>
           <el-table-column label="到账时间" min-width="160">
             <template #default="scope">
-              {{ scope.row.settle_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>
+              {{ scope.row.settleTime || '--' }}
             </template>
           </el-table-column>
         </el-table>
@@ -107,61 +114,53 @@
   import { onMounted, reactive, ref } from 'vue';
   import { ElMessage } from 'element-plus';
   import { useModal, usePagination } from '@/sheep/hooks';
-  import { commissionMockData } from '@/sheep/mock/commission';
+  import { api } from '../finance.service';
+  import { request } from '@/sheep/request';
   import UserDetail from '../../user/list/detail.vue';
-  import CommissionDetail from './detail.vue';
   import OrderDetail from '../../order/order/detail.vue';
   const { pageData } = usePagination();
 
   // 搜索字段配置
   const searchFields = reactive({
-    username: {
+    name: {
       type: 'input',
       label: '用户名',
       placeholder: '请输入用户名',
       width: 150,
     },
-    phone: {
+    phoneNo: {
       type: 'input',
       label: '手机号',
       placeholder: '请输入手机号',
       width: 150,
     },
-    commission_no: {
+    orderNo: {
       type: 'input',
-      label: '佣金单号',
-      placeholder: '请输入佣金单号',
+      label: '单号',
+      placeholder: '请输入单号',
       width: 180,
     },
     status: {
       type: 'select',
-      label: '佣金状态',
+      label: '状态',
       placeholder: '请选择状态',
       width: 120,
       options: [
-        { label: '全部', value: 'all' },
-        { label: '处理中', value: 'processing' },
-        { label: '已到账', value: 'settled' },
-        { label: '未到账', value: 'unsettled' },
+        { label: '全部', value: '' },
+        { label: '未结算', value: 1 },
+        { label: '已结算', value: 2 },
       ],
     },
-    type: {
+    bizType: {
       type: 'select',
       label: '佣金类型',
       placeholder: '请选择类型',
       width: 150,
-      options: [
-        { 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' },
-      ],
+      options: [], // 动态加载
     },
     date_range: {
       type: 'daterange',
-      label: '时间',
+      label: '时间范围',
       placeholder: '请选择时间范围',
       width: 240,
     },
@@ -169,13 +168,36 @@
 
   // 默认搜索值
   const defaultSearchValues = reactive({
-    username: '',
-    phone: '',
-    commission_no: '',
-    status: 'all',
-    type: '',
+    name: '',
+    phoneNo: '',
+    orderNo: '',
+    status: '',
+    bizType: '',
     date_range: [],
   });
+
+  // 当前搜索参数状态
+  const currentSearchParams = ref({});
+
+  // 导出loading状态
+  const exportLoading = ref(false);
+
+  // 搜索处理函数
+  function handleSearch(searchParams) {
+    // 保存当前搜索参数
+    currentSearchParams.value = { ...searchParams };
+    // 执行搜索
+    getData(1, searchParams);
+  }
+
+  // 重置搜索参数处理函数
+  function handleReset() {
+    // 清空当前搜索参数
+    currentSearchParams.value = {};
+    // 执行重置
+    getData(1);
+  }
+
   // 列表
   const table = reactive({
     data: [],
@@ -188,41 +210,93 @@
   // 获取状态类型
   function getStatusType(status) {
     const statusMap = {
-      processing: 'warning',
-      settled: 'success',
-      unsettled: 'danger',
+      1: 'warning', // 未结算
+      2: 'success', // 已结算
     };
     return statusMap[status] || 'info';
   }
 
+  // 获取状态文本
+  function getStatusText(status) {
+    const statusMap = {
+      1: '未结算',
+      2: '已结算',
+    };
+    return statusMap[status] || '未知';
+  }
+
+  // 获取佣金类型文本
+  function getBizTypeText(bizType) {
+    const typeMap = {
+      1001: '充值',
+      2001: '提现',
+      3001: '开团支付',
+      3002: '参团支付',
+      4001: '未成团退款',
+      4002: '成团退款',
+      5001: '开团红包收益',
+      5002: '参团红包收益',
+      5003: '签到红包收益',
+      5004: '下级红包佣金',
+      5005: '下下级红包佣金',
+      6001: '充值返点',
+      7001: '提现手续费',
+    };
+    return typeMap[bizType] || '未知类型';
+  }
+
+  // 获取佣金类型枚举
+  async function getCommissionTypes() {
+    try {
+      const { code, data } = await request({
+        url: 'cif/api/user/getEnum',
+        method: 'GET',
+        params: { id: 5 },
+      });
+
+      if (code === '200' && data) {
+        const options = [{ label: '全部', value: '' }];
+        data.forEach((item) => {
+          options.push({
+            label: item.name,
+            value: item.code,
+          });
+        });
+        searchFields.bizType.options = options;
+      }
+    } catch (error) {
+      console.error('获取佣金类型枚举失败:', error);
+    }
+  }
+
   // 获取数据
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
 
     try {
-      // 处理时间范围参数
+      // 构建请求参数
       let params = {
         page: pageData.page,
         size: pageData.size,
-        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];
+        params.startTime = searchParams.date_range[0];
+        params.endTime = searchParams.date_range[1];
         delete params.date_range;
       }
 
-      const result = commissionMockData.getList(params);
-      if (result.code == '200') {
-        table.data = result.data.data;
-        pageData.page = result.data.current_page;
-        pageData.size = result.data.per_page;
-        pageData.total = result.data.total;
+      // 调用真实API
+      const { code, data } = await api.commission.list(params, false);
+
+      if (code == '200') {
+        table.data = data.list || [];
+        pageData.page = data.pageNum || 1;
+        pageData.size = data.pageSize || 10;
+        pageData.total = data.total || 0;
       }
     } catch (error) {
       console.error('获取数据失败:', error);
@@ -237,23 +311,6 @@
     table.sort = prop;
     getData();
   }
-  // 查看详情
-  function viewDetail(row) {
-    useModal(
-      CommissionDetail,
-      {
-        title: '佣金详情',
-        type: 'view',
-        id: row.id,
-        size: '80%',
-      },
-      {
-        confirm: () => {
-          // 详情页面通常不需要确认回调
-        },
-      },
-    );
-  }
 
   // 查看用户详情
   function viewUserDetail(row) {
@@ -262,7 +319,7 @@
       {
         title: '用户详情',
         type: 'view',
-        id: row.user_id,
+        id: row.userId,
       },
       {
         confirm: () => {
@@ -274,13 +331,13 @@
 
   // 查看订单详情
   function viewOrderDetail(row) {
-    if (!row.order_no) return;
+    if (!row.orderNo) return;
     useModal(
       OrderDetail,
       {
         title: '订单详情',
         type: 'view',
-        id: row.order_no,
+        id: row.orderNo,
       },
       {
         confirm: () => {
@@ -291,23 +348,32 @@
   }
 
   // 导出记录
-  function exportRecords() {
+  async function exportRecords() {
+    if (exportLoading.value) return; // 防止重复点击
+
+    exportLoading.value = true;
     try {
-      const result = commissionMockData.export({});
-      if (result.code == '200') {
-        ElMessage.success('导出成功');
-        // 这里可以触发文件下载
-        console.log('导出文件:', result.data);
-      } else {
-        ElMessage.error(result.msg);
+      // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
+      const exportParams = {
+        ...currentSearchParams.value, // 当前搜索参数
+      };
+
+      // 处理日期范围搜索(与 getData 函数保持一致)
+      if (exportParams.date_range && exportParams.date_range.length === 2) {
+        exportParams.startTime = exportParams.date_range[0];
+        exportParams.endTime = exportParams.date_range[1];
+        delete exportParams.date_range;
       }
-    } catch (error) {
-      console.error('导出失败:', error);
-      ElMessage.error('导出失败');
+
+      // 调用导出API,所有下载逻辑都在REPORT函数中处理
+      await api.commission.report(exportParams, '佣金记录');
+    } finally {
+      exportLoading.value = false;
     }
   }
 
   onMounted(() => {
+    getCommissionTypes(); // 获取佣金类型枚举
     getData();
   });
 </script>

+ 3 - 3
src/app/shop/admin/finance/finance.service.js

@@ -143,12 +143,12 @@ const route = {
 const api = {
   // 佣金相关 API
   commission: {
-    ...CRUD('/cif/red/envelope'),
+    ...CRUD('/cif/red/envelope', ['list', 'detail', 'report']),
   },
 
   // 充值相关 API
   recharge: {
-    ...CRUD('cif/recharge/record'),
+    ...CRUD('cif/recharge/record', ['list', 'detail', 'report']),
     recordDetail: (id) =>
       request({
         url: `cif/recharge/record/detail`,
@@ -170,7 +170,7 @@ const api = {
 
   // 提现相关 API
   withdraw: {
-    ...CRUD('/cif/withdraw/record'),
+    ...CRUD('/cif/withdraw/record', ['list', 'detail', 'report']),
     withdrawDetail: (id) =>
       request({
         url: `cif/withdraw/record/detail`,

+ 36 - 13
src/app/shop/admin/finance/recharge/index.vue

@@ -23,7 +23,15 @@
         <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>
+          <el-button
+            icon="Download"
+            type="primary"
+            :loading="exportLoading"
+            :disabled="exportLoading"
+            @click="exportRecords"
+          >
+            {{ exportLoading ? '导出中...' : '导出记录' }}
+          </el-button>
         </div>
       </div>
     </el-header>
@@ -34,7 +42,6 @@
           class="sa-table"
           :data="table.data"
           @sort-change="fieldFilter"
-          @row-dblclick="viewDetail"
           row-key="id"
           stripe
         >
@@ -177,6 +184,9 @@
   // 当前搜索条件
   const currentSearchParams = ref({});
 
+  // 导出loading状态
+  const exportLoading = ref(false);
+
   // 列表
   const table = reactive({
     data: [],
@@ -315,19 +325,32 @@
   }
 
   // 导出记录
-  function exportRecords() {
+  async function exportRecords() {
+    if (exportLoading.value) return; // 防止重复点击
+
+    exportLoading.value = true;
     try {
-      const result = rechargeMockData.export({});
-      if (result.code == '200') {
-        ElMessage.success('导出成功');
-        // 这里可以触发文件下载
-        console.log('导出文件:', result.data);
-      } else {
-        ElMessage.error(result.msg);
+      // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
+      const exportParams = {
+        ...currentSearchParams.value, // 当前搜索参数
+      };
+
+      // 添加当前状态筛选
+      if (currentStatus.value !== 'all') {
+        exportParams.status = parseInt(currentStatus.value);
       }
-    } catch (error) {
-      console.error('导出失败:', error);
-      ElMessage.error('导出失败');
+
+      // 处理时间范围搜索(与 getData 函数保持一致)
+      if (exportParams.date_range && exportParams.date_range.length === 2) {
+        exportParams.startTime = exportParams.date_range[0];
+        exportParams.endTime = exportParams.date_range[1];
+        delete exportParams.date_range;
+      }
+
+      // 调用导出API,所有下载逻辑都在REPORT函数中处理
+      await api.recharge.report(exportParams, '充值记录');
+    } finally {
+      exportLoading.value = false;
     }
   }
 

+ 36 - 12
src/app/shop/admin/finance/withdraw/index.vue

@@ -25,7 +25,15 @@
         <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>
+          <el-button
+            icon="Download"
+            type="primary"
+            :loading="exportLoading"
+            :disabled="exportLoading"
+            @click="exportRecords"
+          >
+            {{ exportLoading ? '导出中...' : '导出记录' }}
+          </el-button>
         </div>
       </div>
     </el-header>
@@ -144,6 +152,9 @@
   // 当前搜索条件
   const currentSearchParams = ref({});
 
+  // 导出loading状态
+  const exportLoading = ref(false);
+
   // 搜索字段配置
   const searchFields = reactive({
     userName: {
@@ -344,19 +355,32 @@
   }
 
   // 导出记录
-  function exportRecords() {
+  async function exportRecords() {
+    if (exportLoading.value) return; // 防止重复点击
+
+    exportLoading.value = true;
     try {
-      const result = withdrawMockData.export({});
-      if (result.code == '200') {
-        ElMessage.success('导出成功');
-        // 这里可以触发文件下载
-        console.log('导出文件:', result.data);
-      } else {
-        ElMessage.error(result.msg);
+      // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
+      const exportParams = {
+        ...currentSearchParams.value, // 当前搜索参数
+      };
+
+      // 添加当前状态筛选
+      if (currentStatus.value !== 'all') {
+        exportParams.status = parseInt(currentStatus.value);
       }
-    } catch (error) {
-      console.error('导出失败:', error);
-      ElMessage.error('导出失败');
+
+      // 处理时间范围搜索(与 getData 函数保持一致)
+      if (exportParams.date_range && exportParams.date_range.length === 2) {
+        exportParams.startTime = exportParams.date_range[0];
+        exportParams.endTime = exportParams.date_range[1];
+        delete exportParams.date_range;
+      }
+
+      // 调用导出API,所有下载逻辑都在REPORT函数中处理
+      await api.withdraw.report(exportParams, '提现记录');
+    } finally {
+      exportLoading.value = false;
     }
   }
 

+ 78 - 0
src/app/shop/admin/order/order.service.js

@@ -1,5 +1,83 @@
 import Content from '@/sheep/layouts/content.vue';
 import { request } from '@/sheep/request';
+
+// 订单状态配置
+export const ORDER_STATUS = {
+  // 支付状态
+  PAY_STATUS: {
+    0: { text: '待付款', type: 'warning' },
+    1: { text: '已付款', type: 'success' },
+  },
+
+  // 订单状态
+  ORDER_STATUS: {
+    0: { text: '待发货', type: 'warning' },
+    1: { text: '待收货', type: 'primary' },
+    3: { text: '已完成', type: 'success' },
+    4: { text: '已关闭', type: 'info' },
+    5: { text: '已取消', type: 'danger' },
+  },
+
+  // 退款状态
+  REFUND_STATUS: {
+    0: { text: '未退款', type: 'info' },
+    1: { text: '申请中', type: 'warning' },
+    2: { text: '已退款', type: 'success' },
+    3: { text: '退款中', type: 'primary' },
+  },
+
+  // 拼团状态
+  PINK_STATUS: {
+    0: { text: '未开团', type: 'info' },
+    1: { text: '开团成功', type: 'success' },
+    2: { text: '开团失败', type: 'danger' },
+  },
+
+  // 中奖状态
+  WIN_STATUS: {
+    0: { text: '未中奖', type: 'info' },
+    1: { text: '已中奖', type: 'success' },
+  },
+
+  // 支付方式
+  PAY_TYPE: {
+    yue: '账户余额',
+    alipay: '支付宝',
+    balance: '余额支付',
+    offline: '线下支付',
+    wechat: '微信支付',
+  },
+};
+
+// 获取支付状态信息
+export function getPayStatusInfo(paid) {
+  return ORDER_STATUS.PAY_STATUS[paid] || { text: '未知', type: 'info' };
+}
+
+// 获取订单状态信息
+export function getOrderStatusInfo(status) {
+  return ORDER_STATUS.ORDER_STATUS[status] || { text: '未知', type: 'info' };
+}
+
+// 获取退款状态信息
+export function getRefundStatusInfo(refundStatus) {
+  return ORDER_STATUS.REFUND_STATUS[refundStatus] || { text: '未知', type: 'info' };
+}
+
+// 获取拼团状态信息
+export function getPinkStatusInfo(pinkStatus) {
+  return ORDER_STATUS.PINK_STATUS[pinkStatus] || { text: '未知', type: 'info' };
+}
+
+// 获取中奖状态信息
+export function getWinStatusInfo(winStatus) {
+  return ORDER_STATUS.WIN_STATUS[winStatus] || { text: '未知', type: 'info' };
+}
+
+// 获取支付方式文本
+export function getPayTypeText(payType) {
+  return ORDER_STATUS.PAY_TYPE[payType] || payType || '--';
+}
 import { SELECT, CRUD } from '@/sheep/request/crud';
 import { EXPORT } from '@/sheep/request/export';
 

+ 140 - 23
src/app/shop/admin/order/order/detail.vue

@@ -2,15 +2,15 @@
   <el-container class="order-detail">
     <el-main>
       <!-- 订单轨迹 -->
-      <div class="order-track sa-m-b-26">
+      <div class="order-track sa-m-b-46">
         <h3 class="sa-m-b-20">订单轨迹</h3>
         <el-steps :active="getActiveStep()" align-center>
           <el-step
-            v-for="(step, index) in state.orderDetail.status_steps"
+            v-for="(step, index) in orderSteps"
             :key="index"
-            :title="step.status_text"
-            :description="step.time"
-            :status="step.completed ? 'finish' : 'wait'"
+            :title="step.title"
+            :description="step.description"
+            :status="step.status"
           />
         </el-steps>
       </div>
@@ -25,68 +25,98 @@
               <span class="value">{{ state.orderDetail.orderId || '--' }}</span>
             </div>
           </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">支付状态:</span>
+              <el-tag :type="getPayStatusType(state.orderDetail.paid)">
+                {{ getPayStatusText(state.orderDetail.paid) }}
+              </el-tag>
+            </div>
+          </el-col>
           <el-col :span="6">
             <div class="info-item">
               <span class="label">订单状态:</span>
-              <span class="value">{{ state.orderDetail.status_text || '--' }}</span>
+              <el-tag :type="getOrderStatusType(state.orderDetail.status)">
+                {{ getOrderStatusText(state.orderDetail.status) }}
+              </el-tag>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <span class="label">拼团状态:</span>
+              <el-tag :type="getPinkStatusType(state.orderDetail.pinkStatus)">
+                {{ getPinkStatusText(state.orderDetail.pinkStatus) }}
+              </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>
+              <el-tag :type="getRefundStatusType(state.orderDetail.refundStatus)">
+                {{ getRefundStatusText(state.orderDetail.refundStatus) }}
+              </el-tag>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
               <span class="label">全部金额:</span>
-              <span class="value">৳{{ state.orderDetail.totalPrice || '--' }}</span>
+              <span class="value">৳{{ state.orderDetail.totalPrice || 0 }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
               <span class="label">中奖状态:</span>
-              <span class="value">{{ state.orderDetail.lottery_status || '--' }}</span>
+              <el-tag :type="getWinStatusType(state.orderDetail.winStatus)">
+                {{ getWinStatusText(state.orderDetail.winStatus) }}
+              </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">{{ state.orderDetail.user_nickname || '--' }}</span>
+              <span class="value">{{ state.orderDetail.realName || '--' }}</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">{{ state.orderDetail.payment_method || '--' }}</span>
+              <span class="value">{{ getPayTypeText(state.orderDetail.payType) }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
               <span class="label">优惠金额:</span>
-              <span class="value">৳{{ state.orderDetail.discount_amount || '--' }}</span>
+              <span class="value">৳{{ state.orderDetail.deductionPrice || 0 }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
               <span class="label">奖励佣金:</span>
-              <span class="value">৳{{ state.orderDetail.commission_amount || '--' }}</span>
+              <span class="value">৳{{ state.orderDetail.brokerage || 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">{{ state.orderDetail.mobile || '--' }}</span>
+              <span class="value">{{ state.orderDetail.userPhone || '--' }}</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">৳{{ state.orderDetail.pay_amount || '--' }}</span>
+              <span class="value">৳{{ state.orderDetail.totalPrice || 0 }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
               <span class="label">实付金额:</span>
-              <span class="value">৳{{ state.orderDetail.actual_amount || '--' }}</span>
+              <span class="value">৳{{ state.orderDetail.payPrice || 0 }}</span>
             </div>
           </el-col>
         </el-row>
@@ -198,9 +228,17 @@
   </el-container>
 </template>
 <script setup>
-  import { onMounted, reactive } from 'vue';
+  import { onMounted, reactive, computed } from 'vue';
   import { ElMessage } from 'element-plus';
-  import { api } from '../order.service';
+  import {
+    api,
+    getPayStatusInfo,
+    getOrderStatusInfo,
+    getRefundStatusInfo,
+    getPinkStatusInfo,
+    getWinStatusInfo,
+    getPayTypeText,
+  } from '../order.service';
   import { useModal } from '@/sheep/hooks';
   import OrderDispatch from './dispatch.vue';
   import OrderRefund from './refund.vue';
@@ -212,6 +250,43 @@
     orderDetail: {}, // 订单详情
   });
 
+  // 订单步骤配置
+  const stepConfig = [
+    { key: 'create_order', title: '提交订单', waitText: '未提交' },
+    { key: 'payment', title: '付款', waitText: '未支付' },
+    { key: 'group_start', title: '开团', waitText: '未开团' },
+    { key: 'platform_ship', title: '平台发货', waitText: '未发货' },
+    { key: 'user_receive', title: '用户收货', waitText: '未收货' },
+  ];
+
+  // 计算订单步骤
+  const orderSteps = computed(() => {
+    const trackData = state.orderDetail.orderStatusVO || [];
+    const steps = [];
+
+    stepConfig.forEach((config, index) => {
+      const trackItem = trackData[index];
+
+      if (trackItem) {
+        // 有对应的轨迹数据,显示为已完成
+        steps.push({
+          title: config.title,
+          description: trackItem.createTime,
+          status: 'finish',
+        });
+      } else {
+        // 没有对应的轨迹数据,显示为等待状态
+        steps.push({
+          title: config.title,
+          description: config.waitText,
+          status: 'wait',
+        });
+      }
+    });
+
+    return steps;
+  });
+
   // 备注表单
   const memoForm = reactive({
     flag: false,
@@ -228,8 +303,50 @@
 
   // 获取激活步骤
   function getActiveStep() {
-    if (!state.orderDetail.status_steps) return 0;
-    return state.orderDetail.status_steps.filter((step) => step.completed).length - 1;
+    const trackData = state.orderDetail.orderStatusVO || [];
+    // 返回已完成步骤的数量减1(因为 el-steps 的 active 是从0开始的索引)
+    return Math.max(0, trackData.length - 1);
+  }
+
+  // 使用公共配置的状态处理函数
+  function getPayStatusType(paid) {
+    return getPayStatusInfo(paid).type;
+  }
+
+  function getPayStatusText(paid) {
+    return getPayStatusInfo(paid).text;
+  }
+
+  function getOrderStatusType(status) {
+    return getOrderStatusInfo(status).type;
+  }
+
+  function getOrderStatusText(status) {
+    return getOrderStatusInfo(status).text;
+  }
+
+  function getRefundStatusType(refundStatus) {
+    return getRefundStatusInfo(refundStatus).type;
+  }
+
+  function getRefundStatusText(refundStatus) {
+    return getRefundStatusInfo(refundStatus).text;
+  }
+
+  function getPinkStatusType(pinkStatus) {
+    return getPinkStatusInfo(pinkStatus).type;
+  }
+
+  function getPinkStatusText(pinkStatus) {
+    return getPinkStatusInfo(pinkStatus).text;
+  }
+
+  function getWinStatusType(winStatus) {
+    return getWinStatusInfo(winStatus).type;
+  }
+
+  function getWinStatusText(winStatus) {
+    return getWinStatusInfo(winStatus).text;
   }
 
   // 发货

+ 30 - 58
src/app/shop/admin/order/order/index.vue

@@ -124,7 +124,7 @@
               <el-table-column prop="realName" label="用户名" min-width="120" align="center">
                 <template #default="scope">
                   <el-link type="primary" @click="openUserDetail(scope.row.uid)">
-                    {{ scope.row.realName || '-' }}
+                    {{ scope.row.realName || '无昵称' }}
                   </el-link>
                 </template>
               </el-table-column>
@@ -226,7 +226,14 @@
 </script>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../order.service';
+  import {
+    api,
+    getPayStatusInfo,
+    getOrderStatusInfo,
+    getRefundStatusInfo,
+    getPinkStatusInfo,
+    getPayTypeText,
+  } from '../order.service';
   import { useModal, usePagination } from '@/sheep/hooks';
   import OrderDispatch from './dispatch.vue';
   import OrderBatchDispatch from './batchDispatch.vue';
@@ -293,72 +300,37 @@
     getData(1, statusParams);
   };
 
-  // 获取订单状态文本
+  // 使用公共配置的状态处理函数
   const getOrderStatusText = (status) => {
-    switch (status) {
-      case 0:
-        return '待发货';
-      case 1:
-        return '待收货';
-      case 3:
-        return '已完成';
-      case 4:
-        return '已关闭';
-      case 5:
-        return '已取消';
-      default:
-        return '未知';
-    }
+    return getOrderStatusInfo(status).text;
   };
 
-  // 获取订单状态类型
   const getOrderStatusType = (status) => {
-    switch (status) {
-      case 0:
-        return 'info'; // 待发货 - 蓝色
-      case 1:
-        return ''; // 待收货 - 默认色
-      case 3:
-        return 'success'; // 已完成 - 绿色
-      case 4:
-        return 'danger'; // 已关闭 - 红色
-      case 5:
-        return 'danger'; // 已取消 - 红色
-      default:
-        return '';
-    }
+    return getOrderStatusInfo(status).type;
+  };
+
+  const getPayStatusText = (paid) => {
+    return getPayStatusInfo(paid).text;
+  };
+
+  const getPayStatusType = (paid) => {
+    return getPayStatusInfo(paid).type;
   };
 
-  // 获取退款状态文本
   const getRefundStatusText = (refundStatus) => {
-    switch (refundStatus) {
-      case 0:
-        return '未退款';
-      case 1:
-        return '申请中';
-      case 2:
-        return '已退款';
-      case 3:
-        return '退款中';
-      default:
-        return '未退款';
-    }
+    return getRefundStatusInfo(refundStatus).text;
   };
 
-  // 获取退款状态类型
   const getRefundStatusType = (refundStatus) => {
-    switch (refundStatus) {
-      case 0:
-        return ''; // 未退款 - 默认色
-      case 1:
-        return 'warning'; // 申请中 - 橙色
-      case 2:
-        return 'success'; // 已退款 - 绿色
-      case 3:
-        return 'info'; // 退款中 - 蓝色
-      default:
-        return '';
-    }
+    return getRefundStatusInfo(refundStatus).type;
+  };
+
+  const getPinkStatusText = (pinkStatus) => {
+    return getPinkStatusInfo(pinkStatus).text;
+  };
+
+  const getPinkStatusType = (pinkStatus) => {
+    return getPinkStatusInfo(pinkStatus).type;
   };
 
   const loading = ref(true);

+ 38 - 9
src/app/shop/admin/user/list/edit.vue

@@ -41,13 +41,38 @@
           <el-input v-model="form.model.bankAccount" placeholder="请输入收款账户"></el-input>
         </el-form-item>
 
-        <el-form-item label="状态" prop="status">
-          <el-radio-group v-model="form.model.status">
-            <el-radio :label="1">正常</el-radio>
-            <el-radio :label="2">禁止提现</el-radio>
-            <el-radio :label="3">禁止登录</el-radio>
-            <el-radio :label="4">禁止下单</el-radio>
-          </el-radio-group>
+        <!-- 权限设置 -->
+        <el-form-item label="登录权限" prop="hasLogin">
+          <el-switch
+            v-model="form.model.hasLogin"
+            :active-value="1"
+            :inactive-value="2"
+            active-text="允许登录"
+            inactive-text="禁止登录"
+            inline-prompt
+          />
+        </el-form-item>
+
+        <el-form-item label="下单权限" prop="hasOrder">
+          <el-switch
+            v-model="form.model.hasOrder"
+            :active-value="1"
+            :inactive-value="2"
+            active-text="允许下单"
+            inactive-text="禁止下单"
+            inline-prompt
+          />
+        </el-form-item>
+
+        <el-form-item label="提现权限" prop="hasWithdraw">
+          <el-switch
+            v-model="form.model.hasWithdraw"
+            :active-value="1"
+            :inactive-value="2"
+            active-text="允许提现"
+            inactive-text="禁止提现"
+            inline-prompt
+          />
         </el-form-item>
 
         <el-form-item label="备注" prop="memo">
@@ -89,14 +114,18 @@
       bank: '',
       bankAccountName: '',
       bankAccount: '',
-      status: 1,
+      hasLogin: 1, // 登录权限:1是 2否
+      hasOrder: 1, // 下单权限:1是 2否
+      hasWithdraw: 1, // 提现权限:1是 2否
       memo: '',
     },
     rules: {
       // nickname: [{ required: true, message: '请填写用户昵称', trigger: 'blur' }],
       phoneNo: [{ required: true, message: '请填写手机号', trigger: 'blur' }],
       // headPic: [{ required: true, message: '请上传用户头像', trigger: 'change' }],
-      status: [{ required: true, message: '请选择用户状态', trigger: 'change' }],
+      hasLogin: [{ required: true, message: '请设置登录权限', trigger: 'change' }],
+      hasOrder: [{ required: true, message: '请设置下单权限', trigger: 'change' }],
+      hasWithdraw: [{ required: true, message: '请设置提现权限', trigger: 'change' }],
       memo: [{ max: 100, message: '备注不能超过100字', trigger: 'blur' }],
     },
   });

+ 115 - 51
src/app/shop/admin/user/list/index.vue

@@ -6,8 +6,8 @@
         <sa-search-simple
           :searchFields="searchFields"
           :defaultValues="defaultSearchValues"
-          @search="(val) => getData(1, val)"
-          @reset="getData(1)"
+          @search="handleSearch"
+          @reset="handleReset"
         >
           <template #custom="{ data }">
             <el-form-item label="注册时间">
@@ -25,20 +25,22 @@
           </template>
         </sa-search-simple>
       </div>
-      <el-tabs class="sa-tabs" v-model="activeTab" @tab-change="handleTabChange">
-        <el-tab-pane label="全部" :name="0"></el-tab-pane>
-        <el-tab-pane label="正常" :name="1"></el-tab-pane>
-        <el-tab-pane label="禁止提现" :name="2"></el-tab-pane>
-        <el-tab-pane label="禁止登录" :name="3"></el-tab-pane>
-        <el-tab-pane label="禁止下单" :name="4"></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>
+          <el-button
+            icon="Download"
+            type="primary"
+            :loading="exportLoading"
+            :disabled="exportLoading"
+            @click="exportUsers"
+          >
+            {{ exportLoading ? '导出中...' : '导出数据' }}
+          </el-button>
         </div>
       </div>
     </el-header>
@@ -65,7 +67,7 @@
           <el-table-column label="用户昵称" min-width="140">
             <template #default="scope">
               <div class="user-info">
-                {{ scope.row.name || '-' }}
+                {{ scope.row.nickname || '-' }}
               </div>
             </template>
           </el-table-column>
@@ -104,10 +106,24 @@
               <span class="amount">{{ scope.row.earningsBalance || '৳0' }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="状态" min-width="120" align="center">
+          <el-table-column label="登录权限" min-width="100" align="center">
+            <template #default="scope">
+              <el-tag :type="scope.row.hasLogin === 1 ? 'success' : 'danger'" size="small">
+                {{ scope.row.hasLogin === 1 ? '允许' : '禁止' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="下单权限" min-width="100" align="center">
+            <template #default="scope">
+              <el-tag :type="scope.row.hasOrder === 1 ? 'success' : 'danger'" size="small">
+                {{ scope.row.hasOrder === 1 ? '允许' : '禁止' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="提现权限" min-width="100" align="center">
             <template #default="scope">
-              <el-tag :type="getStatusType(scope.row.status)" size="small">
-                {{ getStatusText(scope.row.status) }}
+              <el-tag :type="scope.row.hasWithdraw === 1 ? 'success' : 'danger'" size="small">
+                {{ scope.row.hasWithdraw === 1 ? '允许' : '禁止' }}
               </el-tag>
             </template>
           </el-table-column>
@@ -174,7 +190,40 @@
       type: 'input',
       label: '手机号',
       placeholder: '请输入手机号',
-      width: 200,
+      width: 150,
+    },
+    hasLogin: {
+      type: 'select',
+      label: '登录权限',
+      placeholder: '请选择登录权限',
+      width: 120,
+      options: [
+        { label: '全部', value: '' },
+        { label: '允许', value: 1 },
+        { label: '禁止', value: 2 },
+      ],
+    },
+    hasOrder: {
+      type: 'select',
+      label: '下单权限',
+      placeholder: '请选择下单权限',
+      width: 120,
+      options: [
+        { label: '全部', value: '' },
+        { label: '允许', value: 1 },
+        { label: '禁止', value: 2 },
+      ],
+    },
+    hasWithdraw: {
+      type: 'select',
+      label: '提现权限',
+      placeholder: '请选择提现权限',
+      width: 120,
+      options: [
+        { label: '全部', value: '' },
+        { label: '允许', value: 1 },
+        { label: '禁止', value: 2 },
+      ],
     },
   });
 
@@ -182,11 +231,11 @@
   const defaultSearchValues = reactive({
     name: '',
     phone: '',
-    dateRange: [],
+    hasLogin: '',
+    hasOrder: '',
+    hasWithdraw: '',
   });
 
-  // 当前激活的 Tab
-  const activeTab = ref(0);
   // 列表
   const table = reactive({
     data: [],
@@ -226,34 +275,6 @@
     loading.value = false;
   }
 
-  // Tab 切换处理
-  function handleTabChange(status) {
-    activeTab.value = status;
-    // 如果是全部(0),不传status参数;否则传递对应的状态值
-    const searchParams = status === 0 ? {} : { status };
-    getData(1, searchParams);
-  }
-
-  // 获取状态标签类型
-  function getStatusType(status) {
-    return status === 1 ? 'success' : status === 2 ? 'warning' : 'danger';
-  }
-
-  // 获取状态文本
-  function getStatusText(status) {
-    switch (status) {
-      case 1:
-        return '正常';
-      case 2:
-        return '禁止提现';
-      case 3:
-        return '禁止登录';
-      case 4:
-        return '禁止下单';
-      default:
-        return '-';
-    }
-  }
   // table 字段排序
   function fieldFilter({ prop, order }) {
     table.order = order == 'ascending' ? 'asc' : 'desc';
@@ -303,6 +324,54 @@
     );
   }
 
+  // 当前搜索参数状态
+  const currentSearchParams = ref({});
+
+  // 导出loading状态
+  const exportLoading = ref(false);
+
+  // 更新搜索参数处理函数
+  function handleSearch(searchParams) {
+    // 保存当前搜索参数
+    currentSearchParams.value = { ...searchParams };
+    // 执行搜索
+    getData(1, searchParams);
+  }
+
+  // 重置搜索参数处理函数
+  function handleReset() {
+    // 清空当前搜索参数
+    currentSearchParams.value = {};
+    // 执行重置
+    getData(1);
+  }
+
+  // 导出用户数据
+  async function exportUsers() {
+    if (exportLoading.value) return; // 防止重复点击
+
+    exportLoading.value = true;
+    try {
+      // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
+      const exportParams = {
+        type: 0, // 固定参数
+        ...currentSearchParams.value, // 当前搜索参数
+      };
+
+      // 处理时间范围搜索(与 getData 函数保持一致)
+      if (exportParams.dateRange && exportParams.dateRange.length === 2) {
+        exportParams.startTime = exportParams.dateRange[0];
+        exportParams.endTime = exportParams.dateRange[1];
+        delete exportParams.dateRange;
+      }
+
+      // 调用导出API,所有下载逻辑都在REPORT函数中处理
+      await api.list.report(exportParams, '用户数据');
+    } finally {
+      exportLoading.value = false;
+    }
+  }
+
   function editRow(row) {
     useModal(
       userEdit,
@@ -354,11 +423,6 @@
           deleteApi(ids.join(','));
         });
         break;
-      default:
-        await api.list.edit(ids.join(','), {
-          status: type,
-        });
-        getData();
     }
   }
 

+ 1 - 1
src/app/shop/admin/user/user.service.js

@@ -42,7 +42,7 @@ const api = {
   // 分离分页参数和其他参数
   // 用户列表相关 API
   list: {
-    ...CRUD('/cif/user'),
+    ...CRUD('/cif/user', ['list', 'detail', 'edit', 'delete', 'report']),
     userDetail: (userId) =>
       request({
         url: '/cif/user/detail',

+ 79 - 1
src/sheep/request/crud.js

@@ -1,4 +1,6 @@
 import { request } from './index';
+import $storage from '@/sheep/utils/storage';
+import { ElMessage } from 'element-plus';
 
 // 查看列表
 export const LIST = (url, data, pageInParams = true) => {
@@ -102,6 +104,7 @@ export const CRUD = (url, methods = ['list', 'detail', 'add', 'edit', 'delete'])
   if (methods.includes('add')) apis.add = (data) => ADD(url, data);
   if (methods.includes('edit')) apis.edit = (id, data) => EDIT(url, id, data);
   if (methods.includes('delete')) apis.delete = (id) => DELETE(url, id);
+  if (methods.includes('report')) apis.report = (params, filename) => REPORT(url, params, filename);
   return apis;
 };
 
@@ -114,4 +117,79 @@ export const RECYCLE = (url) => {
   };
 };
 
-// add, list, delete, edit, detail, select, recyclebin, restore, destroy
+// 导出报表 - 需要超级管理员权限
+export const REPORT = async (url, params, filename = '导出数据') => {
+  // 获取用户信息进行权限验证
+  const userInfo = $storage.get('userInfo');
+
+  // 检查用户是否为超级管理员
+  if (!userInfo || userInfo.roleName !== '超级管理员') {
+    ElMessage.error('只有超级管理员可以导出');
+    return Promise.reject(new Error('权限不足:只有超级管理员可以导出'));
+  }
+
+  try {
+    // 发送导出请求
+    const response = await request({
+      url: url + '/report',
+      method: 'GET',
+      params,
+      responseType: 'blob', // 用于文件下载
+      options: {
+        showSuccessMessage: false,
+      },
+    });
+    // 检查响应状态和内容类型
+    if (response.status === 200 && response.headers['content-type']?.includes('excel')) {
+      // 成功响应,处理文件下载
+      const blob = response.data; // 从响应对象中获取 Blob 数据
+      const downloadUrl = window.URL.createObjectURL(blob);
+      const link = document.createElement('a');
+      link.href = downloadUrl;
+      link.download = `${filename}_${new Date().toISOString().slice(0, 10)}.xlsx`;
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
+      window.URL.revokeObjectURL(downloadUrl);
+
+      ElMessage.success('导出成功');
+      return Promise.resolve();
+    } else {
+      // 如果不是成功的文件响应,说明返回的是错误信息
+      let errorData;
+      try {
+        // 尝试解析 Blob 中的 JSON 错误信息
+        const text = await response.data.text();
+        errorData = JSON.parse(text);
+      } catch (parseError) {
+        // 如果解析失败,使用默认错误信息
+        errorData = { code: '500', message: '导出失败' };
+      }
+
+      // 检查返回的 code
+      if (errorData.code !== '200') {
+        const errorMessage = errorData.message || '导出失败';
+        ElMessage.error(errorMessage);
+        return Promise.reject(new Error(errorMessage));
+      }
+    }
+  } catch (error) {
+    console.error('导出失败:', error);
+
+    // 如果是网络错误或其他异常
+    if (error.response) {
+      // 有响应但状态码不是 2xx
+      const errorMessage = error.response.data?.message || '服务器错误';
+      ElMessage.error(errorMessage);
+    } else if (error.message?.includes('权限不足')) {
+      // 权限错误已经在上面处理了,不需要重复提示
+    } else {
+      // 其他错误
+      ElMessage.error('导出失败,请稍后重试');
+    }
+
+    return Promise.reject(error);
+  }
+};
+
+// add, list, delete, edit, detail, select, recyclebin, restore, destroy, report