Sfoglia il codice sorgente

feat:完善用户模块

叶静 1 mese fa
parent
commit
3522d44c4a

+ 5 - 0
.promptx/memory/declarative.md

@@ -12,4 +12,9 @@
 - 2025/07/07 10:24 START
 用户要求移除登录页面所有关于微信扫码登录和验证码相关的代码,只保留账号密码登录和逻辑。我成功完成了这个任务:1. 移除了模板中的验证码输入框、微信登录按钮、扫码登录区域;2. 移除了脚本中的wechatApi导入、验证码字段、相关验证规则和所有验证码/微信相关函数;3. 移除了样式中的验证码、扫码登录相关样式;4. 保留了纯净的账号密码登录、记住我、记住用户、密码显示隐藏等核心功能。最终实现了简洁高效的登录页面。 --tags Vue3 登录页面 移除功能 微信登录 验证码 代码重构
 --tags #最佳实践 #评分:8 #有效期:长期
+- END
+
+- 2025/07/11 09:27 START
+列表页面标准样式规范:基于 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样式 列表页面 标准规范
+--tags #其他 #评分:8 #有效期:长期
 - END

+ 12 - 2
.promptx/pouch.json

@@ -1,5 +1,5 @@
 {
-  "currentState": "initialized",
+  "currentState": "memory_saved",
   "stateHistory": [
     {
       "from": "initial",
@@ -10,7 +10,17 @@
           "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-09T02:57:45.129Z"
+  "lastUpdated": "2025-07-11T01:27:25.528Z"
 }

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

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

+ 156 - 0
src/app/shop/admin/data/express/index.vue

@@ -0,0 +1,156 @@
+<template>
+  <div class="express-page">
+    <el-card>
+      <template #header>
+        <div class="card-header">
+          <span>快递公司管理</span>
+        </div>
+      </template>
+      
+      <div class="search-form">
+        <el-form :inline="true" :model="searchForm">
+          <el-form-item label="快递公司">
+            <el-input
+              v-model="searchForm.name"
+              placeholder="请输入快递公司名称"
+              clearable
+              style="width: 200px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="handleSearch">搜索</el-button>
+            <el-button @click="handleReset">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <el-table :data="tableData" style="width: 100%">
+        <el-table-column prop="code" label="快递代码" width="120" />
+        <el-table-column prop="name" label="快递公司名称" />
+        <el-table-column label="状态" width="100">
+          <template #default="scope">
+            <el-tag type="success">启用</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="150">
+          <template #default="scope">
+            <el-button type="primary" size="small" @click="handleEdit(scope.row)">
+              编辑
+            </el-button>
+            <el-button type="danger" size="small" @click="handleDelete(scope.row)">
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination">
+        <el-pagination
+          v-model:current-page="pagination.page"
+          v-model:page-size="pagination.pageSize"
+          :page-sizes="[10, 20, 50, 100]"
+          :total="pagination.total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import useExpress from './express.js';
+
+const { expressOptions, deliverCompany } = useExpress();
+
+const searchForm = reactive({
+  name: '',
+});
+
+const pagination = reactive({
+  page: 1,
+  pageSize: 10,
+  total: 0,
+});
+
+const tableData = ref([]);
+
+// 初始化数据
+const initData = () => {
+  tableData.value = expressOptions.value;
+  pagination.total = expressOptions.value.length;
+};
+
+// 搜索
+const handleSearch = () => {
+  if (searchForm.name) {
+    tableData.value = expressOptions.value.filter(item => 
+      item.name.includes(searchForm.name) || item.code.includes(searchForm.name)
+    );
+  } else {
+    tableData.value = expressOptions.value;
+  }
+  pagination.total = tableData.value.length;
+};
+
+// 重置
+const handleReset = () => {
+  searchForm.name = '';
+  initData();
+};
+
+// 编辑
+const handleEdit = (row) => {
+  ElMessage.info('编辑功能待开发');
+};
+
+// 删除
+const handleDelete = (row) => {
+  ElMessageBox.confirm('确定要删除这个快递公司吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(() => {
+    ElMessage.success('删除成功');
+  });
+};
+
+// 分页大小改变
+const handleSizeChange = (val) => {
+  pagination.pageSize = val;
+  handleCurrentChange(1);
+};
+
+// 当前页改变
+const handleCurrentChange = (val) => {
+  pagination.page = val;
+};
+
+onMounted(() => {
+  initData();
+});
+</script>
+
+<style scoped>
+.express-page {
+  padding: 20px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.search-form {
+  margin-bottom: 20px;
+}
+
+.pagination {
+  margin-top: 20px;
+  text-align: right;
+}
+</style>

+ 1 - 1
src/app/shop/admin/goods/goods/edit.vue

@@ -444,7 +444,7 @@
   const emit = defineEmits(['success', 'modalCallBack']);
 
   // 当前步骤
-  const currentStep = ref(0);
+  const currentStep = ref(1);
 
   // 是否为编辑模式
   const isEdit = computed(() => props.modal?.params?.type === 'edit');

+ 2 - 2
src/app/shop/admin/order/order/batchDispatch.vue

@@ -56,7 +56,7 @@
           </el-form>
         </div>
       </div>
-      <div class="batch-dispatch">
+      <!-- <div class="batch-dispatch">
         <div class="title">
           方法二:如使用批量发货,需确认
           <el-button class="is-link" type="primary"> &nbsp;商城配置-第三方服务&nbsp; </el-button>
@@ -70,7 +70,7 @@
             >批量发货</el-button
           >
         </div>
-      </div>
+      </div> -->
     </el-main>
   </el-container>
 </template>

+ 1 - 1
src/app/shop/admin/order/order/dispatch.vue

@@ -70,7 +70,7 @@
               <el-form-item label="发货方式">
                 <el-radio-group v-model="state.method">
                   <el-radio label="input">物流快递</el-radio>
-                  <el-radio label="api">一键发货</el-radio>
+                  <!-- <el-radio label="api">一键发货</el-radio> -->
                 </el-radio-group>
               </el-form-item>
             </el-form>

+ 0 - 1251
src/app/shop/admin/order/order/o-detail.vue

@@ -1,1251 +0,0 @@
-<template>
-  <el-container class="order-detail">
-    <el-main>
-      <div class="warm-tip sa-m-b-16">
-        温馨提示
-        <br />1、如果无法发货,请及时与买家联系并说明情况后主动退款;
-        <br />2、买家申请售后,须征得买家同意后发货,否则买家有权拒收货物;
-        <br />3、订单全部退款将会退回商品库存,并且减少实际销量,订单商品上的主动退款不会退回库存和销量
-      </div>
-      <div class="status-content sa-m-b-16">
-        <el-row :gutter="10">
-          <el-col class="sa-col-12 left" :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
-            <div class="name sa-flex sa-m-b-4">
-              <el-icon class="refresh sa-m-r-4" @click="getOrderDetail"><RefreshLeft /></el-icon>
-              {{ state.orderDetail.status_text }}
-            </div>
-            <div class="desc sa-m-b-16">
-              {{ state.orderDetail.status_desc }}
-            </div>
-            <div class="tools sa-m-b-4">
-              <template v-if="state.orderDetail.btns">
-                <el-button
-                  v-if="state.orderDetail.btns.includes('send')"
-                  v-auth="'shop.admin.order.order.dispatch'"
-                  type="primary"
-                  @click="onOpenDispatch"
-                  >立即发货</el-button
-                >
-                <el-button
-                  v-if="state.orderDetail.btns.includes('refund')"
-                  v-auth="'shop.admin.order.order.fullrefund'"
-                  @click="onOpenRefund"
-                  >全部退款</el-button
-                >
-              </template>
-            </div>
-            <div class="memo sa-flex">
-              <template v-if="memoForm.flag">
-                <el-input class="sa-w-120 sa-m-r-12" v-model="memoForm.data" size="small" />
-                <el-button class="is-link" type="primary" size="small" @click="onConfirmMemo"
-                  >确定</el-button
-                >
-                <el-button
-                  class="is-link"
-                  type="primary"
-                  size="small"
-                  @click="memoForm.flag = false"
-                  >取消</el-button
-                >
-              </template>
-              <!-- TODO:这里没有权限的时候有问题 -->
-              <template v-if="!memoForm.flag">
-                <div v-if="state.orderDetail.memo" class="sa-m-r-12">
-                  {{ state.orderDetail.memo }}
-                </div>
-                <el-button
-                  v-auth="'shop.admin.order.order.editmemo'"
-                  class="is-link"
-                  type="primary"
-                  size="small"
-                  @click="onChangeMemoFlag(state.orderDetail.memo)"
-                  >{{ state.orderDetail.memo ? '修改' : '添加' }}备注</el-button
-                >
-              </template>
-            </div>
-          </el-col>
-          <el-col class="sa-col-24 center" :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-            <status-steps :orderDetail="state.orderDetail" />
-          </el-col>
-          <el-col class="sa-col-12 right" :xs="12" :sm="12" :md="6" :lg="6" :xl="6">
-            <div class="right-item">
-              <div class="label">商品总价:</div>
-              <div class="content">¥{{ state.orderDetail.goods_amount }}</div>
-            </div>
-            <div class="right-item">
-              <div class="label">运费价格:</div>
-              <div class="content"> ¥{{ state.orderDetail.dispatch_amount }} </div>
-            </div>
-            <div
-              class="right-item right-item-discount"
-              v-if="
-                state.orderDetail.ext &&
-                (state.orderDetail.ext.promo_infos || state.orderDetail.coupon_id)
-              "
-            >
-              <div class="label">活动优惠:</div>
-              <div class="content">
-                <div
-                  class="sa-flex sa-m-b-12"
-                  v-if="Number(state.orderDetail.ext.promo_discounts.full_reduce) > 0"
-                >
-                  <div class="label">满减</div>
-                  <div class="content sa-m-l-12">
-                    -¥{{ state.orderDetail.ext.promo_discounts.full_reduce }}
-                  </div>
-                </div>
-                <div
-                  class="sa-flex sa-m-b-12"
-                  v-if="Number(state.orderDetail.ext.promo_discounts.full_discount) > 0"
-                >
-                  <div class="label">满折</div>
-                  <div class="content sa-m-l-12">
-                    -¥{{ state.orderDetail.ext.promo_discounts.full_discount }}
-                  </div>
-                </div>
-                <div
-                  class="sa-flex sa-m-b-12"
-                  v-if="Number(state.orderDetail.ext.promo_discounts.full_gift) > 0"
-                >
-                  <div class="label">满赠</div>
-                  <div class="content sa-m-l-12"></div>
-                </div>
-                <div
-                  class="sa-flex sa-m-b-12"
-                  v-if="Number(state.orderDetail.ext.promo_discounts.free_shipping) > 0"
-                >
-                  <div class="label">满包邮</div>
-                  <div class="content sa-m-l-12">
-                    -¥{{ state.orderDetail.ext.promo_discounts.free_shipping }}
-                  </div>
-                </div>
-                <div v-if="state.orderDetail.coupon_id" class="right-item">
-                  <div class="label">优惠券</div>
-                  <div class="content sa-m-l-12">
-                    -¥{{ state.orderDetail.coupon_discount_fee }}
-                  </div>
-                </div>
-              </div>
-            </div>
-            <div class="right-item">
-              <div class="label">
-                {{
-                  ['paid', 'completed'].includes(state.orderDetail.status)
-                    ? '实付金额'
-                    : '应付金额'
-                }}:
-              </div>
-              <div class="content fee-content sa-flex">
-                ¥{{ state.orderDetail.pay_fee }}
-                <s
-                  v-if="state.orderDetail.pay_fee != state.orderDetail.original_pay_fee"
-                  class="original-pay-fee sa-m-l-4"
-                >
-                  {{ state.orderDetail.original_pay_fee }}
-                </s>
-                <el-button
-                  v-if="state.orderDetail.btns && state.orderDetail.btns.includes('change_fee')"
-                  v-auth="'shop.admin.order.order.changefee'"
-                  class="is-link sa-m-l-8"
-                  type="primary"
-                  size="small"
-                  @click="onChangeFee"
-                  >改价</el-button
-                >
-              </div>
-            </div>
-          </el-col>
-        </el-row>
-        <div class="remark right-item">
-          <div class="label">买家留言:</div>
-          <div class="content">
-            {{ state.orderDetail.remark || '暂无留言' }}
-          </div>
-        </div>
-      </div>
-
-      <!-- tabs内容 -->
-      <div class="tabs-content sa-m-b-16">
-        <el-tabs v-model="state.orderTabs" @tab-change="state.changeExpressStatus = false">
-          <el-tab-pane label="订单信息" name="1">
-            <el-row class="order-content" :gutter="10">
-              <el-col class="sa-col-12" :xs="24" :sm="24" :md="12" :lg="6" :xl="6">
-                <div class="title">交易信息</div>
-                <div class="item">
-                  <div class="label">订单编号:</div>
-                  <div class="content">
-                    {{ state.orderDetail.order_sn }}
-                    <sa-svg
-                      class="copy sa-m-l-4 cursor-pointer"
-                      name="sa-copy"
-                      @click="useClip(state.orderDetail.order_sn)"
-                    ></sa-svg>
-                  </div>
-                </div>
-                <div class="item">
-                  <div class="label">订单来源:</div>
-                  <div class="content">
-                    {{ state.orderDetail.platform }}
-                  </div>
-                </div>
-                <div class="item" v-if="state.orderDetail.paid_time">
-                  <div class="label">付款时间:</div>
-                  <div class="content">{{ state.orderDetail.paid_time }}</div>
-                </div>
-              </el-col>
-              <el-col
-                v-if="state.orderDetail.user"
-                class="sa-col-12"
-                :xs="24"
-                :sm="24"
-                :md="12"
-                :lg="6"
-                :xl="6"
-              >
-                <div class="title">买家信息</div>
-                <div class="item">
-                  <div class="label">用户昵称:</div>
-                  <div class="content sa-flex">
-                    <sa-user-profile
-                      :user="state.orderDetail.user"
-                      :id="state.orderDetail.user_id"
-                      :isAvatar="false"
-                    />
-                  </div>
-                </div>
-              </el-col>
-              <el-col
-                v-if="state.orderDetail.address"
-                class="sa-col-12"
-                :xs="24"
-                :sm="24"
-                :md="12"
-                :lg="6"
-                :xl="6"
-              >
-                <div class="title sa-flex">
-                  收货信息
-                  <template v-if="state.orderDetail?.btns.includes('edit_consignee')">
-                    <template v-if="!state.editConsigneeFlag">
-                      <el-button
-                        v-auth="'shop.admin.order.order.editconsignee'"
-                        class="is-link sa-m-l-12"
-                        type="primary"
-                        size="small"
-                        @click="onChangeEditConsigneeFlag(true)"
-                        >修改</el-button
-                      >
-                      <el-button
-                        class="is-link sa-m-l-8"
-                        type="success"
-                        size="small"
-                        @click="
-                          useClip(
-                            `收货昵称:${state.orderDetail.address.consignee};联系方式:${state.orderDetail.address.mobile};
-                          收货地址:${state.orderDetail.address.province_name}${state.orderDetail.address.city_name}${state.orderDetail.address.district_name}${state.orderDetail.address.address}`,
-                          )
-                        "
-                        >复制</el-button
-                      >
-                    </template>
-                    <template v-if="state.editConsigneeFlag">
-                      <el-button
-                        class="is-link sa-m-l-12"
-                        type="danger"
-                        size="small"
-                        @click="state.editConsigneeFlag = false"
-                      >
-                        取消
-                      </el-button>
-                      <el-button
-                        class="is-link sa-m-l-8"
-                        type="primary"
-                        size="small"
-                        @click="onEditConsignee()"
-                        >确定</el-button
-                      >
-                    </template>
-                  </template>
-                </div>
-                <div class="item">
-                  <div class="label">收货昵称:</div>
-                  <div class="content">
-                    <span v-if="!state.editConsigneeFlag">
-                      {{ state.orderDetail.address.consignee }}
-                    </span>
-                    <el-input
-                      v-if="state.editConsigneeFlag"
-                      v-model="state.address.consignee"
-                      size="small"
-                    ></el-input>
-                  </div>
-                </div>
-                <div class="item">
-                  <div class="label">联系方式:</div>
-                  <div class="content">
-                    <span v-if="!state.editConsigneeFlag">
-                      {{ state.orderDetail.address.mobile }}
-                    </span>
-                    <el-input
-                      v-if="state.editConsigneeFlag"
-                      v-model="state.address.mobile"
-                      size="small"
-                    ></el-input>
-                  </div>
-                </div>
-                <div class="item">
-                  <div class="label">收货地址:</div>
-                  <div class="content">
-                    <div class="sa-m-b-4" v-if="!state.editConsigneeFlag">
-                      {{ state.orderDetail.address.province_name }}&nbsp;
-                      {{ state.orderDetail.address.city_name }}&nbsp;
-                      {{ state.orderDetail.address.district_name }}
-                    </div>
-                    <el-cascader
-                      v-if="state.editConsigneeFlag"
-                      class="sa-m-b-4"
-                      ref="addressRef"
-                      v-model="state.address.pcd"
-                      :options="state.area"
-                      :props="{ label: 'name', value: 'id' }"
-                      size="small"
-                    />
-                    <div v-if="!state.editConsigneeFlag">
-                      {{ state.orderDetail.address.address }}
-                    </div>
-                    <el-input
-                      v-if="state.editConsigneeFlag"
-                      v-model="state.address.address"
-                      size="small"
-                    ></el-input>
-                  </div>
-                </div>
-              </el-col>
-              <el-col
-                v-if="state.orderDetail.invoice_status == 1"
-                class="sa-col-12"
-                :xs="24"
-                :sm="24"
-                :md="12"
-                :lg="6"
-                :xl="6"
-              >
-                <div class="title sa-flex">
-                  发票信息
-                  <div class="sa-m-l-12">
-                    <span
-                      v-if="state.orderDetail.invoice.status != 'waiting'"
-                      class="invoice-status"
-                      >{{ state.orderDetail.invoice.status_text }}</span
-                    >
-                    <el-button
-                      v-if="state.orderDetail.invoice.status == 'waiting'"
-                      class="is-link"
-                      type="primary"
-                      text
-                      size="small"
-                      @click="onChangeInvoiceStatus(state.orderDetail.invoice)"
-                      >确认开具</el-button
-                    >
-                  </div>
-                </div>
-                <div class="item">
-                  <div class="label">发票类型:</div>
-                  <div class="content">
-                    {{ state.orderDetail.invoice.type_text }}
-                  </div>
-                </div>
-                <div class="item">
-                  <div class="label">抬头名称:</div>
-                  <div class="content">
-                    {{ state.orderDetail.invoice.name }}
-                  </div>
-                </div>
-                <div class="item" v-if="state.orderDetail.invoice.type === 'company'">
-                  <div class="label">税号:</div>
-                  <div class="content">
-                    {{ state.orderDetail.invoice.tax_no }}
-                  </div>
-                </div>
-                <div class="item">
-                  <div class="label">手机号:</div>
-                  <div class="content">
-                    {{ state.orderDetail.invoice.mobile }}
-                  </div>
-                </div>
-                <div class="item">
-                  <div class="label">金额:</div>
-                  <div class="content"> ¥{{ state.orderDetail.invoice.amount }} </div>
-                </div>
-                <div class="item" v-if="state.orderDetail.invoice.status === 'finish'">
-                  <div class="label">实际开票金额:</div>
-                  <div class="content"> ¥{{ state.orderDetail.invoice.invoice_amount }} </div>
-                </div>
-              </el-col>
-            </el-row>
-          </el-tab-pane>
-          <el-tab-pane
-            :label="`包裹${exindex + 1}`"
-            v-for="(ex, exindex) in state.orderDetail.express"
-            :key="ex"
-            :name="`${exindex + 2}`"
-          >
-            <el-row class="express-content" :gutter="10">
-              <el-col class="sa-col-8 left" :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
-                <div class="title sa-flex">
-                  物流信息
-                  <div class="sa-flex sa-m-l-12">
-                    <!-- 运单信息 -->
-                    <template v-if="!state.changeExpressStatus">
-                      <el-button
-                        v-auth="'shop.admin.order.order.dispatch'"
-                        class="is-link"
-                        type="primary"
-                        size="small"
-                        @click="onChangeExpressStatus(ex)"
-                        >修改运单</el-button
-                      >
-                      <el-popconfirm
-                        width="fit-content"
-                        confirm-button-text="确认"
-                        cancel-button-text="取消"
-                        title="您确定要取消运单吗?"
-                        @confirm="onCancelExpress(ex.id)"
-                      >
-                        <template #reference>
-                          <el-button
-                            v-auth="'shop.admin.order.order.dispatch'"
-                            class="is-link sa-m-l-8"
-                            type="danger"
-                            size="small"
-                            @click="cancelExpressPopover = true"
-                          >
-                            取消运单
-                          </el-button>
-                        </template>
-                      </el-popconfirm>
-                    </template>
-                    <!-- 修改运单信息 -->
-                    <template v-else>
-                      <el-button
-                        class="is-link"
-                        type="danger"
-                        size="small"
-                        @click="state.changeExpressStatus = false"
-                      >
-                        取消
-                      </el-button>
-                      <el-button
-                        class="is-link sa-m-l-8"
-                        type="primary"
-                        size="small"
-                        @click="onConfirmExpress(ex.id)"
-                        >确认</el-button
-                      >
-                    </template>
-                  </div>
-                </div>
-                <div class="item">
-                  <div class="label">快递公司:</div>
-                  <div class="content">
-                    <div v-if="!state.changeExpressStatus">
-                      {{ ex.express_name }}
-                    </div>
-                    <el-select
-                      size="small"
-                      v-if="state.changeExpressStatus"
-                      v-model="express.code"
-                      placeholder="请选择快递公司"
-                      @change="onChangeExpressCode"
-                      filterable
-                      remote
-                      reserve-keyword
-                      :remote-method="remoteMethod"
-                      :loading="deliverCompany.loading"
-                    >
-                      <el-option
-                        v-for="dc in deliverCompany.data"
-                        :key="dc"
-                        :label="dc.name"
-                        :value="dc.code"
-                        :ref="`dc-${dc.code}`"
-                        :data-name="dc.name"
-                        >{{ dc.name }}&nbsp;({{ dc.code }})</el-option
-                      >
-                      <div class="sa-p-l-15 sa-p-r-27">
-                        <sa-pagination
-                          layout="total, prev, pager, next"
-                          :pageData="deliverCompany.pageData"
-                          @updateFn="getDeliverCompany"
-                        />
-                      </div>
-                    </el-select>
-                  </div>
-                </div>
-                <div class="item">
-                  <div class="label">快递单号:</div>
-                  <div class="content">
-                    <div v-if="!state.changeExpressStatus">
-                      {{ ex.express_no }}
-                    </div>
-                    <el-input
-                      size="small"
-                      v-if="state.changeExpressStatus"
-                      v-model="express.no"
-                    ></el-input>
-                  </div>
-                </div>
-                <div class="goods-item sa-flex sa-m-t-12" v-for="goods in ex.items" :key="goods">
-                  <sa-image :url="goods.goods_image" size="40"></sa-image>
-                  <div class="sa-m-l-8">
-                    <div class="goods-title sa-table-line-1 sa-m-b-4">
-                      {{ goods.goods_title }}
-                    </div>
-                    <div class="sku sa-flex">
-                      <div v-if="goods.goods_sku_text" class="sa-m-r-8">
-                        {{ goods.goods_sku_text }}
-                      </div>
-                      <div>x {{ goods.goods_num }}</div>
-                    </div>
-                  </div>
-                </div>
-              </el-col>
-              <el-col
-                v-if="state.orderDetail.user"
-                class="sa-col-16 right"
-                :xs="24"
-                :sm="24"
-                :md="16"
-                :lg="16"
-                :xl="16"
-              >
-                <div class="title sa-flex sa-row-between">
-                  <template class="sa-flex">
-                    物流状态
-                    <el-button
-                      v-auth="'shop.admin.order.order.updateexpress'"
-                      class="is-link sa-m-l-12"
-                      type="primary"
-                      size="small"
-                      @click="onUpdateExpress(ex.id, 'subscribe')"
-                      >重新订阅</el-button
-                    >
-                    <el-popover
-                      placement="top"
-                      :width="300"
-                      trigger="hover"
-                      content="如果长时间物流状态没有更新,可以尝试刷新一下。如果没有物流信息,可以尝试重新订阅一下!"
-                    >
-                      <template #reference>
-                        <el-icon class="warning sa-m-l-4">
-                          <Warning />
-                        </el-icon>
-                      </template>
-                    </el-popover>
-                  </template>
-                  <el-button
-                    v-auth="'shop.admin.order.order.updateexpress'"
-                    class="refresh"
-                    size="small"
-                    icon="RefreshRight"
-                    @click="onUpdateExpress(ex.id, 'search')"
-                  ></el-button>
-                </div>
-                <el-timeline>
-                  <el-timeline-item
-                    :class="index == 0 ? 'el-timeline-item-first' : ''"
-                    v-for="(log, index) in ex.logs"
-                    :key="index"
-                    :timestamp="log.change_date"
-                    :color="index == 0 ? 'var(--t-color-primary)' : ''"
-                  >
-                    <div class="log-content">
-                      {{ log.content }}
-                    </div>
-                    <div class="change-date sa-m-t-8">{{ log.change_date }}</div>
-                  </el-timeline-item>
-                </el-timeline>
-              </el-col>
-            </el-row>
-          </el-tab-pane>
-        </el-tabs>
-      </div>
-
-      <!-- 商品列表 -->
-      <div class="goods-content">
-        <el-tabs>
-          <el-tab-pane label="商品信息">
-            <el-table class="sa-table" :data="state.orderDetail.items" stripe>
-              <el-table-column label="商品信息" min-width="250">
-                <template #default="scope">
-                  <goods-item
-                    :goods="{
-                      id: scope.row.goods_id,
-                      image: scope.row.goods_image,
-                      title: scope.row.goods_title,
-                      sku_text: scope.row.goods_sku_text,
-                    }"
-                    :isGoods="true"
-                    mode="order-detail"
-                  ></goods-item>
-                </template>
-              </el-table-column>
-              <el-table-column prop="goods_price" label="单价(元)" min-width="100" />
-              <el-table-column prop="goods_num" label="数量(件)" min-width="80" />
-              <el-table-column prop="discount_fee" label="优惠(元)" min-width="90" />
-              <el-table-column prop="pay_fee" label="支付金额" min-width="120" />
-              <el-table-column label="订单类型" min-width="100">
-                <template #default>{{ state.orderDetail.type_text }}</template>
-              </el-table-column>
-              <el-table-column prop="dispatch_type_text" label="配送方式" width="100">
-              </el-table-column>
-              <el-table-column label="发货状态" min-width="120">
-                <template #default="scope">
-                  <status-button
-                    from="detail"
-                    :order="state.orderDetail"
-                    type="dispatch"
-                    :item="scope.row"
-                  ></status-button>
-                </template>
-              </el-table-column>
-              <el-table-column label="退款状态" min-width="100">
-                <template #default="scope">
-                  <status-button
-                    from="detail"
-                    :order="state.orderDetail"
-                    type="refund"
-                    :item="scope.row"
-                    @updateList="getOrderDetail"
-                  ></status-button>
-                </template>
-              </el-table-column>
-              <el-table-column label="售后状态" min-width="120">
-                <template #default="scope">
-                  <status-button from="detail" type="aftersale" :item="scope.row"></status-button>
-                </template>
-              </el-table-column>
-              <el-table-column label="商品评价" min-width="100">
-                <template #default="scope">
-                  <status-button
-                    type="comment"
-                    :order="state.orderDetail"
-                    :item="scope.row"
-                  ></status-button>
-                </template>
-              </el-table-column>
-              <el-table-column width="100" label="配送信息">
-                <template #default="scope">
-                  <div v-if="state.orderDetail.dispatch_status != 0">
-                    <el-popover trigger="hover" width="220">
-                      <div
-                        v-if="
-                          scope.row.dispatch_type == 'autosend' ||
-                          scope.row.dispatch_type == 'custom'
-                        "
-                      >
-                        <span>发货内容:</span>
-                        <template v-if="scope.row.ext?.dispatch_content_type == 'text'">
-                          {{ scope.row.ext?.dispatch_content }}
-                        </template>
-                        <template
-                          v-if="scope.row.ext?.dispatch_content_type == 'params'"
-                          v-for="item in scope.row.ext?.dispatch_content"
-                        >
-                          {{ item.title }}-{{ item.content }}
-                        </template>
-                      </div>
-                      <template v-if="scope.row.dispatch_type != 'express'" #reference>
-                        <el-button type="primary" link>查看详情</el-button>
-                      </template>
-                    </el-popover>
-                    <div v-if="scope.row.dispatch_type == 'express'">-</div>
-                  </div>
-                  <div v-else>-</div>
-                </template>
-              </el-table-column>
-            </el-table>
-          </el-tab-pane>
-          <el-tab-pane v-if="state.orderDetail?.activity_orders?.length > 0" label="优惠信息">
-            <el-table class="sa-table" :data="state.orderDetail.activity_orders" stripe>
-              <el-table-column prop="id" label="ID" min-width="80" />
-              <el-table-column label="活动标题" min-width="120">
-                <template #default="scope">
-                  <div class="sa-table-line-1">
-                    {{ scope.row.activity_title }}
-                  </div>
-                </template>
-              </el-table-column>
-              <el-table-column label="活动类型" min-width="120" align="center">
-                <template #default="scope">
-                  <div
-                    :style="
-                      activityStatusStyle(scope.row.activity_type, {
-                        padding: '8px 12px',
-                        'border-radius': '15px',
-                      })
-                    "
-                  >
-                    {{ scope.row.activity_type_text }}
-                  </div>
-                </template>
-              </el-table-column>
-              <el-table-column label="优惠信息" min-width="120" align="center">
-                <template #default="scope">
-                  <div class="sa-table-line-1">
-                    {{ scope.row.discount_text }}
-                  </div>
-                </template>
-              </el-table-column>
-              <el-table-column
-                prop="discount_fee"
-                label="优惠金额(元)"
-                min-width="120"
-                align="center"
-              />
-              <el-table-column
-                prop="goods_amount"
-                label="参与商品金额(元)"
-                min-width="140"
-                align="center"
-              />
-              <el-table-column label="参与商品" min-width="250" align="center">
-                <template #default="scope">
-                  <el-popover placement="top-start" :width="300" trigger="hover">
-                    <div class="discount-goods">
-                      <div class="item sa-flex" v-for="goods in scope.row.items" :key="goods">
-                        <sa-image :url="goods.goods_image" size="40"></sa-image>
-                        <div class="sa-m-l-12">
-                          <div class="sa-table-line-1">
-                            {{ goods.goods_title }}
-                          </div>
-                          <div class="">#{{ goods.goods_id }}</div>
-                        </div>
-                      </div>
-                    </div>
-                    <template #reference>
-                      <div>
-                        <span class="discount-items">
-                          {{ scope.row.items?.length }}
-                        </span>
-                        件商品
-                      </div>
-                    </template>
-                  </el-popover>
-                </template>
-              </el-table-column>
-              <el-table-column prop="create_time" label="参与时间" min-width="172" />
-            </el-table>
-          </el-tab-pane>
-        </el-tabs>
-      </div>
-
-      <!-- 支付列表 -->
-      <div class="sa-title">支付信息</div>
-      <div class="pay-content">
-        <el-table class="sa-table" :data="state.orderDetail.pays" stripe>
-          <el-table-column prop="pay_sn" label="支付单号" min-width="250" />
-          <el-table-column prop="pay_type_text" label="支付方式" min-width="80" align="center" />
-          <el-table-column prop="pay_fee" label="支付金额" min-width="120" align="center" />
-          <el-table-column prop="transaction_id" label="交易单号" min-width="280" align="center">
-            <template #default="scope">
-              <span>{{ scope.row.transaction_id || '-' }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="支付状态" min-width="120" align="center">
-            <template #default="scope">
-              <span :class="['status', `status-${scope.row.status}`]">{{
-                scope.row.status_text
-              }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column prop="refund_fee" label="已退款金额" min-width="120" align="center" />
-          <el-table-column prop="create_time" label="交易时间" min-width="172" align="center" />
-        </el-table>
-      </div>
-    </el-main>
-  </el-container>
-</template>
-<script setup>
-  import { computed, onMounted, reactive, ref, unref } from 'vue';
-  import { api } from '../order.service';
-  import { api as dataApi } from '@/app/shop/admin/data/data.service';
-  import { useModal } from '@/sheep/hooks';
-  import useClip from '@/sheep/utils/clipboard.js';
-  import useExpress from '@/app/shop/admin/data/express/express.js';
-
-  import StatusButton from './components/status.vue';
-  import { activityStatusStyle } from './status.js';
-  import GoodsItem from '@/app/shop/components/goods-item.vue';
-  import OrderDispatch from './dispatch.vue';
-  import OrderRefund from './refund.vue';
-  import OrderFee from './fee.vue';
-  import StatusSteps from './components/status-steps.vue';
-  import InvoiceEdit from '../invoice/edit.vue';
-  import { cloneDeep } from 'lodash';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps(['modal']);
-
-  const state = reactive({
-    orderDetail: {}, // 订单详情
-    orderStep: 1,
-    orderTabs: '1',
-    changeExpressStatus: false, // 变更运单状态
-    editConsigneeFlag: false,
-    address: {},
-    area: [],
-  });
-
-  // 获取订单详情
-  async function getOrderDetail() {
-    const { error, data } = await api.order.detail(props.modal.params.id);
-    if (error === 0) {
-      state.orderDetail = data;
-      state.orderTabs = '1';
-
-      onChangeEditConsigneeFlag(false);
-      changeOrderStep();
-    }
-  }
-
-  async function getArea() {
-    const { data } = await dataApi.area.select();
-    state.area = data;
-  }
-
-  function changeOrderStep() {
-    if (state.orderDetail.status == 'unpaid') {
-      state.orderStep = 1;
-    } else if (state.orderDetail.status == 'paid') {
-      state.orderStep = 2;
-      switch (state.orderDetail.status_code) {
-        case 'nosend':
-          state.orderStep = 2;
-          break;
-        case 'noget':
-          state.orderStep = 3;
-          break;
-        case 'nocomment':
-          state.orderStep = 4;
-          break;
-        case 'commented':
-          state.orderStep = 4;
-          break;
-      }
-    } else if (state.orderDetail.status == 'completed') {
-      state.orderStep = 5;
-    }
-  }
-
-  function onOpenDispatch() {
-    useModal(
-      OrderDispatch,
-      {
-        title: '订单发货',
-        data: state.orderDetail,
-      },
-      {
-        confirm: () => {
-          getOrderDetail();
-        },
-      },
-    );
-  }
-
-  function onOpenRefund() {
-    useModal(
-      OrderRefund,
-      {
-        title: '全部退款',
-        class: 'refund-dialog',
-        type: 'order.fullRefund',
-        data: {
-          id: state.orderDetail.id,
-          money: state.orderDetail.pay_fee,
-        },
-      },
-      {
-        confirm: () => {
-          getOrderDetail();
-        },
-      },
-    );
-  }
-
-  function onChangeFee() {
-    useModal(
-      OrderFee,
-      {
-        title: '改价',
-        class: 'fee-dialog',
-        id: state.orderDetail.id,
-        pay_fee: state.orderDetail.pay_fee,
-      },
-      {
-        confirm: () => {
-          getOrderDetail();
-        },
-      },
-    );
-  }
-
-  // 备注
-  const memoForm = reactive({
-    flag: false,
-    data: '',
-  });
-  function onChangeMemoFlag(val) {
-    memoForm.flag = true;
-    memoForm.data = val;
-  }
-  async function onConfirmMemo() {
-    const { error } = await api.order.editMemo(state.orderDetail.id, {
-      memo: memoForm.data,
-    });
-    if (error == 0) {
-      memoForm.flag = false;
-      getOrderDetail();
-    }
-  }
-
-  const { express, deliverCompany, getDeliverCompany, onChangeExpressCode, remoteMethod } =
-    useExpress();
-
-  function onChangeExpressStatus(ex) {
-    state.changeExpressStatus = true;
-
-    express.name = ex.express_name;
-    express.code = ex.express_code;
-    express.no = ex.express_no;
-  }
-
-  async function onChangeInvoiceStatus(row) {
-    useModal(
-      InvoiceEdit,
-      {
-        title: '编辑',
-        type: 'edit',
-        data: row,
-      },
-      {
-        confirm: () => {
-          getOrderDetail();
-        },
-      },
-    );
-    // const { data } = await api.invoice.confirm(id);
-  }
-
-  // 确认修改运单
-  async function onConfirmExpress(order_express_id) {
-    await api.order.dispatch({
-      order_id: props.modal.params.id,
-      order_express_id,
-      action: 'change',
-      express: express,
-    });
-    state.changeExpressStatus = false;
-    getOrderDetail();
-  }
-
-  // 取消运单
-  const cancelExpressPopover = ref(false);
-  async function onCancelExpress(order_express_id) {
-    await api.order.dispatch({
-      order_id: props.modal.params.id,
-      order_express_id,
-      action: 'cancel',
-    });
-    getOrderDetail();
-  }
-
-  async function onUpdateExpress(order_express_id, type) {
-    const { error } = await api.order.updateExpress(order_express_id, { type });
-    if (error == 0) getOrderDetail();
-  }
-
-  // 修改收货信息
-  function onChangeEditConsigneeFlag(type) {
-    if (state.orderDetail.address) {
-      state.address = {
-        consignee: state.orderDetail.address.consignee,
-        mobile: state.orderDetail.address.mobile,
-        pcd: [
-          state.orderDetail.address.province_id,
-          state.orderDetail.address.city_id,
-          state.orderDetail.address.district_id,
-        ],
-        address: state.orderDetail.address.address,
-      };
-    }
-    state.editConsigneeFlag = type;
-  }
-  const addressRef = ref();
-  async function onEditConsignee() {
-    let label = unref(addressRef).getCheckedNodes()[0].pathLabels;
-    let tempAddress = cloneDeep(state.address);
-    tempAddress = {
-      ...tempAddress,
-      province_name: label[0],
-      province_id: state.address.pcd[0],
-      city_name: label[1],
-      city_id: state.address.pcd[1],
-      district_name: label[2],
-      district_id: state.address.pcd[2],
-    };
-    delete tempAddress.pcd;
-
-    await api.order.editConsignee(state.orderDetail.id, tempAddress);
-    getOrderDetail();
-    state.editConsigneeFlag = false;
-  }
-
-  onMounted(() => {
-    getOrderDetail();
-    getArea();
-  });
-</script>
-<style lang="scss" scoped>
-  .order-detail {
-    .warm-tip {
-      line-height: 16px;
-      font-size: 12px;
-      color: var(--sa-font);
-    }
-    .status-content {
-      border-radius: 8px;
-      padding: var(--sa-padding);
-      background: var(--sa-table-header-bg);
-      .left,
-      .center,
-      .right {
-        margin-bottom: var(--sa-padding);
-      }
-      .right-item {
-        line-height: 16px;
-        margin-bottom: 8px;
-        display: flex;
-        align-items: center;
-        font-size: 12px;
-        &.right-item-discount {
-          align-items: flex-start;
-          margin-bottom: 0;
-        }
-        .label {
-          flex-shrink: 0;
-          color: var(--sa-subfont);
-        }
-        .content {
-          color: var(--sa-subtitle);
-        }
-      }
-      .left {
-        .name {
-          line-height: 24px;
-          font-size: 18px;
-          color: var(--sa-title);
-          font-weight: 900;
-          .refresh {
-            color: var(--t-color-primary);
-            cursor: pointer;
-          }
-        }
-        .desc {
-          line-height: 16px;
-          font-size: 12px;
-          color: var(--sa-subfont);
-        }
-        .tools {
-          height: 32px;
-        }
-        .memo {
-          height: 24px;
-          line-height: 24px;
-          font-size: 12px;
-          font-weight: 400;
-          color: var(--sa-font);
-        }
-      }
-      .right {
-        .fee-content {
-          height: 16px;
-          .original-pay-fee {
-            color: #999;
-          }
-        }
-      }
-      .remark {
-        height: 32px;
-        line-height: 32px;
-        padding: 0 12px;
-        background: var(--sa-table-striped);
-        border-radius: 4px;
-      }
-    }
-    .tabs-content {
-      :deep() {
-        .el-tabs {
-          .el-tabs__content {
-            padding: var(--sa-padding) var(--sa-padding) 0;
-          }
-        }
-      }
-      .title {
-        height: 24px;
-        font-size: 14px;
-        font-weight: 600;
-        color: var(--sa-title);
-        margin-bottom: 8px;
-        display: flex;
-        align-items: center;
-        overflow: hidden;
-        .warning {
-          color: #faad14;
-        }
-      }
-      .item {
-        line-height: 24px;
-        margin-bottom: 4px;
-        display: flex;
-        font-size: 12px;
-        &:last-child {
-          margin-bottom: 0;
-        }
-        .label {
-          flex-shrink: 0;
-          color: var(--sa-subfont);
-        }
-        .content {
-          color: var(--sa-subtitle);
-        }
-        .copy {
-          color: var(--el-color-primary);
-        }
-      }
-      .order-content {
-        .el-col {
-          padding-bottom: var(--sa-padding);
-        }
-        .invoice-status {
-          font-size: 12px;
-          color: var(--sa-font);
-        }
-        :deep() {
-          .nickname {
-            height: 24px;
-            line-height: 24px;
-          }
-        }
-      }
-      .express-content {
-        .left {
-          padding-bottom: var(--sa-padding);
-          max-height: 218px;
-          overflow: auto;
-          .goods-item {
-            .goods-title {
-              font-weight: 500;
-            }
-            .goods-title,
-            .sku {
-              height: 16px;
-              line-height: 16px;
-              font-size: 12px;
-              color: var(--sa-font);
-            }
-          }
-        }
-        .right {
-          padding-bottom: var(--sa-padding);
-          max-height: 218px;
-          overflow: auto;
-          .title {
-            .refresh {
-              padding: 5px;
-              background-color: transparent;
-            }
-          }
-          :deep() {
-            .el-timeline-item__timestamp {
-              position: absolute;
-              top: 2px;
-              left: -140px;
-              margin-top: 0;
-            }
-          }
-        }
-      }
-    }
-    .goods-content {
-      border-radius: 8px;
-      overflow: hidden;
-    }
-    .pay-content {
-      border-radius: 8px;
-      overflow: hidden;
-      .status {
-        &.status-unpaid {
-          color: #999;
-        }
-        &.status-paid {
-          color: #52c41a;
-        }
-        &.status-refund {
-          color: #ff4d4f;
-        }
-      }
-    }
-    :deep() {
-      .el-tabs {
-        .el-tabs__nav-wrap::after {
-          height: 0;
-        }
-        .el-tabs__content {
-          background: var(--sa-table-header-bg);
-          border-radius: 8px;
-        }
-      }
-    }
-    .discount-items {
-      color: var(--el-color-primary);
-    }
-
-    .discount-goods {
-      .item {
-        padding: 8px 0;
-        border-bottom: 1px solid var(--sa-border);
-        &:last-of-type {
-          border-bottom: 0;
-        }
-      }
-    }
-    .log-content {
-      line-height: 18px;
-      font-size: 12px;
-      font-weight: 400;
-      color: var(--sa-subtitle);
-    }
-    .change-date {
-      line-height: 14px;
-      font-size: 12px;
-      font-weight: 400;
-      color: var(--sa-subtitle);
-    }
-    .el-timeline-item-first {
-      &::after {
-        content: '';
-        position: absolute;
-        top: 20px;
-        left: 4px;
-        width: 2px;
-        height: calc(50% - 16px);
-        background: var(--t-color-primary);
-      }
-      .el-timeline-item__tail {
-        top: 8px;
-        bottom: 8px;
-      }
-    }
-  }
-</style>

+ 0 - 1207
src/app/shop/admin/order/order/o-index.vue

@@ -1,1207 +0,0 @@
-<template>
-  <el-container class="order-page panel-block">
-    <el-header class="sa-header">
-      <el-tabs class="sa-tabs" v-model="filterParams.data.status" @tab-change="onChangeStatus">
-        <el-tab-pane
-          v-for="(sl, key) in statusList"
-          :key="sl"
-          :label="`${sl.label}${sl.num ? '(' + sl.num + ')' : ''}`"
-          :name="key"
-        ></el-tab-pane>
-      </el-tabs>
-      <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">
-          <span class="left">订单列表</span>
-          <search-condition
-            :conditionLabel="filterParams.conditionLabel"
-            @deleteFilter="deleteFilter"
-          ></search-condition>
-        </div>
-        <div>
-          <el-button
-            v-auth="'shop.admin.order.order.list'"
-            class="sa-button-refresh"
-            icon="RefreshRight"
-            @click="getData()"
-          ></el-button>
-          <el-button class="sa-button-refresh" icon="Search" @click="openFilter"></el-button>
-          <el-button
-            v-auth="'shop.admin.order.order.export'"
-            :loading="exportLoading"
-            :disabled="exportLoading"
-            @click="onExport('export')"
-            >订单导出</el-button
-          >
-          <el-button
-            v-if="filterParams.data.status == 'nosend'"
-            v-auth="'shop.admin.order.order.exportdelivery'"
-            :loading="exportLoading"
-            :disabled="exportLoading"
-            @click="onExport('exportDelivery')"
-            >导出发货单</el-button
-          >
-        </div>
-      </div>
-    </el-header>
-    <el-main class="sa-p-0" v-loading="loading">
-      <el-table
-        height="100%"
-        class="sa-table"
-        :data="table.data"
-        :span-method="arraySpanMethod"
-        default-expand-all
-        @selection-change="handleSelectionChange"
-      >
-        <template #empty>
-          <sa-empty />
-        </template>
-        <el-table-column type="expand">
-          <template #default="props">
-            <el-table
-              class="sa-table sa-expand-table"
-              :data="props.row.items"
-              :span-method="objectSpanMethod"
-            >
-              <el-table-column width="88"></el-table-column>
-              <el-table-column width="324">
-                <template #default="scope">
-                  <div class="goods-item sa-flex">
-                    <sa-image :url="scope.row.goods_image" size="58" />
-                    <div class="right sa-m-l-12">
-                      <div class="goods-title sa-flex sa-table-line-1">
-                        <span class="goods-id" @click="onOpenGoodsEdit(scope.row.goods_id)"
-                          >#{{ scope.row.goods_id }}</span
-                        >
-                        {{ scope.row.goods_title }}
-                      </div>
-                      <div class="goods-sku-text sa-m-t-10">
-                        <span>商品编码:{{ scope.row.goods_sku_price_out_code }}</span>
-                      </div>
-                      <div class="sa-flex">
-                        <span class="goods-price sa-m-r-8">¥{{ scope.row.goods_price }}</span>
-                        <span class="goods-num sa-m-r-12">x{{ scope.row.goods_num }}</span>
-                        <index-status type="dispatch" :item="scope.row" />
-                        <el-button
-                          class="sa-m-l-8"
-                          v-auth="'shop.admin.order.order.dispatch'"
-                          type="primary"
-                          size="small"
-                          plain
-                          @click="onOpenManualOrder(scope.row.id)"
-                          >手动下单</el-button
-                        >
-                        <el-button
-                          class="sa-m-l-8"
-                          v-auth="'shop.admin.order.order.dispatch'"
-                          type="primary"
-                          size="small"
-                          plain
-                          v-if="
-                            scope.row.order_source === 'zkh' && scope.row.status_code === 'nosend'
-                          "
-                          @click="onOpenZkhSubmitConfirm(scope.row.id)"
-                          >震坤行下单</el-button
-                        >
-                      </div>
-                      <div class="sa-m-t-8 sa-font-24">
-                        <div v-if="scope.row.out_order_id"
-                          >外部订单号:{{ scope.row.out_order_id }}</div
-                        >
-                        <div v-if="scope.row.out_order_item_id" class="sa-m-t-5 sa-m-b-5"
-                          >外部子订单号:{{ scope.row.out_order_item_id }}</div
-                        >
-                        <div v-if="scope.row.order_source"
-                          >下单渠道:{{ formatOrderSource(scope.row.order_source) }}</div
-                        >
-                      </div>
-                    </div>
-                  </div>
-                </template>
-              </el-table-column>
-              <el-table-column min-width="140" align="center">
-                <template #default="scope">
-                  <index-status type="aftersale" :item="scope.row" @updateList="getData" />
-                </template>
-              </el-table-column>
-              <el-table-column min-width="92" align="center">
-                <template #default>
-                  <sa-user-profile :user="props.row.user" :id="props.row.user_id" mode="col" />
-                </template>
-              </el-table-column>
-              <el-table-column min-width="168" align="center">
-                <template #default>
-                  <div
-                    class="address-content sa-flex sa-flex-col sa-col-top"
-                    v-if="props.row.address"
-                  >
-                    <div class="consignee sa-m-b-6">
-                      <span>{{ props.row.address.consignee }}</span>
-                      <span class="sa-m-l-8">{{ props.row.address.mobile }}</span>
-                    </div>
-                    <div class="address-name sa-m-b-6">
-                      {{ props.row.address.province_name }}/{{ props.row.address.city_name }}/{{
-                        props.row.address.district_name
-                      }}/{{ props.row.address.town_name || '暂无' }}
-                    </div>
-                    <div class="address sa-table-line-1">
-                      {{ props.row.address.address }}
-                    </div>
-                  </div>
-                  <div v-else>{{ props.row.address_id }}</div>
-                </template>
-              </el-table-column>
-              <el-table-column prop="dispatch_type_text" min-width="168" align="center">
-              </el-table-column>
-              <el-table-column min-width="160" align="center">
-                <template #default="scope">
-                  <div class="sa-flex-col sa-col-center">
-                    <div
-                      v-if="scope.row.activity_type"
-                      class="activity-type-text sa-m-r-4"
-                      :class="scope.row.activity_type"
-                      >{{ scope.row.activity_type_text }}</div
-                    >
-                    <div class="sa-flex">
-                      ¥{{ scope.row.pay_fee }}
-                      <index-status
-                        v-if="scope.row.refund_status != 0"
-                        type="refund"
-                        :item="scope.row"
-                      />
-                    </div>
-                    <template v-if="Number(scope.row.discount_fee)">
-                      <el-popover
-                        popper-class="discount-fee-popover"
-                        placement="top"
-                        trigger="hover"
-                      >
-                        <div class="sa-flex">
-                          <div
-                            class="promo-type-text"
-                            v-for="text in scope.row.promo_types_text"
-                            :key="text"
-                            >{{ text }}</div
-                          >
-                        </div>
-                        <template #reference>
-                          <div>
-                            <div v-if="scope.row.promo_types_text?.length > 0" class="discount-fee">
-                              (优惠: ¥{{ scope.row.discount_fee }})
-                            </div>
-                          </div>
-                        </template>
-                      </el-popover>
-                      <div v-if="scope.row.promo_types_text?.length == 0" class="discount-fee">
-                        (优惠: -¥{{ scope.row.discount_fee }})
-                      </div>
-                    </template>
-                  </div>
-                </template>
-              </el-table-column>
-              <el-table-column min-width="240">
-                <template #default>
-                  <div class="sa-flex">
-                    <el-popover
-                      v-model:visible="refundPopover[props.$index]"
-                      popper-class="refund-popover"
-                      placement="top-start"
-                      :width="204"
-                      trigger="click"
-                    >
-                      <div class="title sa-flex">
-                        <el-icon><QuestionFilled /></el-icon>
-                        您同意用户进行申请退款吗?
-                      </div>
-                      <div class="sa-flex sa-row-right">
-                        <el-button
-                          v-auth="'shop.admin.order.order.applyrefundrefuse'"
-                          class="is-link"
-                          size="small"
-                          type="info"
-                          @click="onApplyRefundRefuse(props.row.id, props.$index)"
-                          >拒绝</el-button
-                        >
-                        <el-button
-                          v-auth="'shop.admin.order.order.fullrefund'"
-                          size="small"
-                          type="danger"
-                          @click="onOpenRefund(props.row, props.$index)"
-                          >同意</el-button
-                        >
-                      </div>
-                      <template #reference>
-                        <div class="apply-refund-wrap">
-                          <el-button
-                            v-if="props.row.btns?.includes('apply_refund_oper')"
-                            class="apply-refund-button sa-m-r-12"
-                            type="danger"
-                            plain
-                          >
-                            用户申请退款
-                            <el-icon><ArrowRight /></el-icon>
-                          </el-button>
-                        </div>
-                      </template>
-                    </el-popover>
-                    <el-popover
-                      v-model:visible="confirmPopover[props.$index]"
-                      popper-class="confirm-popover sa-popper"
-                      placement="top-start"
-                      :width="204"
-                      trigger="click"
-                    >
-                      <div class="title sa-flex">
-                        <el-icon>
-                          <question-filled />
-                        </el-icon>
-                        确认用户是否收货?
-                      </div>
-                      <div class="sa-flex sa-row-right">
-                        <el-button
-                          v-auth="'shop.admin.order.order.offlinerefuse'"
-                          size="small"
-                          type="danger"
-                          link
-                          @click="onOfflineRefuse(props.row.id, props.$index)"
-                          >用户拒收
-                        </el-button>
-                        <el-button
-                          v-auth="'shop.admin.order.order.offlineconfirm'"
-                          size="small"
-                          type="primary"
-                          @click="onOfflineConfirm(props.row.id, props.$index)"
-                          >确认收货
-                        </el-button>
-                      </div>
-                      <template #reference>
-                        <div class="apply-refund-wrap">
-                          <el-button
-                            v-if="props.row.btns?.includes('confirm')"
-                            class="send-button sa-m-r-12"
-                            type="primary"
-                            plain
-                          >
-                            确认收货
-                            <el-icon>
-                              <arrow-right />
-                            </el-icon>
-                          </el-button>
-                        </div>
-                      </template>
-                    </el-popover>
-
-                    <el-button
-                      v-if="props.row.btns?.includes('send')"
-                      v-auth="'shop.admin.order.order.dispatch'"
-                      class="send-button"
-                      type="primary"
-                      plain
-                      @click="onSend(props.row)"
-                    >
-                      立即发货
-                      <el-icon><ArrowRight /></el-icon>
-                    </el-button>
-                    <el-button
-                      v-auth="'shop.admin.order.order.detail'"
-                      class="is-link"
-                      type="primary"
-                      @click="detailRow(props.row.id)"
-                      >详情</el-button
-                    >
-                    <el-button
-                      v-auth="'shop.admin.order.order.action'"
-                      class="is-link"
-                      type="primary"
-                      @click="onOpenAction(props.row.id)"
-                      >日志</el-button
-                    >
-                  </div>
-                </template>
-              </el-table-column>
-            </el-table>
-          </template>
-        </el-table-column>
-        <el-table-column type="selection" width="40" />
-        <el-table-column label="商品信息" width="304">
-          <template #default="scope">
-            <div class="order-wrap sa-flex">
-              <div class="id">#{{ scope.row.id }}</div>
-              <div class="order-sn sa-flex">
-                订单号:{{ scope.row.order_sn }}
-                <sa-svg
-                  class="copy sa-m-l-4 cursor-pointer"
-                  name="sa-copy"
-                  @click="useClip(scope.row.order_sn)"
-                ></sa-svg>
-              </div>
-              <div class="create-time"> 下单时间:{{ scope.row.create_time }} </div>
-              <div class="pay-types-text">{{ scope.row.pay_types_text?.join(',') }}</div>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="售后状态" min-width="140" align="center"></el-table-column>
-        <el-table-column label="下单用户" min-width="92" align="center"></el-table-column>
-        <el-table-column label="收货地址" min-width="168" align="center"></el-table-column>
-        <el-table-column label="配送方式" min-width="168" align="center"></el-table-column>
-        <el-table-column label="支付信息" min-width="160" align="center">
-          <template #default="scope">
-            <div class="sa-flex sa-row-center">
-              <el-popover popper-class="pay-fee-popover sa-popper" placement="top" trigger="hover">
-                <div>
-                  <div class="pay-fee-item">
-                    <div class="label">商品总价:</div>
-                    <div class="content">¥{{ scope.row.goods_amount }}</div>
-                  </div>
-                  <div class="pay-fee-item">
-                    <div class="label">运费价格:</div>
-                    <div class="content"> ¥{{ scope.row.dispatch_amount }} </div>
-                  </div>
-                  <div
-                    class="pay-fee-item pay-fee-item-discount"
-                    v-if="scope.row.ext && (scope.row.ext.promo_infos || scope.row.coupon_id)"
-                  >
-                    <div class="label">活动优惠:</div>
-                    <div class="content">
-                      <div
-                        class="pay-fee-item"
-                        v-if="Number(scope.row.ext.promo_discounts.full_reduce) > 0"
-                      >
-                        <div class="label sa-m-r-8">满减</div>
-                        <div class="content">
-                          -¥{{ scope.row.ext.promo_discounts.full_reduce }}
-                        </div>
-                      </div>
-                      <div
-                        class="pay-fee-item"
-                        v-if="Number(scope.row.ext.promo_discounts.full_discount) > 0"
-                      >
-                        <div class="label sa-m-r-8">满折</div>
-                        <div class="content">
-                          -¥{{ scope.row.ext.promo_discounts.full_discount }}
-                        </div>
-                      </div>
-                      <div
-                        class="pay-fee-item"
-                        v-if="Number(scope.row.ext.promo_discounts.full_gift) > 0"
-                      >
-                        <div class="label sa-m-r-8">满赠</div>
-                        <div class="content"></div>
-                      </div>
-                      <div
-                        class="pay-fee-item"
-                        v-if="Number(scope.row.ext.promo_discounts.free_shipping) > 0"
-                      >
-                        <div class="label sa-m-r-8">满包邮</div>
-                        <div class="content">
-                          -¥{{ scope.row.ext.promo_discounts.free_shipping }}
-                        </div>
-                      </div>
-                      <div v-if="scope.row.coupon_id" class="pay-fee-item">
-                        <div class="label sa-m-r-8">优惠券</div>
-                        <div class="content"> -¥{{ scope.row.coupon_discount_fee }} </div>
-                      </div>
-                    </div>
-                  </div>
-                  <div class="pay-fee-item">
-                    <div class="label"
-                      >{{
-                        ['paid', 'completed'].includes(scope.row.status) ? '实付金额' : '应付金额'
-                      }}:</div
-                    >
-                    <div class="content sa-flex">
-                      ¥{{ scope.row.pay_fee }}
-                      <s
-                        v-if="scope.row.pay_fee != scope.row.original_pay_fee"
-                        class="original-pay-fee sa-m-l-4"
-                      >
-                        {{ scope.row.original_pay_fee }}
-                      </s>
-                    </div>
-                  </div>
-                </div>
-                <template #reference>
-                  <div class="pay-fee-reference">¥{{ scope.row.pay_fee }}</div>
-                </template>
-              </el-popover>
-              <el-button
-                v-if="scope.row?.btns.includes('change_fee')"
-                class="is-link"
-                type="primary"
-                @click="onChangeFee(scope.row)"
-                >改价</el-button
-              >
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" min-width="240">
-          <template #default="scope">
-            <index-status :order="scope.row" type="status_code" />
-          </template>
-        </el-table-column>
-      </el-table>
-    </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-container>
-</template>
-<script>
-  export default {
-    name: 'shop.admin.order.order',
-  };
-</script>
-<script setup>
-  import { computed, onMounted, reactive, ref } from 'vue';
-  import { ElMessage, ElMessageBox } from 'element-plus';
-  import { api } from '../order.service';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { useSearch } from '@/sheep/components/sa-table/sa-search/useSearch';
-  import { composeFilter } from '@/sheep/utils';
-  import useClip from '@/sheep/utils/clipboard.js';
-  import StatusButton from './components/status.vue';
-  import IndexStatus from './components/index-status.vue';
-  import OrderDispatch from './dispatch.vue';
-  import OrderRefund from './refund.vue';
-  import OrderFee from './fee.vue';
-  import { activityStatusStyle } from './status.js';
-  import OrderBatchDispatch from './batchDispatch.vue';
-  import OrderDetail from './detail.vue';
-  import OrderAction from './action.vue';
-  import GoodsEdit from '@/app/shop/admin/goods/goods/edit.vue';
-  import { useRoute } from 'vue-router';
-  import { cloneDeep } from 'lodash';
-  import ManualOrder from './manualOrder.vue';
-  const route = useRoute();
-
-  // getType
-  const statusList = reactive({});
-  async function getType() {
-    const { data } = await api.order.getType();
-    for (var key in data) {
-      if (filterParams.tools[key] && key != 'status') {
-        filterParams.tools[key].options.data = data[key];
-      } else if (key == 'pay_type') {
-        filterParams.tools['pay.pay_type'].options.data = data[key];
-      } else if (key == 'status') {
-        data[key].forEach((s) => {
-          statusList[s.type] = { label: s.name, num: 0 };
-        });
-      }
-    }
-  }
-
-  const filterParams = reactive({
-    tools: {
-      status: { label: '订单状态', value: route.query.status || 'all' },
-      order: {
-        type: 'tinputprepend',
-        label: '请输入查询内容',
-        field: 'order',
-        order: {
-          field: 'id',
-          value: '',
-        },
-        options: [
-          {
-            label: '订单ID',
-            value: 'id',
-          },
-          {
-            label: '订单编号',
-            value: 'order_sn',
-          },
-          {
-            label: '售后单号',
-            value: 'aftersale.aftersale_sn',
-          },
-          {
-            label: '支付单号',
-            value: 'pay.pay_sn',
-          },
-          {
-            label: '交易流水号',
-            value: 'pay.transaction_id',
-          },
-        ],
-      },
-      user: {
-        type: 'tinputprepend',
-        label: '请输入查询内容',
-        field: 'user',
-        user: {
-          field: 'user_id',
-          value: '',
-        },
-        options: [
-          {
-            label: '用户ID',
-            value: 'user_id',
-          },
-          {
-            label: '用户昵称',
-            value: 'user.nickname',
-          },
-          {
-            label: '用户手机号',
-            value: 'user.mobile',
-          },
-          {
-            label: '收货人',
-            value: 'address.consignee',
-          },
-          {
-            label: '收货人手机号',
-            value: 'address.mobile',
-          },
-        ],
-      },
-      type: {
-        type: 'tselect',
-        label: '订单类型',
-        field: 'type',
-        value: '',
-        options: {
-          data: [],
-          props: {
-            label: 'name',
-            value: 'type',
-          },
-        },
-      },
-      platform: {
-        type: 'tselect',
-        label: '订单来源',
-        field: 'platform',
-        value: '',
-        options: {
-          data: [],
-          props: {
-            label: 'name',
-            value: 'type',
-          },
-        },
-      },
-      'pay.pay_type': {
-        type: 'tselect',
-        label: '支付方式',
-        field: 'pay.pay_type',
-        value: '',
-        options: {
-          data: [],
-          props: {
-            label: 'name',
-            value: 'type',
-          },
-        },
-      },
-      activity_type: {
-        type: 'tselect',
-        label: '活动类型',
-        field: 'activity_type',
-        value: '',
-        options: {
-          data: [],
-          props: {
-            label: 'name',
-            value: 'type',
-          },
-        },
-      },
-      promo_types: {
-        type: 'tselect',
-        label: '促销类型',
-        field: 'promo_types',
-        value: '',
-        options: {
-          data: [],
-          props: {
-            label: 'name',
-            value: 'type',
-          },
-        },
-      },
-      'item.goods_title': {
-        type: 'tinput',
-        label: '商品名称',
-        field: 'item.goods_title',
-        value: '',
-      },
-      create_time: {
-        type: 'tdatetimerange',
-        label: '下单时间',
-        field: 'create_time',
-        value: route.query.create_time || [],
-      },
-    },
-    data: {
-      status: route.query.status || 'all',
-      order: { field: 'id', value: '' },
-      user: { field: 'user_id', value: '' },
-      type: '',
-      platform: '',
-      'pay.pay_type': '',
-      activity_type: '',
-      promo_types: '',
-      'item.goods_title': '',
-      create_time: route.query.create_time || [],
-    },
-    conditionLabel: {},
-  });
-  const { openFilter, deleteFilter } = useSearch({ filterParams, getData });
-
-  const loading = ref(true);
-
-  // 表格
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-
-  const { pageData } = usePagination();
-
-  // 下单渠道格式化函数
-  const formatOrderSource = (source) => {
-    const sourceMap = {
-      self: '自下单',
-      linkedmall: 'linkedmall',
-      zkh: '震坤行',
-    };
-    return sourceMap[source] || source;
-  };
-
-  // 获取数据
-  async function getData(page) {
-    loading.value = true;
-    if (page) pageData.page = page;
-    let tempSearch = cloneDeep(filterParams.data);
-    let search = composeFilter(tempSearch, {
-      order_sn: 'like',
-      'aftersale.aftersale_sn': 'like',
-      'pay.pay_sn': 'like',
-      'pay.transaction_id': 'like',
-      'user.nickname': 'like',
-      'user.mobile': 'like',
-      'address.consignee': 'like',
-      'address.mobile': 'like',
-      promo_types: 'find_in_set',
-      'item.goods_title': 'like',
-      create_time: 'range',
-    });
-    const { error, data } = await api.order.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      ...search,
-    });
-    if (error === 0) {
-      table.data = data.orders.data;
-      pageData.page = data.orders.current_page;
-      pageData.list_rows = data.orders.per_page;
-      pageData.total = data.orders.total;
-      for (var key in statusList) {
-        statusList[key].num = data[key];
-      }
-    }
-    loading.value = false;
-  }
-
-  function onChangeStatus(tab) {
-    table.data = [];
-    getData(1);
-  }
-
-  function handleSelectionChange(val) {
-    table.selected = val;
-  }
-
-  // 导出订单/导出发货单
-  const exportLoading = ref(false);
-  async function onExport(type) {
-    exportLoading.value = true;
-    let tempSearch = cloneDeep(filterParams.data);
-    let search = composeFilter(tempSearch, {
-      order_sn: 'like',
-      'aftersale.aftersale_sn': 'like',
-      'pay.pay_sn': 'like',
-      'pay.transaction_id': 'like',
-      'user.nickname': 'like',
-      'user.mobile': 'like',
-      'address.consignee': 'like',
-      'address.mobile': 'like',
-      promo_types: 'find_in_set',
-      'item.goods_title': 'like',
-      create_time: 'range',
-    });
-    await api.order.export(type, { search: search.search });
-    exportLoading.value = false;
-  }
-
-  const batchHandleTools = [
-    {
-      type: 'all',
-      label: '批量发货',
-      auth: 'shop.admin.order.order.dispatch',
-      buttonType: 'default',
-    },
-  ];
-  function batchHandle() {
-    let order_ids = [];
-    table.selected.forEach((s) => {
-      order_ids.push(s.id);
-    });
-    useModal(
-      OrderBatchDispatch,
-      {
-        title: '批量发货',
-        order_ids,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  function detailRow(id) {
-    useModal(
-      OrderDetail,
-      {
-        title: '订单详情',
-        type: 'detail',
-        id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-        close: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  function onOpenAction(id) {
-    useModal(OrderAction, {
-      title: '日志',
-      id,
-    });
-  }
-
-  const arraySpanMethod = ({ row, column, rowIndex, columnIndex }) => {
-    if (columnIndex == 2) {
-      return [1, 4];
-    } else if (columnIndex == 3 || columnIndex == 4 || columnIndex == 5) {
-      return [0, 0];
-    }
-  };
-
-  const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
-    if (columnIndex == 0) {
-      return [0, 0];
-    }
-    if (columnIndex == 1) {
-      return [1, 2];
-    }
-    if (columnIndex == 3 || columnIndex == 4 || columnIndex == 7) {
-      if (rowIndex == 0) {
-        return {
-          rowspan: 200,
-          colspan: 1,
-        };
-      } else {
-        return {
-          rowspan: 0,
-          colspan: 0,
-        };
-      }
-    }
-  };
-
-  function onOpenGoodsEdit(id) {
-    useModal(GoodsEdit, {
-      title: '商品',
-      type: 'edit',
-      id: id,
-    });
-  }
-
-  function onSend(row) {
-    useModal(
-      OrderDispatch,
-      {
-        title: '订单发货',
-        data: row,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  function onChangeFee(row) {
-    useModal(
-      OrderFee,
-      {
-        title: '改价',
-        class: 'fee-dialog',
-        id: row.id,
-        pay_fee: row.pay_fee,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  function onOpenRefund(row, index) {
-    refundPopover[index] = false;
-    useModal(
-      OrderRefund,
-      {
-        title: '全部退款',
-        class: 'refund-dialog',
-        type: 'order.fullRefund',
-        data: {
-          id: row.id,
-          money: row.pay_fee,
-        },
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  const refundPopover = reactive({});
-  async function onApplyRefundRefuse(id, index) {
-    refundPopover[index] = false;
-    const { error } = await api.order.applyRefundRefuse(id);
-    if (error == 0) {
-      getData();
-    }
-  }
-
-  function onOpenZkhSubmitConfirm(order_item_id) {
-    ElMessageBox.confirm('确定要下单到震坤行吗?', '提示', {
-      confirmButtonText: '确定',
-      cancelButtonText: '取消',
-      type: 'warning',
-    })
-      .then(async () => {
-        const { error } = await api.order.zkhOrderSubmit({ order_item_id });
-        if (error == 0) {
-          ElMessage.success('下单成功');
-          getData();
-        }
-      })
-      .catch(() => {
-        // 用户取消操作,不做任何处理
-      });
-  }
-
-  const confirmPopover = reactive({});
-  async function onOfflineRefuse(id, index) {
-    confirmPopover[index] = false;
-    const { error } = await api.order.offlineRefuse(id);
-    if (error == 0) {
-      getData();
-    }
-  }
-  async function onOfflineConfirm(id, index) {
-    confirmPopover[index] = false;
-    const { error } = await api.order.offlineConfirm(id);
-    if (error == 0) {
-      getData();
-    }
-  }
-
-  function onOpenManualOrder(id) {
-    useModal(
-      ManualOrder,
-      {
-        title: '手动下单',
-        id: id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  onMounted(async () => {
-    await getType();
-    getData();
-  });
-</script>
-<style lang="scss" scoped>
-  .order-page {
-    .el-main {
-      :deep() {
-        .el-table {
-          --el-table-row-hover-bg-color: var(--sa-background-hex-hover);
-          .el-table__row {
-            background: var(--sa-background-hex-hover);
-          }
-          .el-table__header-wrapper {
-            margin-bottom: 4px;
-          }
-          .el-table__expanded-cell {
-            padding: 0;
-            background: var(--sa-background-assist);
-          }
-        }
-        .sa-expand-table {
-          --el-table-row-hover-bg-color: var(--el-fill-color-light);
-          .el-table__header-wrapper {
-            margin-bottom: 0;
-          }
-          .el-table__row {
-            background: var(--el-table-tr-bg-color);
-          }
-          .el-table__body tr:hover > td.el-table__cell {
-            background-color: var(--sa-background-assist) !important;
-          }
-          .el-table__body tr.hover-row.current-row > td.el-table__cell,
-          .el-table__body tr.hover-row.el-table__row--striped.current-row > td.el-table__cell,
-          .el-table__body tr.hover-row.el-table__row--striped > td.el-table__cell,
-          .el-table__body tr.hover-row > td.el-table__cell {
-            background-color: var(--sa-background-assist) !important;
-          }
-        }
-      }
-
-      .sa-table {
-        .apply-refund {
-          font-size: 12px;
-          font-weight: 400;
-          color: #999;
-        }
-        .order-wrap {
-          position: relative;
-          line-height: 14px;
-          font-size: 12px;
-          font-weight: 400;
-          color: var(--sa-font);
-          & > div {
-            margin-right: 24px;
-          }
-          .id {
-            min-width: 80px;
-            color: var(--sa-subtitle);
-          }
-          .order-sn {
-            min-width: 228px;
-            height: 14px;
-            font-size: 12px;
-            color: var(--sa-subtitle);
-            .copy {
-              width: 12px !important;
-              height: 12px !important;
-            }
-          }
-          .create-time {
-            line-height: 14px;
-            font-size: 12px;
-            color: var(--sa-subtfont);
-          }
-          .platform-text {
-            min-width: 116px;
-          }
-          .pay-types-text {
-            margin-right: 0;
-          }
-        }
-        .address-content {
-          width: 100%;
-          display: inline-flex;
-          .consignee {
-            line-height: 14px;
-            color: var(--sa-font);
-            font-size: 12px;
-            text-align: left;
-          }
-          .address-name {
-            line-height: 16px;
-            font-size: 14px;
-            color: var(--sa-subtitle);
-            text-align: left;
-          }
-          .address {
-            height: 14px;
-            line-height: 14px;
-            font-size: 12px;
-            color: var(--sa-subfont);
-          }
-        }
-      }
-      .sa-expand-table {
-        :deep() {
-          .el-table__header-wrapper {
-            display: none;
-          }
-        }
-      }
-    }
-  }
-  .refund-popover,
-  .confirm-popover {
-    .title {
-      line-height: 20px;
-      font-size: 12px;
-      font-weight: 400;
-      color: var(--sa-font);
-      margin-bottom: 16px;
-      .el-icon {
-        font-size: 14px;
-        color: #faad14;
-        margin-right: 8px;
-      }
-    }
-    .tip {
-      font-size: 12px;
-      font-weight: 400;
-      color: var(--sa-subfont);
-    }
-  }
-  .pay-fee-reference {
-    width: fit-content;
-    color: var(--t-color-primary);
-    border-bottom: 1px dashed var(--t-color-primary);
-    cursor: pointer;
-  }
-  .pay-fee-popover {
-    .pay-fee-item {
-      line-height: 16px;
-      font-size: 12px;
-      display: flex;
-      align-items: center;
-      margin-bottom: 8px;
-      &.pay-fee-item-discount {
-        align-items: flex-start;
-      }
-      &:last-child {
-        margin-bottom: 0;
-      }
-      .label {
-        flex-shrink: 0;
-        color: var(--sa-subfont);
-      }
-      .content {
-        color: var(--sa-subtitle);
-      }
-      .original-pay-fee {
-        color: #999;
-      }
-    }
-  }
-
-  .goods-item {
-    .right {
-      flex: 1;
-      line-height: 14px;
-      font-size: 12px;
-      font-weight: 400;
-      color: var(--sa-font);
-      .goods-title {
-        height: 14px;
-        line-height: 14px;
-        font-size: 12px;
-        font-weight: 500;
-        margin-bottom: 4px;
-        .goods-id {
-          color: var(--t-color-primary);
-          cursor: pointer;
-        }
-      }
-      .goods-sku-text {
-        height: 14px;
-        line-height: 14px;
-        margin-bottom: 10px;
-      }
-      .goods-price {
-      }
-      .goods-num {
-      }
-    }
-  }
-
-  .activity-type-text {
-    width: fit-content;
-    height: 20px;
-    padding: 0 5px;
-    border-radius: 2px;
-    font-weight: 400;
-    font-size: 12px;
-    line-height: 20px;
-    text-align: center;
-    &.groupon,
-    &.groupon_ladder {
-      background: var(--t-bg-active);
-      color: var(--t-color-primary);
-    }
-    &.seckill {
-      background: rgba(255, 77, 79, 0.16);
-      color: #ff4d4f;
-    }
-  }
-
-  .discount-fee {
-    line-height: 14px;
-    font-size: 12px;
-    font-weight: 400;
-    color: var(--sa-font);
-    text-decoration-line: underline;
-  }
-  .discount-fee-popover {
-    .promo-type-text {
-      width: fit-content;
-      height: 20px;
-      padding: 0 8px;
-      background: rgba(250, 173, 20, 0.16);
-      border-radius: 10px;
-      font-weight: 400;
-      font-size: 12px;
-      color: #faad14;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-  }
-  .apply-refund-wrap {
-    width: fit-content;
-  }
-  .apply-refund-button {
-    height: 26px;
-    border-radius: 13px;
-    border: none;
-    padding: 0 8px 0 12px;
-    --el-button-bg-color: rgba(255, 77, 79, 0.16);
-  }
-  .send-button {
-    height: 26px;
-    border-radius: 13px;
-    border: none;
-    padding: 0 8px 0 12px;
-    --el-button-bg-color: var(--t-bg-active);
-  }
-</style>

+ 925 - 0
src/app/shop/admin/user/list/detail.vue

@@ -0,0 +1,925 @@
+<template>
+  <el-container class="user-detail">
+    <el-main>
+      <!-- 操作按钮 -->
+      <div class="action-buttons sa-m-b-2">
+        <el-button @click="onSendSms">发短信</el-button>
+        <el-button @click="onAppPush">APP推送</el-button>
+        <el-button type="primary" @click="onEdit">编辑资料</el-button>
+      </div>
+
+      <!-- 基本信息 -->
+      <div class="basic-info sa-m-b-26">
+        <div class="sa-title sa-m-b-10 font-bold">基本信息</div>
+        <div class="basic-info-tables">
+          <!-- 第一行表格 -->
+          <el-table
+            class="sa-table basic-info-table-1"
+            :data="[state.basicInfoTableData[0]]"
+            stripe
+            border
+            :show-header="true"
+          >
+            <el-table-column label="头像" width="180" align="center" :rowspan="2">
+              <template #default="{ row }">
+                <div class="avatar-section">
+                  <el-image
+                    :src="state.userDetail.avatar"
+                    style="width: 60px; height: 60px; border-radius: 8px"
+                    fit="cover"
+                  />
+                  <div class="user-name"
+                    >{{ state.userDetail.nickname || '--' }}
+                    <span class="user-level ml-4px">V{{ state.userDetail.level || '1' }}</span>
+                  </div>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="用户ID" align="center">
+              <template #default="{ row }">
+                {{ row.field1 || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="状态" align="center">
+              <template #default="{ row }">
+                {{ row.field2 || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="昵称" align="center">
+              <template #default="{ row }">
+                {{ row.field3 || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="收款银行" align="center">
+              <template #default="{ row }">
+                {{ row.field4 || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="账户名称" align="center">
+              <template #default="{ row }">
+                {{ row.field5 || '--' }}
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <!-- 第二行表格 -->
+          <el-table
+            class="sa-table basic-info-table-2"
+            :data="[state.basicInfoTableData[1]]"
+            stripe
+            border
+            :show-header="true"
+          >
+            <el-table-column label="" width="180" align="center">
+              <template #default="{ row }">
+                <!-- 空白,因为头像已经在上面显示 -->
+              </template>
+            </el-table-column>
+            <el-table-column label="手机号" align="center">
+              <template #default="{ row }">
+                {{ row.field1 || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="注册时间" align="center">
+              <template #default="{ row }">
+                {{ row.field2 || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="注册时间" align="center">
+              <template #default="{ row }">
+                {{ row.field3 || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="收款账户" align="center">
+              <template #default="{ row }">
+                {{ row.field4 || '--' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="账户名称" align="center">
+              <template #default="{ row }">
+                {{ row.field5 || '--' }}
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+
+      <!-- 统计信息 -->
+      <div class="stats-info sa-m-b-26">
+        <div class="sa-title sa-m-b-10 font-bold">统计信息</div>
+        <el-table class="sa-table" :data="state.statsTableData" stripe border>
+          <el-table-column label="邀请好友数" align="center">
+            <template #default="{ row }">{{ row.inviteFriends || '--' }}</template>
+          </el-table-column>
+          <el-table-column label="成功订单数" align="center">
+            <template #default="{ row }">{{ row.successOrders || '--' }}</template>
+          </el-table-column>
+          <el-table-column label="可参团次数" align="center">
+            <template #default="{ row }">{{ row.teamCount || '--' }}</template>
+          </el-table-column>
+          <el-table-column label="全部佣金总金额" width="150" align="center">
+            <template #default="{ row }">৳ {{ row.totalCommission || '--' }}</template>
+          </el-table-column>
+          <el-table-column label="已结算佣金余额" width="150" align="center">
+            <template #default="{ row }">৳ {{ row.settledCommission || '--' }}</template>
+          </el-table-column>
+          <el-table-column label="充值总金额" min-width="120" align="center">
+            <template #default="{ row }">৳ {{ row.rechargeTotal || '--' }}</template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 第二行统计数据 -->
+        <el-table class="sa-table mt-2px" :data="state.statsTableData2" stripe border>
+          <el-table-column label="团队人数" align="center">
+            <template #default="{ row }">{{ row.teamMembers || '--' }}</template>
+          </el-table-column>
+          <el-table-column label="账户余额" align="center">
+            <template #default="{ row }">৳ {{ row.accountBalance || '--' }}</template>
+          </el-table-column>
+          <el-table-column label="佣金账户余额" align="center">
+            <template #default="{ row }">৳ {{ row.commissionBalance || '--' }}</template>
+          </el-table-column>
+          <el-table-column label="近7天佣金余额" width="150" align="center">
+            <template #default="{ row }">৳ {{ row.recentCommission || '--' }}</template>
+          </el-table-column>
+          <el-table-column label="待结算佣金余额" width="150" align="center">
+            <template #default="{ row }">৳ {{ row.pendingCommission || '--' }}</template>
+          </el-table-column>
+          <el-table-column label="提现总金额" min-width="120" align="center">
+            <template #default="{ row }">৳ {{ row.withdrawTotal || '--' }}</template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <!-- Tab切换内容 -->
+      <div class="tab-content">
+        <el-tabs v-model="state.activeTab" @tab-change="handleTabChange">
+          <el-tab-pane label="订单记录" name="orders">
+            <el-skeleton v-if="state.loading.orders" :rows="5" animated />
+            <el-table v-else class="sa-table" :data="state.orders" stripe border>
+              <el-table-column label="订单编号" prop="order_sn" width="180" align="center" />
+              <el-table-column label="商品信息" width="300" align="left">
+                <template #default="{ row }">
+                  <div class="goods-info">
+                    <el-image
+                      :src="row.goods_image"
+                      style="width: 50px; height: 50px; border-radius: 4px; margin-right: 10px"
+                      fit="cover"
+                    />
+                    <div class="goods-detail">
+                      <div class="goods-name">{{ row.goods_name || '--' }}</div>
+                    </div>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="规格" prop="spec" width="100" align="center" />
+              <el-table-column label="数量" prop="quantity" width="80" align="center" />
+              <el-table-column label="付款金额" align="center">
+                <template #default="{ row }">৳{{ row.amount || '--' }}</template>
+              </el-table-column>
+              <el-table-column label="订单状态" prop="status_text" width="100" align="center" />
+              <el-table-column label="物流信息" prop="logistics_info" width="100" align="center" />
+              <el-table-column label="下单时间" prop="created_at" width="140" align="center" />
+              <el-table-column label="付款时间" prop="paid_at" width="140" align="center" />
+              <el-table-column label="操作" width="80" align="center">
+                <template #default="{ row }">
+                  <el-button type="primary" link @click="viewOrder(row)">查看</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-tab-pane>
+
+          <el-tab-pane label="下线用户" name="subordinates">
+            <el-skeleton v-if="state.loading.subordinates" :rows="5" animated />
+            <el-table v-else class="sa-table" :data="state.subordinates" stripe border>
+              <el-table-column label="昵称" prop="nickname" align="center" />
+              <el-table-column label="手机号" prop="mobile" align="center" />
+              <el-table-column label="注册时间" prop="created_at" align="center" />
+              <el-table-column label="等级" width="100" align="center">
+                <template #default="{ row }"> V{{ row.level || '1' }} </template>
+              </el-table-column>
+              <el-table-column label="操作" width="80" align="center">
+                <template #default="{ row }">
+                  <el-button type="primary" link @click="viewSubordinateUser(row)">查看</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-tab-pane>
+
+          <el-tab-pane label="佣金记录" name="commissions">
+            <el-skeleton v-if="state.loading.commissions" :rows="5" animated />
+            <el-table v-else class="sa-table" :data="state.commissions" stripe border>
+              <el-table-column label="佣金ID" prop="commission_id" align="center" />
+              <el-table-column label="佣金类型" prop="type_text" align="center" />
+              <el-table-column label="佣金说明" prop="description" />
+              <el-table-column label="佣金金额" align="center">
+                <template #default="{ row }"> ৳{{ row.amount || '--' }} </template>
+              </el-table-column>
+              <el-table-column label="佣金状态" prop="status_text" align="center" />
+              <el-table-column label="佣金发放时间" prop="created_at" align="center" />
+              <el-table-column label="操作" width="80" align="center">
+                <template #default="{ row }">
+                  <el-button type="primary" link @click="viewCommission(row)">查看</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-tab-pane>
+
+          <el-tab-pane label="充值记录" name="recharges">
+            <el-skeleton v-if="state.loading.recharges" :rows="5" animated />
+            <el-table v-else class="sa-table" :data="state.recharges" stripe border>
+              <el-table-column label="充值订单号" prop="order_sn" align="center" />
+              <el-table-column label="充值渠道" prop="channel_text" align="center" />
+              <el-table-column label="币种" prop="currency" align="center" />
+              <el-table-column label="金额" align="center">
+                <template #default="{ row }">৳{{ row.amount || '--' }}</template>
+              </el-table-column>
+              <el-table-column label="状态" prop="status_text" align="center" />
+              <el-table-column label="下单时间" prop="created_at" align="center" />
+              <el-table-column label="成功时间" prop="success_at" align="center" />
+              <el-table-column label="操作" width="80" align="center">
+                <template #default="{ row }">
+                  <el-button type="primary" link @click="viewRecharge(row)">查看</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-tab-pane>
+
+          <el-tab-pane label="提现记录" name="withdraws">
+            <el-skeleton v-if="state.loading.withdraws" :rows="5" animated />
+            <el-table v-else class="sa-table" :data="state.withdraws" stripe border>
+              <el-table-column label="提款订单号" prop="order_sn" width="150" align="center" />
+              <el-table-column label="退款类型" prop="type_text" width="100" align="center" />
+              <el-table-column label="提款渠道" prop="channel_text" width="100" align="center" />
+              <el-table-column label="币种" prop="currency" width="80" align="center" />
+              <el-table-column label="提款金额" width="100" align="center">
+                <template #default="{ row }">৳{{ row.amount || '--' }}</template>
+              </el-table-column>
+              <el-table-column label="状态" prop="status_text" width="100" align="center" />
+              <el-table-column label="收款银行" prop="bank_name" width="100" align="center" />
+              <el-table-column label="收款账户名称" prop="account_name" align="center" />
+              <el-table-column label="收款账户" prop="account_number" align="center" />
+              <el-table-column label="下单时间" prop="created_at" width="140" align="center" />
+              <el-table-column label="成功时间" prop="success_at" width="140" align="center" />
+              <el-table-column label="操作" width="80" align="center">
+                <template #default="{ row }">
+                  <el-button type="primary" link @click="viewWithdraw(row)">查看</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-tab-pane>
+
+          <el-tab-pane label="收货地址" name="addresses">
+            <el-skeleton v-if="state.loading.addresses" :rows="5" animated />
+            <el-table v-else class="sa-table" :data="state.addresses" stripe border>
+              <el-table-column label="收件人" prop="name" align="center" />
+              <el-table-column label="手机号" prop="mobile" align="center" />
+              <el-table-column label="详细地址" prop="address" min-width="600" />
+            </el-table>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </el-main>
+  </el-container>
+</template>
+
+<script setup>
+  import { reactive, onMounted } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import { useModal } from '@/sheep/hooks';
+  import { userDetailMock } from '@/sheep/mock/user';
+  import userEdit from './edit.vue';
+  import OrderDetail from '../../order/order/detail.vue';
+  import userDetail from './detail.vue';
+  import RechargeEdit from '../../finance/recharge/edit.vue';
+  import WithdrawEdit from '../../finance/withdraw/edit.vue';
+  import CommissionEdit from '../../finance/commission/edit.vue';
+
+  const props = defineProps({
+    modal: {
+      type: Object,
+      default: () => ({}),
+    },
+  });
+
+  const state = reactive({
+    userDetail: {},
+    basicInfoTableData: [],
+    statsTableData: [],
+    statsTableData2: [],
+    orders: [],
+    subordinates: [],
+    commissions: [],
+    recharges: [],
+    withdraws: [],
+    addresses: [],
+    activeTab: 'orders',
+    loading: {
+      orders: false,
+      subordinates: false,
+      commissions: false,
+      recharges: false,
+      withdraws: false,
+      addresses: false,
+    },
+    loadedTabs: new Set(), // 记录已加载的Tab
+  });
+
+  // 初始化基本信息表格数据
+  const initBasicInfoTableData = () => {
+    state.basicInfoTableData = [
+      {
+        field1: state.userDetail.user_id, // 用户ID
+        field2: state.userDetail.status_text, // 状态
+        field3: state.userDetail.nickname, // 昵称
+        field4: state.userDetail.bank_name, // 收款银行
+        field5: state.userDetail.account_name, // 账户名称
+      },
+      {
+        field1: state.userDetail.mobile, // 手机号
+        field2: state.userDetail.created_at, // 注册时间
+        field3: state.userDetail.created_at, // 注册时间
+        field4: state.userDetail.account_number, // 收款账户
+        field5: state.userDetail.account_name, // 账户名称
+      },
+    ];
+
+    // 初始化统计信息表格数据
+    const stats = userDetailMock.statsData;
+    state.statsTableData = [
+      {
+        inviteFriends: stats.invite_friends,
+        successOrders: stats.success_orders,
+        teamCount: stats.team_count,
+        totalCommission: stats.total_commission,
+        settledCommission: stats.settled_commission,
+        rechargeTotal: stats.recharge_total,
+      },
+    ];
+
+    state.statsTableData2 = [
+      {
+        teamMembers: stats.team_members,
+        accountBalance: stats.account_balance,
+        commissionBalance: stats.commission_balance,
+        recentCommission: stats.recent_commission,
+        pendingCommission: stats.pending_commission,
+        withdrawTotal: stats.withdraw_total,
+      },
+    ];
+  };
+
+  // 获取用户详情
+  const getUserDetail = async () => {
+    try {
+      // 使用mock数据
+      state.userDetail = userDetailMock.userDetail;
+      initBasicInfoTableData();
+    } catch (error) {
+      console.error('获取用户详情失败:', error);
+    }
+  };
+
+  // 获取订单记录
+  const getUserOrders = async () => {
+    try {
+      state.orders = userDetailMock.orders;
+    } catch (error) {
+      console.error('获取订单记录失败:', error);
+    }
+  };
+
+  // 获取下线用户
+  const getUserSubordinates = async () => {
+    try {
+      state.subordinates = userDetailMock.subordinates;
+    } catch (error) {
+      console.error('获取下线用户失败:', error);
+    }
+  };
+
+  // 获取佣金记录
+  const getUserCommissions = async () => {
+    try {
+      state.commissions = userDetailMock.commissions;
+    } catch (error) {
+      console.error('获取佣金记录失败:', error);
+    }
+  };
+
+  // 获取充值记录
+  const getUserRecharges = async () => {
+    try {
+      state.recharges = userDetailMock.recharges;
+    } catch (error) {
+      console.error('获取充值记录失败:', error);
+    }
+  };
+
+  // 获取提现记录
+  const getUserWithdraws = async () => {
+    try {
+      state.withdraws = userDetailMock.withdraws;
+    } catch (error) {
+      console.error('获取提现记录失败:', error);
+    }
+  };
+
+  // 获取收货地址
+  const getUserAddresses = async () => {
+    try {
+      state.addresses = userDetailMock.addresses;
+    } catch (error) {
+      console.error('获取收货地址失败:', error);
+    }
+  };
+
+  // Tab切换处理
+  const handleTabChange = (tabName) => {
+    state.activeTab = tabName;
+
+    // 如果该Tab还没有加载过数据,则进行懒加载
+    if (!state.loadedTabs.has(tabName)) {
+      loadTabData(tabName);
+    }
+  };
+
+  // 加载Tab数据
+  const loadTabData = async (tabName) => {
+    if (state.loadedTabs.has(tabName)) return;
+
+    state.loading[tabName] = true;
+
+    try {
+      // 模拟API请求延迟
+      await new Promise((resolve) => setTimeout(resolve, 1000));
+
+      switch (tabName) {
+        case 'orders':
+          await getUserOrders();
+          break;
+        case 'subordinates':
+          await getUserSubordinates();
+          break;
+        case 'commissions':
+          await getUserCommissions();
+          break;
+        case 'recharges':
+          await getUserRecharges();
+          break;
+        case 'withdraws':
+          await getUserWithdraws();
+          break;
+        case 'addresses':
+          await getUserAddresses();
+          break;
+      }
+
+      state.loadedTabs.add(tabName);
+    } catch (error) {
+      console.error(`加载${tabName}数据失败:`, error);
+    } finally {
+      state.loading[tabName] = false;
+    }
+  };
+
+  // 发短信
+  const onSendSms = () => {
+    ElMessage.info('发短信功能待开发');
+  };
+
+  // APP推送
+  const onAppPush = () => {
+    ElMessage.info('APP推送功能待开发');
+  };
+
+  // 编辑资料
+  const onEdit = () => {
+    useModal(
+      userEdit,
+      {
+        title: '编辑用户资料',
+        width: '80%',
+        id: state.userDetail.id,
+        type: 'edit',
+      },
+      {
+        confirm: () => {
+          // 编辑成功后刷新数据
+          getUserDetail();
+        },
+      },
+    );
+  };
+
+  // 查看订单详情
+  const viewOrder = (row) => {
+    useModal(OrderDetail, {
+      title: '订单详情',
+      width: '90%',
+      id: row.order_sn,
+    });
+  };
+
+  // 查看下线用户详情
+  const viewSubordinateUser = (row) => {
+    useModal(
+      userDetail,
+      {
+        title: '用户详情',
+        type: 'view',
+        width: '1200px',
+        id: row.id,
+      },
+      {
+        confirm: () => {
+          getData();
+        },
+      },
+    );
+  };
+
+  // 查看佣金记录详情
+  const viewCommission = (row) => {
+    useModal(CommissionEdit, {
+      title: '佣金详情',
+      width: '80%',
+      id: row.commission_id,
+    });
+  };
+
+  // 查看充值详情
+  const viewRecharge = (row) => {
+    useModal(RechargeEdit, {
+      title: '充值详情',
+      width: '80%',
+      id: row.order_sn,
+    });
+  };
+
+  // 查看提现详情
+  const viewWithdraw = (row) => {
+    useModal(WithdrawEdit, {
+      title: '提现详情',
+      width: '80%',
+      id: row.order_sn,
+    });
+  };
+
+  onMounted(() => {
+    // 初始化用户详情和基本信息
+    getUserDetail();
+    // 默认加载订单记录
+    loadTabData('orders');
+  });
+</script>
+
+<style lang="scss" scoped>
+  .user-detail {
+    padding: 20px;
+
+    .action-buttons {
+      text-align: right;
+      margin-bottom: 26px;
+
+      .el-button {
+        margin-right: 12px;
+      }
+    }
+
+    .basic-info {
+      h3 {
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--sa-title);
+      }
+
+      .avatar-section {
+        text-align: center;
+
+        .user-name {
+          font-weight: 600;
+          font-size: 16px;
+          color: var(--sa-title);
+        }
+
+        .user-level {
+          // 使用会员黄
+          color: rgba(252, 202, 0, 1);
+          font-size: 14px;
+        }
+      }
+
+      .info-item {
+        display: flex;
+        align-items: center;
+        margin-bottom: 8px;
+
+        .label {
+          flex-shrink: 0;
+          color: var(--sa-subfont);
+          font-size: 14px;
+        }
+
+        .value {
+          color: var(--sa-subtitle);
+          font-size: 14px;
+          font-weight: 500;
+        }
+      }
+    }
+
+    .goods-info {
+      display: flex;
+      align-items: center;
+
+      .goods-detail {
+        flex: 1;
+
+        .goods-name {
+          font-size: 14px;
+          color: #333;
+          line-height: 1.4;
+          word-break: break-word;
+        }
+      }
+    }
+
+    // 基本信息表格特殊样式
+    .basic-info-tables {
+      .basic-info-table-1 {
+        margin-bottom: 0;
+
+        .sa-table {
+          border-bottom: none;
+          border-radius: 8px 8px 0 0;
+        }
+      }
+
+      .basic-info-table-2 {
+        margin-top: -1px;
+
+        .sa-table {
+          border-top: none;
+          border-radius: 0 0 8px 8px;
+        }
+
+        // 隐藏第一列的内容,但保持宽度
+        :deep(.el-table__body-wrapper) {
+          .el-table__body tr td:first-child {
+            border-left: none;
+            background: transparent;
+          }
+        }
+      }
+    }
+
+    // 表格边框样式 - 参考order/detail.vue
+    .basic-info,
+    .stats-info,
+    .tab-content {
+      .sa-table {
+        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;
+              }
+            }
+          }
+        }
+
+        // 确保边框完整性
+        :deep(.el-table--border) {
+          border: none;
+
+          &::after {
+            display: none;
+          }
+
+          &::before {
+            display: none;
+          }
+        }
+
+        :deep(.el-table--border .el-table__cell) {
+          border-right: 1px solid #dcdfe6;
+          border-left: none;
+
+          &:first-child {
+            border-left: none;
+          }
+
+          &:last-child {
+            border-right: none;
+          }
+        }
+
+        // 修复表格底部和左侧边框
+        :deep(.el-table__body) {
+          border-left: none;
+          border-bottom: none;
+        }
+
+        :deep(.el-table__header) {
+          border-left: none;
+        }
+      }
+    }
+
+    .stats-info {
+      h3 {
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--sa-title);
+      }
+
+      .stat-item {
+        text-align: center;
+        padding: 16px;
+        border: 1px solid #dcdfe6;
+        border-radius: 8px;
+        background: var(--sa-table-header-bg, #f5f7fa);
+
+        .stat-label {
+          color: var(--sa-subfont);
+          font-size: 14px;
+          margin-bottom: 8px;
+        }
+
+        .stat-value {
+          color: var(--sa-title);
+          font-size: 18px;
+          font-weight: 600;
+        }
+      }
+    }
+
+    .orders-content,
+    .subordinates-content,
+    .commissions-content,
+    .addresses-content {
+      .sa-title {
+        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;
+              }
+            }
+          }
+        }
+
+        :deep(.el-table--border) {
+          border: none;
+
+          &::after {
+            display: none;
+          }
+
+          &::before {
+            display: none;
+          }
+        }
+
+        :deep(.el-table--border .el-table__cell) {
+          border-right: 1px solid #dcdfe6;
+          border-left: none;
+
+          &:first-child {
+            border-left: none;
+          }
+
+          &:last-child {
+            border-right: none;
+          }
+        }
+
+        :deep(.el-table__body) {
+          border-left: none;
+          border-bottom: none;
+        }
+
+        :deep(.el-table__header) {
+          border-left: none;
+        }
+      }
+    }
+
+    .goods-title {
+      font-size: 14px;
+      color: var(--sa-subtitle);
+      line-height: 1.4;
+    }
+  }
+  .sa-title {
+    line-height: 32px;
+  }
+</style>

+ 115 - 21
src/app/shop/admin/user/list/edit.vue

@@ -2,23 +2,65 @@
   <el-container>
     <el-main>
       <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
-        <el-form-item label="用户名" prop="username">
-          <el-input v-model="form.model.username" placeholder="请填写用户名"></el-input>
+        <el-form-item label="用户昵称" prop="nickname">
+          <el-input v-model="form.model.nickname" placeholder="请输入用户昵称"></el-input>
         </el-form-item>
-        <el-form-item label="昵称" prop="nickname">
-          <el-input v-model="form.model.nickname" placeholder="请填写昵称"></el-input>
+
+        <el-form-item label="用户手机" prop="mobile">
+          <el-input v-model="form.model.mobile" placeholder="请输入用户手机"></el-input>
         </el-form-item>
-        <el-form-item label="手机号" prop="mobile">
-          <el-input v-model="form.model.mobile" placeholder="请填写手机号"></el-input>
+
+        <el-form-item label="用户头像" prop="avatar">
+          <sa-upload-image
+            v-model="form.model.avatar"
+            :max-count="1"
+            :accept="['jpg', 'png']"
+            :max-size="5"
+            :direct-upload="true"
+            :size="100"
+            placeholder="上传用户头像"
+          />
         </el-form-item>
-        <el-form-item label="邮箱" prop="email">
-          <el-input v-model="form.model.email" placeholder="请填写邮箱"></el-input>
+
+        <el-form-item label="收款银行" prop="bank_name">
+          <el-input v-model="form.model.bank_name" placeholder="请输入收款银行"></el-input>
         </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-select>
+
+        <el-form-item label="账户名称" prop="account_name">
+          <el-input v-model="form.model.account_name" placeholder="请输入账户名称"></el-input>
+        </el-form-item>
+
+        <el-form-item label="收款账户" prop="account_number">
+          <el-input v-model="form.model.account_number" placeholder="请输入收款账户"></el-input>
+        </el-form-item>
+
+        <el-form-item label="登录密码" prop="password">
+          <el-input
+            v-model="form.model.password"
+            type="password"
+            placeholder="请输入登录密码"
+            show-password
+          ></el-input>
+        </el-form-item>
+
+        <el-form-item label="权限" prop="permissions">
+          <el-checkbox-group v-model="form.model.permissions">
+            <el-checkbox label="normal">正常</el-checkbox>
+            <el-checkbox label="withdraw_disabled">禁止提现</el-checkbox>
+            <el-checkbox label="login_disabled">禁止登录</el-checkbox>
+            <el-checkbox label="order_disabled">禁止下单</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+
+        <el-form-item label="备注" prop="remark">
+          <el-input
+            v-model="form.model.remark"
+            type="textarea"
+            :rows="4"
+            maxlength="100"
+            show-word-limit
+            placeholder="请输入备注(限100字)"
+          ></el-input>
         </el-form-item>
       </el-form>
     </el-main>
@@ -42,18 +84,26 @@
   let formRef = ref(null);
   const form = reactive({
     model: {
-      username: '',
       nickname: '',
       mobile: '',
-      email: '',
-      status: 'active',
+      avatar: [],
+      bank_name: '',
+      account_name: '',
+      account_number: '',
+      password: '',
+      permissions: ['normal'],
+      remark: '',
     },
     rules: {
-      username: [{ required: true, message: '请填写用户名', trigger: 'blur' }],
-      nickname: [{ required: true, message: '请填写昵称', trigger: 'blur' }],
+      nickname: [{ required: true, message: '请填写用户昵称', trigger: 'blur' }],
       mobile: [{ required: true, message: '请填写手机号', trigger: 'blur' }],
-      email: [{ required: true, message: '请填写邮箱', trigger: 'blur' }],
-      status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+      avatar: [{ required: true, message: '请上传用户头像', trigger: 'change' }],
+      password: [
+        { required: true, message: '请填写登录密码', trigger: 'blur' },
+        { min: 6, max: 20, message: '密码长度为6-20位', trigger: 'blur' },
+      ],
+      permissions: [{ required: true, message: '请选择用户权限', trigger: 'change' }],
+      remark: [{ max: 100, message: '备注不能超过100字', trigger: 'blur' }],
     },
   });
   const loading = ref(false);
@@ -61,14 +111,46 @@
   async function getDetail(id) {
     loading.value = true;
     const { error, data } = await api.list.detail(id);
-    error === 0 && (form.model = data);
+    if (error === 0) {
+      // 处理头像数据
+      if (data.avatar) {
+        form.model.avatar = Array.isArray(data.avatar) ? data.avatar : [data.avatar];
+      }
+      // 处理权限数据
+      if (data.permissions) {
+        form.model.permissions = Array.isArray(data.permissions)
+          ? data.permissions
+          : [data.permissions];
+      }
+      // 其他字段直接赋值
+      Object.keys(form.model).forEach((key) => {
+        if (key !== 'avatar' && key !== 'permissions' && data[key] !== undefined) {
+          form.model[key] = data[key];
+        }
+      });
+    }
     loading.value = false;
   }
+
   // 表单关闭时提交
   async function confirm() {
     unref(formRef).validate(async (valid) => {
       if (!valid) return;
       let submitForm = cloneDeep(form.model);
+
+      // 处理头像数据 - 只提交第一张图片的URL
+      if (submitForm.avatar && submitForm.avatar.length > 0) {
+        submitForm.avatar = submitForm.avatar[0];
+      } else {
+        submitForm.avatar = '';
+      }
+
+      // 处理权限数据 - 转换为字符串或保持数组格式
+      if (submitForm.permissions && submitForm.permissions.length > 0) {
+        // 根据后端需要的格式调整,这里保持数组格式
+        submitForm.permissions = submitForm.permissions;
+      }
+
       const { error } =
         props.modal.params.type == 'add'
           ? await api.list.add(submitForm)
@@ -87,3 +169,15 @@
     init();
   });
 </script>
+
+<style lang="scss" scoped>
+  .el-form {
+    max-width: 600px;
+
+    .el-checkbox-group {
+      .el-checkbox {
+        margin-right: 15px;
+      }
+    }
+  }
+</style>

+ 137 - 53
src/app/shop/admin/user/list/index.vue

@@ -1,7 +1,7 @@
 <template>
   <el-container class="user-list-view panel-block">
     <el-header class="sa-header">
-      <!-- 简化搜索组件 -->
+      <!-- 搜索组件 -->
       <div class="search-container">
         <sa-search-simple
           :searchFields="searchFields"
@@ -9,25 +9,46 @@
           @search="(val) => getData(1, val)"
           @reset="getData(1)"
         >
+          <template #custom="{ data }">
+            <el-form-item label="注册时间">
+              <el-date-picker
+                v-model="data.dateRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+                style="width: 240px"
+              />
+            </el-form-item>
+          </template>
         </sa-search-simple>
       </div>
+      <el-tabs class="sa-tabs" v-model="activeTab" @tab-change="handleTabChange">
+        <el-tab-pane label="全部" name="all"></el-tab-pane>
+        <el-tab-pane label="正常" name="normal"></el-tab-pane>
+        <el-tab-pane label="禁止提现" name="withdraw_disabled"></el-tab-pane>
+        <el-tab-pane label="禁止登录" name="login_disabled"></el-tab-pane>
+      </el-tabs>
       <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">用户列表</div>
+        <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="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">
+      <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-dblclick="editRow"
           row-key="id"
           stripe
         >
@@ -35,58 +56,89 @@
             <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="120">
+          <el-table-column prop="id" label="用户ID" min-width="120" sortable="custom">
             <template #default="scope">
-              <span class="sa-table-line-1">
-                {{ scope.row.username || '-' }}
-              </span>
+              <span class="user-id">{{ scope.row.id }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="昵称" min-width="120">
+          <el-table-column label="用户昵称" min-width="140">
             <template #default="scope">
-              <span class="sa-table-line-1">
-                {{ scope.row.nickname || '-' }}
-              </span>
+              <div class="user-info">
+                <el-link type="primary" @click="viewDetail(scope.row)">
+                  {{ scope.row.username || '-' }}
+                </el-link>
+              </div>
             </template>
           </el-table-column>
-          <el-table-column label="手机号" min-width="120">
+          <el-table-column label="手机号码" min-width="140">
             <template #default="scope">
               {{ scope.row.mobile || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="邮箱" min-width="160">
+          <el-table-column label="用户等级" min-width="100" align="center">
+            <template #default="scope">
+              <el-tag type="info" size="small">{{ scope.row.level || '-' }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="邀请好友数" min-width="120" align="center">
+            <template #default="scope">
+              {{ scope.row.invite_count || 0 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="团队人数" min-width="120" align="center">
+            <template #default="scope">
+              {{ scope.row.team_count || 0 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="成功订单" min-width="120" align="center">
+            <template #default="scope">
+              {{ scope.row.success_orders || 0 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="账户余额" min-width="120" align="center">
             <template #default="scope">
-              {{ scope.row.email || '-' }}
+              <span class="amount">{{ scope.row.balance || '৳0' }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="状态" min-width="100">
+          <el-table-column label="佣金余额" min-width="120" align="center">
             <template #default="scope">
-              <el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'">
+              <span class="amount">{{ scope.row.commission_balance || '৳0' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="状态" min-width="120" align="center">
+            <template #default="scope">
+              <el-tag
+                :type="
+                  scope.row.status === 'normal'
+                    ? 'success'
+                    : scope.row.status === 'withdraw_disabled'
+                      ? 'warning'
+                      : 'danger'
+                "
+                size="small"
+              >
                 {{ scope.row.status_text || '-' }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="创建时间" min-width="160">
+          <el-table-column label="备注" min-width="120">
             <template #default="scope">
-              {{ scope.row.create_time || '-' }}
+              {{ scope.row.remark || '--' }}
             </template>
           </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="120">
+          <el-table-column label="注册时间" min-width="160">
             <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>
-                </template>
-              </el-popconfirm>
+              {{ scope.row.register_time || '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column fixed="right" label="操作" min-width="140">
+            <template #default="scope">
+              <el-button class="is-link" type="primary" @click="viewDetail(scope.row)">
+                详情
+              </el-button>
+              <el-button class="is-link" type="primary" @click="editRow(scope.row)">
+                编辑
+              </el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -113,14 +165,15 @@
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';
   import userEdit from './edit.vue';
+  import userDetail from './detail.vue';
   const { pageData } = usePagination();
 
   // 搜索字段配置
   const searchFields = reactive({
     username: {
       type: 'input',
-      label: '用户名',
-      placeholder: '请输入用户名',
+      label: '账号',
+      placeholder: '请输入用户名或昵称',
       width: 200,
     },
     mobile: {
@@ -130,11 +183,16 @@
       width: 200,
     },
   });
+
   // 默认搜索值
   const defaultSearchValues = reactive({
     username: '',
     mobile: '',
+    dateRange: [],
   });
+
+  // 当前激活的 Tab
+  const activeTab = ref('all');
   // 列表
   const table = reactive({
     data: [],
@@ -143,17 +201,29 @@
     selected: [],
   });
   const loading = ref(true);
-  // 获取
+
+  // 获取数据
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.list.list({
+
+    // 构建查询参数
+    const params = {
       page: pageData.page,
       list_rows: pageData.list_rows,
       order: table.order,
-      ...searchParams,
       sort: table.sort,
-    });
+      status: activeTab.value === 'all' ? '' : activeTab.value,
+      ...searchParams,
+    };
+
+    // 处理日期范围
+    if (searchParams.dateRange && searchParams.dateRange.length === 2) {
+      params.start_time = searchParams.dateRange[0];
+      params.end_time = searchParams.dateRange[1];
+    }
+
+    const { error, data } = await api.list.list(params);
     console.log('API 响应:', error, data);
     if (error === 0) {
       table.data = data.data;
@@ -163,6 +233,12 @@
     }
     loading.value = false;
   }
+
+  // Tab 切换处理
+  function handleTabChange(tabName) {
+    activeTab.value = tabName;
+    getData(1);
+  }
   // table 字段排序
   function fieldFilter({ prop, order }) {
     table.order = order == 'ascending' ? 'asc' : 'desc';
@@ -182,6 +258,24 @@
       class: 'danger',
     },
   ];
+  // 查看详情
+  function viewDetail(row) {
+    useModal(
+      userDetail,
+      {
+        title: '用户详情',
+        type: 'view',
+        width: '1400px',
+        id: row.id,
+      },
+      {
+        confirm: () => {
+          getData();
+        },
+      },
+    );
+  }
+
   function addRow() {
     useModal(
       userEdit,
@@ -193,6 +287,7 @@
       },
     );
   }
+
   function editRow(row) {
     useModal(
       userEdit,
@@ -240,15 +335,4 @@
     getData();
   });
 </script>
-<style lang="scss" scoped>
-  .user-list-view {
-    .el-header {
-      height: auto;
-    }
-    .el-main {
-      .sa-table-wrap {
-        height: 100%;
-      }
-    }
-  }
-</style>
+<style lang="scss" scoped></style>

+ 44 - 0
src/app/shop/admin/user/user.service.js

@@ -18,6 +18,7 @@ const route = {
         title: '用户列表',
       },
     },
+
     {
       path: 'level',
       name: 'shop.admin.user.level',
@@ -54,6 +55,49 @@ const api = {
       }),
   },
 
+  // 用户详情相关 API
+  getUserDetail: (id) =>
+    request({
+      url: `shop/admin/user/list/${id}`,
+      method: 'GET',
+    }),
+  getUserOrders: (id, params) =>
+    request({
+      url: `shop/admin/user/list/${id}/orders`,
+      method: 'GET',
+      params,
+    }),
+  getUserSubordinates: (id, params) =>
+    request({
+      url: `shop/admin/user/list/${id}/subordinates`,
+      method: 'GET',
+      params,
+    }),
+  getUserCommissions: (id, params) =>
+    request({
+      url: `shop/admin/user/list/${id}/commissions`,
+      method: 'GET',
+      params,
+    }),
+  getUserRecharges: (id, params) =>
+    request({
+      url: `shop/admin/user/list/${id}/recharges`,
+      method: 'GET',
+      params,
+    }),
+  getUserWithdraws: (id, params) =>
+    request({
+      url: `shop/admin/user/list/${id}/withdraws`,
+      method: 'GET',
+      params,
+    }),
+  getUserAddresses: (id, params) =>
+    request({
+      url: `shop/admin/user/list/${id}/addresses`,
+      method: 'GET',
+      params,
+    }),
+
   // 会员等级相关 API
   level: {
     ...CRUD('shop/admin/user/level'),

+ 3 - 1
src/assets/css/sa-global.scss

@@ -201,7 +201,9 @@ button {
   padding: 12px var(--sa-padding) 0 !important;
   background: var(--sa-background-assist);
   border-radius: 0 0 8px 8px;
-  box-shadow: 0 0 4px rgba(0, 0, 0, 0.08), 0 -2px 6px rgba(0, 0, 0, 0.06);
+  box-shadow:
+    0 0 4px rgba(0, 0, 0, 0.08),
+    0 -2px 6px rgba(0, 0, 0, 0.06);
   display: flex;
   align-items: center;
   justify-content: space-between;

+ 39 - 7
src/sheep/components/sa-uploader/sa-upload-image.global.vue

@@ -20,9 +20,10 @@
             <el-image
               :src="element"
               fit="cover"
-              :preview-src-list="[element]"
+              :preview-src-list="compact ? [] : imageList"
+              :initial-index="compact ? 0 : index"
               :preview-teleported="true"
-              @click="compact ? previewImage(element) : null"
+              @click="compact ? previewImage(element, index) : null"
               :style="{ cursor: compact ? 'pointer' : 'default' }"
             >
               <template #error>
@@ -39,7 +40,9 @@
               </template>
               <template v-else>
                 <el-icon class="sortable-drag" title="拖拽排序" size="24"><Rank /></el-icon>
-                <el-icon @click="previewImage(element)" title="预览" size="24"><ZoomIn /></el-icon>
+                <el-icon @click="previewImage(element, index)" title="预览" size="24"
+                  ><ZoomIn
+                /></el-icon>
                 <el-icon @click="removeImage(index)" title="删除" size="24"><Delete /></el-icon>
               </template>
             </div>
@@ -76,12 +79,24 @@
       <span v-if="accept && accept.length">支持{{ accept.join('、') }}格式,</span>
       <span v-if="maxSize">单张图片不超过{{ maxSize }}MB</span>
     </div>
+
+    <!-- 图片预览弹窗 -->
+    <el-image-viewer
+      v-if="previewVisible"
+      :url-list="previewImageList"
+      :initial-index="previewInitialIndex"
+      :infinite="false"
+      :hide-on-click-modal="true"
+      :teleported="true"
+      :z-index="3000"
+      @close="closePreview"
+    />
   </div>
 </template>
 
 <script>
   import { ref, computed, watch } from 'vue';
-  import { ElMessage } from 'element-plus';
+  import { ElMessage, ElImageViewer } from 'element-plus';
   import { Picture, Plus, Rank, ZoomIn, Delete } from '@element-plus/icons-vue';
   import SaDraggable from 'vuedraggable';
   import adminApi from '@/app/admin/api';
@@ -95,6 +110,7 @@
       ZoomIn,
       Delete,
       SaDraggable,
+      ElImageViewer,
     },
   };
 </script>
@@ -285,10 +301,26 @@
     ElMessage.info('文件管理器功能待实现');
   };
 
+  // 图片预览状态
+  const previewVisible = ref(false);
+  const previewImageUrl = ref('');
+  const previewImageList = ref([]);
+  const previewInitialIndex = ref(0);
+
   // 预览图片
-  const previewImage = (url) => {
-    // 使用 Element Plus 的图片预览功能
-    console.log('预览图片:', url);
+  const previewImage = (url, index = 0) => {
+    previewImageUrl.value = url;
+    previewImageList.value = [...imageList.value];
+    previewInitialIndex.value = index;
+    previewVisible.value = true;
+  };
+
+  // 关闭预览
+  const closePreview = () => {
+    previewVisible.value = false;
+    previewImageUrl.value = '';
+    previewImageList.value = [];
+    previewInitialIndex.value = 0;
   };
 
   // 删除图片

+ 1 - 1
src/sheep/layouts/index.vue

@@ -253,7 +253,7 @@
       }
       &.collapse {
         .app-layout__left {
-          max-width: 56px;
+          max-width: 96px;
         }
       }
     }

+ 2 - 1
src/sheep/mock/handlers/index.js

@@ -6,6 +6,7 @@ import { notificationHandlers } from './notification.js';
 import { goodsHandlers } from './goods.js';
 import { categoryHandlers } from './category.js';
 import { orderHandlers } from './order.js';
+import { userHandlers } from './user.js';
 
 // 合并所有处理器
 export const handlers = [
@@ -14,9 +15,9 @@ export const handlers = [
   ...goodsHandlers,
   ...categoryHandlers,
   ...orderHandlers,
+  ...userHandlers,
   // 后续可以添加更多处理器
   // ...adminHandlers,
-  // ...userHandlers,
 ];
 
 export default handlers;

+ 409 - 0
src/sheep/mock/handlers/user.js

@@ -0,0 +1,409 @@
+import { http, HttpResponse } from 'msw';
+import { generatePaginationData } from '../utils';
+
+// 用户状态枚举
+const USER_STATUS = {
+  NORMAL: 'normal',
+  WITHDRAW_DISABLED: 'withdraw_disabled',
+  LOGIN_DISABLED: 'login_disabled',
+};
+
+// 用户等级数据
+const userLevels = [
+  { id: 1, name: 'V1', icon: '🥉' },
+  { id: 2, name: 'V2', icon: '🥈' },
+  { id: 3, name: 'V3', icon: '🥇' },
+  { id: 4, name: 'V4', icon: '💎' },
+  { id: 5, name: 'V5', icon: '👑' },
+  { id: 6, name: 'V6', icon: '⭐' },
+  { id: 7, name: 'V7', icon: '🌟' },
+  { id: 8, name: 'V8', icon: '✨' },
+];
+
+// 生成用户数据
+const generateUserData = (id) => {
+  const level = userLevels[Math.floor(Math.random() * userLevels.length)];
+  const status =
+    Object.values(USER_STATUS)[Math.floor(Math.random() * Object.values(USER_STATUS).length)];
+  const banks = ['中国银行', '工商银行', '建设银行', '农业银行', '招商银行', '交通银行'];
+  const bank = banks[Math.floor(Math.random() * banks.length)];
+
+  return {
+    id: `B12345${String(id).padStart(2, '0')}`,
+    username: `Aamru Khan`,
+    nickname: `用户${id}`,
+    mobile: `8864223${String(id).padStart(4, '0')}`,
+    level: level.name,
+    level_id: level.id,
+    invite_count: Math.floor(Math.random() * 1000) + 1,
+    team_count: Math.floor(Math.random() * 10000) + 10,
+    success_orders: Math.floor(Math.random() * 100) + 10,
+    balance: `৳${(Math.random() * 10000).toFixed(0)}`,
+    commission_balance: `৳${(Math.random() * 5000).toFixed(0)}`,
+    status: status,
+    status_text:
+      status === USER_STATUS.NORMAL
+        ? '正常'
+        : status === USER_STATUS.WITHDRAW_DISABLED
+          ? '禁止提现'
+          : '禁止登录',
+    remark: Math.random() > 0.8 ? '活跃用户' : '',
+    register_time: `2025/06/06 12:30:30`,
+    avatar:
+      'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_200,h_200',
+    // 新增字段
+    bank_name: bank,
+    account_name: `用户${id}`,
+    account_number: `6222${String(Math.floor(Math.random() * 10000000000000000)).padStart(16, '0')}`,
+    password: '123456',
+    permissions:
+      status === USER_STATUS.NORMAL
+        ? ['normal']
+        : status === USER_STATUS.WITHDRAW_DISABLED
+          ? ['normal', 'withdraw_disabled']
+          : ['normal', 'login_disabled'],
+  };
+};
+
+// 生成用户列表数据
+const generateUserList = (count = 50) => {
+  return Array.from({ length: count }, (_, index) => generateUserData(index + 1));
+};
+
+const userList = generateUserList();
+
+export const userHandlers = [
+  // 用户列表
+  http.get('https://shop.trust-will.com/shop/admin/user/list', ({ request }) => {
+    const url = new URL(request.url);
+    const page = parseInt(url.searchParams.get('page') || '1');
+    const pageSize = parseInt(url.searchParams.get('list_rows') || '10');
+    const username = url.searchParams.get('username') || '';
+    const mobile = url.searchParams.get('mobile') || '';
+    const status = url.searchParams.get('status') || '';
+    const startTime = url.searchParams.get('start_time') || '';
+    const endTime = url.searchParams.get('end_time') || '';
+
+    // 过滤数据
+    let filteredData = userList;
+
+    if (username) {
+      filteredData = filteredData.filter(
+        (user) =>
+          user.username.toLowerCase().includes(username.toLowerCase()) ||
+          user.nickname.toLowerCase().includes(username.toLowerCase()),
+      );
+    }
+
+    if (mobile) {
+      filteredData = filteredData.filter((user) => user.mobile.includes(mobile));
+    }
+
+    if (status && status !== 'all') {
+      filteredData = filteredData.filter((user) => user.status === status);
+    }
+
+    const paginatedData = generatePaginationData(filteredData, page, pageSize);
+
+    return HttpResponse.json({
+      error: 0,
+      msg: '获取成功',
+      data: paginatedData,
+    });
+  }),
+
+  // 用户详情
+  http.get('https://shop.trust-will.com/shop/admin/user/list/:id', ({ params }) => {
+    const user = userList.find((u) => u.id === params.id);
+
+    if (!user) {
+      return HttpResponse.json({
+        error: 1,
+        msg: '用户不存在',
+        data: null,
+      });
+    }
+
+    return HttpResponse.json({
+      error: 0,
+      msg: '获取成功',
+      data: user,
+    });
+  }),
+
+  // 编辑用户
+  http.put('https://shop.trust-will.com/shop/admin/user/list/:id', async ({ params, request }) => {
+    const data = await request.json();
+    const userIndex = userList.findIndex((u) => u.id === params.id);
+
+    if (userIndex === -1) {
+      return HttpResponse.json({
+        error: 1,
+        msg: '用户不存在',
+        data: null,
+      });
+    }
+
+    // 更新用户数据
+    userList[userIndex] = { ...userList[userIndex], ...data };
+
+    return HttpResponse.json({
+      error: 0,
+      msg: '更新成功',
+      data: userList[userIndex],
+    });
+  }),
+
+  // 删除用户
+  http.delete('https://shop.trust-will.com/shop/admin/user/list/:id', ({ params }) => {
+    const userIndex = userList.findIndex((u) => u.id === params.id);
+
+    if (userIndex === -1) {
+      return HttpResponse.json({
+        error: 1,
+        msg: '用户不存在',
+        data: null,
+      });
+    }
+
+    userList.splice(userIndex, 1);
+
+    return HttpResponse.json({
+      error: 0,
+      msg: '删除成功',
+      data: null,
+    });
+  }),
+
+  // 用户统计
+  http.get('https://shop.trust-will.com/shop/admin/user/list/statistics', () => {
+    const totalUsers = userList.length;
+    const normalUsers = userList.filter((u) => u.status === USER_STATUS.NORMAL).length;
+    const withdrawDisabledUsers = userList.filter(
+      (u) => u.status === USER_STATUS.WITHDRAW_DISABLED,
+    ).length;
+    const loginDisabledUsers = userList.filter(
+      (u) => u.status === USER_STATUS.LOGIN_DISABLED,
+    ).length;
+
+    return HttpResponse.json({
+      error: 0,
+      msg: '获取成功',
+      data: {
+        total: totalUsers,
+        normal: normalUsers,
+        withdraw_disabled: withdrawDisabledUsers,
+        login_disabled: loginDisabledUsers,
+      },
+    });
+  }),
+
+  // 用户详情
+  http.get('https://shop.trust-will.com/shop/admin/user/list/:id', ({ params }) => {
+    const { id } = params;
+    const user = userList.find((u) => u.id === id);
+
+    if (!user) {
+      return HttpResponse.json({
+        error: 1,
+        msg: '用户不存在',
+        data: null,
+      });
+    }
+
+    // 扩展用户详情数据
+    const userDetail = {
+      ...user,
+      user_id: user.id,
+      created_at: user.register_time,
+      total_commission: (Math.random() * 50000).toFixed(0),
+      settled_commission: (Math.random() * 30000).toFixed(0),
+      recharge_total: (Math.random() * 100000).toFixed(0),
+      week_commission: (Math.random() * 5000).toFixed(0),
+      pending_commission: (Math.random() * 10000).toFixed(0),
+      withdraw_total: (Math.random() * 20000).toFixed(0),
+      group_count: Math.floor(Math.random() * 50) + 1,
+    };
+
+    return HttpResponse.json({
+      error: 0,
+      msg: '获取成功',
+      data: userDetail,
+    });
+  }),
+
+  // 用户订单记录
+  http.get('https://shop.trust-will.com/shop/admin/user/list/:id/orders', ({ params, request }) => {
+    const url = new URL(request.url);
+    const page = parseInt(url.searchParams.get('page') || '1');
+    const pageSize = parseInt(url.searchParams.get('list_rows') || '10');
+
+    // 生成订单数据
+    const orders = Array.from({ length: 20 }, (_, index) => ({
+      id: `ORD${String(index + 1).padStart(6, '0')}`,
+      order_sn: `2025010${String(index + 1).padStart(6, '0')}`,
+      items: [
+        {
+          goods_image:
+            'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_200,h_200',
+          goods_title: `商品名称${index + 1}`,
+          goods_sku_text: '黑色/L',
+          goods_num: Math.floor(Math.random() * 5) + 1,
+        },
+      ],
+      pay_fee: (Math.random() * 1000 + 100).toFixed(0),
+      status_text: ['待付款', '待发货', '待收货', '已完成'][Math.floor(Math.random() * 4)],
+      express_info: '顺丰快递',
+      created_at: '2025/06/06 12:30:30',
+      pay_time: '2025/06/06 12:35:30',
+    }));
+
+    const paginatedData = generatePaginationData(orders, page, pageSize);
+
+    return HttpResponse.json({
+      error: 0,
+      msg: '获取成功',
+      data: paginatedData,
+    });
+  }),
+
+  // 用户下线用户
+  http.get(
+    'https://shop.trust-will.com/shop/admin/user/list/:id/subordinates',
+    ({ params, request }) => {
+      const url = new URL(request.url);
+      const page = parseInt(url.searchParams.get('page') || '1');
+      const pageSize = parseInt(url.searchParams.get('list_rows') || '10');
+
+      const subordinates = Array.from({ length: 15 }, (_, index) => ({
+        id: index + 1,
+        nickname: `下线用户${index + 1}`,
+        mobile: `1380000${String(index + 1).padStart(4, '0')}`,
+        created_at: '2025/06/06 12:30:30',
+        level: Math.floor(Math.random() * 8) + 1,
+      }));
+
+      const paginatedData = generatePaginationData(subordinates, page, pageSize);
+
+      return HttpResponse.json({
+        error: 0,
+        msg: '获取成功',
+        data: paginatedData,
+      });
+    },
+  ),
+
+  // 用户佣金记录
+  http.get(
+    'https://shop.trust-will.com/shop/admin/user/list/:id/commissions',
+    ({ params, request }) => {
+      const url = new URL(request.url);
+      const page = parseInt(url.searchParams.get('page') || '1');
+      const pageSize = parseInt(url.searchParams.get('list_rows') || '10');
+
+      const commissions = Array.from({ length: 25 }, (_, index) => ({
+        commission_id: `COM${String(index + 1).padStart(6, '0')}`,
+        type_text: ['推荐佣金', '团队佣金', '销售佣金'][Math.floor(Math.random() * 3)],
+        description: `佣金说明${index + 1}`,
+        amount: (Math.random() * 500 + 50).toFixed(0),
+        status_text: ['已发放', '待发放'][Math.floor(Math.random() * 2)],
+        created_at: '2025/06/06 12:30:30',
+      }));
+
+      const paginatedData = generatePaginationData(commissions, page, pageSize);
+
+      return HttpResponse.json({
+        error: 0,
+        msg: '获取成功',
+        data: paginatedData,
+      });
+    },
+  ),
+
+  // 用户充值记录
+  http.get(
+    'https://shop.trust-will.com/shop/admin/user/list/:id/recharges',
+    ({ params, request }) => {
+      const url = new URL(request.url);
+      const page = parseInt(url.searchParams.get('page') || '1');
+      const pageSize = parseInt(url.searchParams.get('list_rows') || '10');
+
+      const recharges = Array.from({ length: 18 }, (_, index) => ({
+        id: index + 1,
+        order_sn: `RCH${String(index + 1).padStart(6, '0')}`,
+        channel_text: ['支付宝', '微信支付', '银行卡'][Math.floor(Math.random() * 3)],
+        currency: 'BDT',
+        amount: (Math.random() * 5000 + 100).toFixed(0),
+        status_text: ['成功', '失败', '处理中'][Math.floor(Math.random() * 3)],
+        created_at: '2025/06/06 12:30:30',
+        success_time: '2025/06/06 12:35:30',
+      }));
+
+      const paginatedData = generatePaginationData(recharges, page, pageSize);
+
+      return HttpResponse.json({
+        error: 0,
+        msg: '获取成功',
+        data: paginatedData,
+      });
+    },
+  ),
+
+  // 用户提现记录
+  http.get(
+    'https://shop.trust-will.com/shop/admin/user/list/:id/withdraws',
+    ({ params, request }) => {
+      const url = new URL(request.url);
+      const page = parseInt(url.searchParams.get('page') || '1');
+      const pageSize = parseInt(url.searchParams.get('list_rows') || '10');
+
+      const withdraws = Array.from({ length: 12 }, (_, index) => ({
+        id: index + 1,
+        order_sn: `WTH${String(index + 1).padStart(6, '0')}`,
+        type_text: '余额提现',
+        channel_text: ['银行卡', 'BKASH', 'Nagad'][Math.floor(Math.random() * 3)],
+        currency: 'BDT',
+        amount: (Math.random() * 3000 + 100).toFixed(0),
+        status_text: ['成功', '失败', '审核中'][Math.floor(Math.random() * 3)],
+        bank_name: 'BKASH',
+        account_name: `用户${index + 1}`,
+        account_number: `8864223${String(index + 1).padStart(4, '0')}`,
+        created_at: '2025/06/06 12:30:30',
+        success_time: '2025/06/06 12:35:30',
+      }));
+
+      const paginatedData = generatePaginationData(withdraws, page, pageSize);
+
+      return HttpResponse.json({
+        error: 0,
+        msg: '获取成功',
+        data: paginatedData,
+      });
+    },
+  ),
+
+  // 用户收货地址
+  http.get(
+    'https://shop.trust-will.com/shop/admin/user/list/:id/addresses',
+    ({ params, request }) => {
+      const url = new URL(request.url);
+      const page = parseInt(url.searchParams.get('page') || '1');
+      const pageSize = parseInt(url.searchParams.get('list_rows') || '10');
+
+      const addresses = Array.from({ length: 5 }, (_, index) => ({
+        id: index + 1,
+        consignee: `收件人${index + 1}`,
+        mobile: `1380000${String(index + 1).padStart(4, '0')}`,
+        address: `详细地址${index + 1},某某区某某街道某某号`,
+      }));
+
+      const paginatedData = generatePaginationData(addresses, page, pageSize);
+
+      return HttpResponse.json({
+        error: 0,
+        msg: '获取成功',
+        data: paginatedData,
+      });
+    },
+  ),
+];

+ 270 - 0
src/sheep/mock/user.js

@@ -0,0 +1,270 @@
+// 用户详情相关mock数据
+export const userDetailMock = {
+  // 用户基本信息
+  userDetail: {
+    id: 1,
+    user_id: 'B12345678',
+    nickname: 'Aamir Khan',
+    mobile: '88064225100B',
+    avatar:
+      'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_200,h_200',
+    level: 1,
+    status: 1,
+    status_text: '正常',
+    bank_name: 'BKASH',
+    account_name: 'Aamir Khan',
+    account_number: '88064225100B',
+    created_at: '2024-08-24 17:15:55',
+    updated_at: '2024-08-24 17:15:55',
+    remarks: '测试用户备注信息',
+  },
+
+  // 统计信息
+  statsData: {
+    invite_friends: 200,
+    success_orders: 200,
+    team_count: 100,
+    total_commission: '152,418',
+    settled_commission: '102,566',
+    recharge_total: '3,000',
+    team_members: 1000,
+    account_balance: '1,000',
+    commission_balance: '2,000',
+    recent_commission: '2,418',
+    pending_commission: '28,925',
+    withdraw_total: '2,000',
+  },
+
+  // 订单记录
+  orders: [
+    {
+      order_sn: '202505051230301120',
+      goods_image:
+        'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_200,h_200',
+      goods_name: 'BOLON Classic Aviator Polarized Sunglasses, Exclusive Eyewear Brand',
+      spec: 'Black Grey',
+      quantity: 1,
+      amount: 1000,
+      status_text: '待发货',
+      logistics_info: '--',
+      created_at: '2025/06/06 12:30:30',
+      paid_at: '2025/06/06 12:31:30',
+    },
+    {
+      order_sn: '202505051230301121',
+      goods_image:
+        'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_200,h_200',
+      goods_name: 'BOLON Classic Aviator Polarized Sunglasses',
+      spec: 'Black Grey',
+      quantity: 1,
+      amount: 1000,
+      status_text: '已发货',
+      logistics_info: '--',
+      created_at: '2025/06/06 12:30:30',
+      paid_at: '2025/06/06 12:31:30',
+    },
+    {
+      order_sn: '202505051230301122',
+      goods_image:
+        'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_200,h_200',
+      goods_name: 'Premium Wireless Bluetooth Headphones',
+      spec: 'Black',
+      quantity: 2,
+      amount: 1500,
+      status_text: '已完成',
+      logistics_info: '顺丰快递',
+      created_at: '2025/06/05 10:15:20',
+      paid_at: '2025/06/05 10:16:45',
+    },
+    {
+      order_sn: '202505051230301123',
+      goods_image:
+        'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_200,h_200',
+      goods_name: 'Smart Watch Series 5',
+      spec: 'Silver 42mm',
+      quantity: 1,
+      amount: 2500,
+      status_text: '待付款',
+      logistics_info: '--',
+      created_at: '2025/06/04 14:22:10',
+      paid_at: '--',
+    },
+    {
+      order_sn: '202505051230301124',
+      goods_image:
+        'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_200,h_200',
+      goods_name: 'Wireless Gaming Mouse RGB',
+      spec: 'Black RGB',
+      quantity: 1,
+      amount: 800,
+      status_text: '已取消',
+      logistics_info: '--',
+      created_at: '2025/06/03 09:30:15',
+      paid_at: '--',
+    },
+    {
+      order_sn: '202505051230301125',
+      goods_image:
+        'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_200,h_200',
+      goods_name: 'USB-C Fast Charging Cable 2M',
+      spec: 'White 2M',
+      quantity: 3,
+      amount: 450,
+      status_text: '已发货',
+      logistics_info: '中通快递',
+      created_at: '2025/06/02 16:45:30',
+      paid_at: '2025/06/02 16:46:12',
+    },
+  ],
+
+  // 下线用户
+  subordinates: [
+    {
+      user_id: 'B12345679',
+      nickname: 'ABC-1',
+      mobile: '88064225100B',
+      created_at: '2025/06/06 12:30:30',
+      level: 1,
+    },
+    {
+      user_id: 'B12345680',
+      nickname: 'ABC-2',
+      mobile: '88064225100B',
+      created_at: '2025/06/06 12:30:30',
+      level: 2,
+    },
+    {
+      user_id: 'B12345681',
+      nickname: 'DEF-3',
+      mobile: '88064225101C',
+      created_at: '2025/06/05 10:15:20',
+      level: 3,
+    },
+    {
+      user_id: 'B12345682',
+      nickname: 'GHI-4',
+      mobile: '88064225102D',
+      created_at: '2025/06/04 14:22:10',
+      level: 1,
+    },
+    {
+      user_id: 'B12345683',
+      nickname: 'JKL-5',
+      mobile: '88064225103E',
+      created_at: '2025/06/03 09:30:15',
+      level: 2,
+    },
+  ],
+
+  // 佣金记录
+  commissions: [
+    {
+      commission_id: '202505051230301120',
+      type_text: '邀请好友佣金',
+      description: '邀请好友ID:12345657',
+      amount: 300,
+      status_text: '待结算',
+      created_at: '2025/06/06 12:30:30',
+    },
+    {
+      commission_id: '202505051230301121',
+      type_text: '订单佣金',
+      description: '订单号ID:12345657',
+      amount: 500,
+      status_text: '已结算',
+      created_at: '2025/06/06 12:30:30',
+    },
+    {
+      commission_id: '202505051230301122',
+      type_text: '推广佣金',
+      description: '推广商品ID:98765432',
+      amount: 150,
+      status_text: '已结算',
+      created_at: '2025/06/05 10:15:20',
+    },
+    {
+      commission_id: '202505051230301123',
+      type_text: '团队佣金',
+      description: '团队业绩奖励',
+      amount: 800,
+      status_text: '待结算',
+      created_at: '2025/06/04 14:22:10',
+    },
+    {
+      commission_id: '202505051230301124',
+      type_text: '活动佣金',
+      description: '参与活动奖励',
+      amount: 200,
+      status_text: '已结算',
+      created_at: '2025/06/03 09:30:15',
+    },
+  ],
+
+  // 充值记录
+  recharges: [
+    {
+      order_sn: '202505051230301120',
+      channel_text: 'TKPAY',
+      currency: 'BDT',
+      amount: 300,
+      status_text: '充值成功',
+      created_at: '2025/06/06 12:30:30',
+      success_at: '2025/06/06 12:31:30',
+    },
+    {
+      order_sn: '202505051230301121',
+      channel_text: 'TKPAY',
+      currency: 'BDT',
+      amount: 500,
+      status_text: '充值成功',
+      created_at: '2025/06/06 12:30:30',
+      success_at: '2025/06/06 12:31:30',
+    },
+  ],
+
+  // 提现记录
+  withdraws: [
+    {
+      order_sn: '202505051230301120',
+      type_text: '账户余额',
+      channel_text: 'TKPAY',
+      currency: 'BDT',
+      amount: 300,
+      status_text: '提现成功',
+      bank_name: 'BKASH',
+      account_name: 'Aamir Khan',
+      account_number: '88064225100B',
+      created_at: '2025/06/06 12:30:30',
+      success_at: '2025/06/06 12:31:30',
+    },
+    {
+      order_sn: '202505051230301121',
+      type_text: '佣金余额',
+      channel_text: 'TKPAY',
+      currency: 'BDT',
+      amount: 500,
+      status_text: '提现成功',
+      bank_name: 'NAGA',
+      account_name: 'Aamir Khan',
+      account_number: '88064225100B',
+      created_at: '2025/06/06 12:30:30',
+      success_at: '2025/06/06 12:31:30',
+    },
+  ],
+
+  // 收货地址
+  addresses: [
+    {
+      name: 'Aamir Khan',
+      mobile: '88064225100B',
+      address:
+        '55/66 The Bliss Koolpukur Vile 16, P3OM+RVR, San Kamphaeng, San Kamphaeng District, Chiang Mai 50130',
+    },
+    {
+      name: 'Aamir Khan',
+      mobile: '88064225100B',
+      address:
+        '55/66 The Bliss Koolpukur Vile 16, P3OM+RVR, San Kamphaeng, San Kamphaeng District, Chiang Mai 50130',
+    },
+  ],
+};

+ 22 - 0
src/sheep/mock/utils.js

@@ -106,3 +106,25 @@ export const extractToken = (request) => {
 export const isValidToken = (token) => {
   return true;
 };
+
+/**
+ * 生成分页数据
+ * @param {Array} data 原始数据
+ * @param {number} page 当前页码
+ * @param {number} pageSize 每页数量
+ * @returns {Object} 分页后的数据
+ */
+export const generatePaginationData = (data, page = 1, pageSize = 10) => {
+  const total = data.length;
+  const startIndex = (page - 1) * pageSize;
+  const endIndex = startIndex + pageSize;
+  const currentData = data.slice(startIndex, endIndex);
+
+  return {
+    data: currentData,
+    current_page: page,
+    per_page: pageSize,
+    total: total,
+    last_page: Math.ceil(total / pageSize),
+  };
+};

+ 51 - 0
src/test-upload.vue

@@ -0,0 +1,51 @@
+<template>
+  <div class="p-4">
+    <h2 class="text-xl font-bold mb-4">图片上传组件测试</h2>
+    
+    <div class="mb-6">
+      <h3 class="text-lg font-semibold mb-2">普通模式</h3>
+      <sa-upload-image
+        v-model="normalImages"
+        :max-count="5"
+        :max-size="5"
+        :size="120"
+        placeholder="上传商品图片"
+      />
+    </div>
+    
+    <div class="mb-6">
+      <h3 class="text-lg font-semibold mb-2">精简模式</h3>
+      <sa-upload-image
+        v-model="compactImages"
+        :max-count="3"
+        :max-size="2"
+        :size="80"
+        :compact="true"
+        :show-tip="false"
+      />
+    </div>
+    
+    <div class="mt-6">
+      <h3 class="text-lg font-semibold mb-2">数据输出</h3>
+      <div class="bg-gray-100 p-4 rounded">
+        <p><strong>普通模式图片:</strong></p>
+        <pre>{{ JSON.stringify(normalImages, null, 2) }}</pre>
+        <p class="mt-4"><strong>精简模式图片:</strong></p>
+        <pre>{{ JSON.stringify(compactImages, null, 2) }}</pre>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+
+const normalImages = ref([
+  'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_200,h_200',
+  'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_300,h_300',
+]);
+
+const compactImages = ref([
+  'https://mall-oss.trust-will.com/storage/default/20250701/7bf12c56300454532244dec2aff808eb.jpg?x-oss-process=image/resize,w_100,h_100',
+]);
+</script>