Browse Source

feat: 移除无用文件,更新路由结构 完善订单详情页面

叶静 1 month ago
parent
commit
f17978b54e
100 changed files with 3198 additions and 10746 deletions
  1. 105 0
      src/app/admin/api/index.js
  2. 0 37
      src/app/admin/views/auth/adminLog/detail.vue
  3. 0 230
      src/app/admin/views/auth/adminLog/index.vue
  4. 0 297
      src/app/admin/views/config/basic.vue
  5. 0 459
      src/app/admin/views/config/easysms.vue
  6. 0 301
      src/app/admin/views/notification/config/edit.vue
  7. 0 327
      src/app/admin/views/notification/config/index.vue
  8. 0 314
      src/app/file/admin/config.vue
  9. 0 13
      src/app/file/admin/index.vue
  10. 0 55
      src/app/file/api/index.js
  11. 0 35
      src/app/file/routes/index.js
  12. 0 30
      src/app/install/api/index.js
  13. 0 835
      src/app/install/index.vue
  14. 0 12
      src/app/install/routes/index.js
  15. 0 128
      src/app/shop/admin/app/app.service.js
  16. 0 267
      src/app/shop/admin/app/mplive/goods/edit.vue
  17. 0 294
      src/app/shop/admin/app/mplive/goods/index.vue
  18. 0 34
      src/app/shop/admin/app/mplive/index.vue
  19. 0 302
      src/app/shop/admin/app/mplive/room/edit.vue
  20. 0 322
      src/app/shop/admin/app/mplive/room/index.vue
  21. 0 118
      src/app/shop/admin/app/mplive/room/playback.vue
  22. 0 85
      src/app/shop/admin/app/mplive/room/pushUrl.vue
  23. 0 102
      src/app/shop/admin/app/mplive/room/qrcode.vue
  24. 0 126
      src/app/shop/admin/app/mplive/select.vue
  25. 0 196
      src/app/shop/admin/app/scoreShop/edit.vue
  26. 0 340
      src/app/shop/admin/app/scoreShop/index.vue
  27. 0 184
      src/app/shop/admin/app/scoreShop/recyclebin.vue
  28. 0 120
      src/app/shop/admin/app/scoreShop/select.vue
  29. 0 17
      src/app/shop/admin/category/category.service.js
  30. 0 129
      src/app/shop/admin/category/select.vue
  31. 2 3
      src/app/shop/admin/config/componenets/payConfig/edit.vue
  32. 0 17
      src/app/shop/admin/content/banner/banner.service.js
  33. 1 1
      src/app/shop/admin/content/banner/edit.vue
  34. 3 3
      src/app/shop/admin/content/banner/index.vue
  35. 59 0
      src/app/shop/admin/content/content.service.js
  36. 4 4
      src/app/shop/admin/content/notification/edit.vue
  37. 1 1
      src/app/shop/admin/content/notification/index.vue
  38. 0 17
      src/app/shop/admin/content/notification/notification.service.js
  39. 7 7
      src/app/shop/admin/content/sms/edit.vue
  40. 1 1
      src/app/shop/admin/content/sms/index.vue
  41. 0 17
      src/app/shop/admin/content/sms/sms.service.js
  42. 0 33
      src/app/shop/admin/coupon/coupon.service.js
  43. 0 489
      src/app/shop/admin/coupon/edit.vue
  44. 0 506
      src/app/shop/admin/coupon/index.vue
  45. 0 184
      src/app/shop/admin/coupon/recyclebin.vue
  46. 0 197
      src/app/shop/admin/coupon/select.vue
  47. 0 13
      src/app/shop/admin/data/data.service.js
  48. 17 23
      src/app/shop/admin/data/report/index.vue
  49. 0 27
      src/app/shop/admin/data/report/report.service.js
  50. 0 168
      src/app/shop/admin/decorate/designer/preview.vue
  51. 0 1353
      src/app/shop/admin/decorate/page/data.js
  52. 0 173
      src/app/shop/admin/decorate/page/preview.vue
  53. 0 89
      src/app/shop/admin/decorate/template/edit.vue
  54. 0 27
      src/app/shop/admin/finance/commission/commission.service.js
  55. 22 18
      src/app/shop/admin/finance/commission/edit.vue
  56. 13 17
      src/app/shop/admin/finance/commission/index.vue
  57. 99 0
      src/app/shop/admin/finance/finance.service.js
  58. 28 16
      src/app/shop/admin/finance/recharge/edit.vue
  59. 12 14
      src/app/shop/admin/finance/recharge/index.vue
  60. 0 27
      src/app/shop/admin/finance/recharge/recharge.service.js
  61. 8 8
      src/app/shop/admin/finance/withdraw/edit.vue
  62. 21 27
      src/app/shop/admin/finance/withdraw/index.vue
  63. 0 37
      src/app/shop/admin/finance/withdraw/withdraw.service.js
  64. 4 4
      src/app/shop/admin/goods/category/edit.vue
  65. 3 3
      src/app/shop/admin/goods/category/index.vue
  66. 129 0
      src/app/shop/admin/goods/category/select.vue
  67. 12 0
      src/app/shop/admin/goods/goods.service.js
  68. 2 5
      src/app/shop/admin/goods/goods/edit.vue
  69. 1 1
      src/app/shop/admin/goods/goods/select.vue
  70. 15 15
      src/app/shop/admin/marketing/group/edit.vue
  71. 0 17
      src/app/shop/admin/marketing/group/group.service.js
  72. 6 10
      src/app/shop/admin/marketing/group/index.vue
  73. 31 0
      src/app/shop/admin/marketing/marketing.service.js
  74. 0 1
      src/app/shop/admin/order/aftersale/index.vue
  75. 15 3
      src/app/shop/admin/order/order.service.js
  76. 1 0
      src/app/shop/admin/order/order/detail.vue
  77. 1251 0
      src/app/shop/admin/order/order/o-detail.vue
  78. 1207 0
      src/app/shop/admin/order/order/o-index.vue
  79. 23 16
      src/app/shop/admin/order/setting/edit.vue
  80. 1 1
      src/app/shop/admin/order/setting/index.vue
  81. 0 17
      src/app/shop/admin/order/setting/setting.service.js
  82. 0 74
      src/app/shop/admin/user/coupon.vue
  83. 0 190
      src/app/shop/admin/user/couponList.vue
  84. 13 18
      src/app/shop/admin/user/level/edit.vue
  85. 8 12
      src/app/shop/admin/user/level/index.vue
  86. 0 17
      src/app/shop/admin/user/level/level.service.js
  87. 4 4
      src/app/shop/admin/user/list/edit.vue
  88. 4 4
      src/app/shop/admin/user/list/index.vue
  89. 0 17
      src/app/shop/admin/user/list/list.service.js
  90. 0 70
      src/app/shop/admin/user/order.vue
  91. 0 78
      src/app/shop/admin/user/share.vue
  92. 11 8
      src/app/shop/admin/user/tag/edit.vue
  93. 4 4
      src/app/shop/admin/user/tag/index.vue
  94. 0 17
      src/app/shop/admin/user/tag/tag.service.js
  95. 50 6
      src/app/shop/admin/user/user.service.js
  96. 0 75
      src/app/user/admin/components/commission.vue
  97. 0 75
      src/app/user/admin/components/money.vue
  98. 0 75
      src/app/user/admin/components/score.vue
  99. 0 443
      src/app/user/admin/detail.vue
  100. 0 240
      src/app/user/admin/index.vue

+ 105 - 0
src/app/admin/api/index.js

@@ -188,4 +188,109 @@ export default {
         data,
       }),
   },
+
+  // 文件管理
+  file: {
+    // 文件列表
+    list: (params) =>
+      request({
+        url: 'admin/file',
+        method: 'GET',
+        params,
+      }),
+    // 文件详情
+    detail: (id) =>
+      request({
+        url: `admin/file/${id}`,
+        method: 'GET',
+      }),
+    // 文件上传
+    upload: (params, data) =>
+      request({
+        url: 'admin/file/upload',
+        method: 'POST',
+        params,
+        data,
+        headers: {
+          'Content-Type': 'multipart/form-data',
+        },
+      }),
+    // 删除文件
+    delete: (id) =>
+      request({
+        url: `admin/file/${id}`,
+        method: 'DELETE',
+        options: {
+          showSuccessMessage: true,
+        },
+      }),
+    // 批量删除文件
+    batchDelete: (ids) =>
+      request({
+        url: 'admin/file/batchDelete',
+        method: 'POST',
+        data: { ids },
+        options: {
+          showSuccessMessage: true,
+        },
+      }),
+    // 移动文件到分组
+    move: (data) =>
+      request({
+        url: 'admin/file/move',
+        method: 'POST',
+        data,
+        options: {
+          showSuccessMessage: true,
+        },
+      }),
+    // 重命名文件
+    rename: (id, data) =>
+      request({
+        url: `admin/file/rename/${id}`,
+        method: 'PUT',
+        data,
+        options: {
+          showSuccessMessage: true,
+        },
+      }),
+    // 文件分组管理
+    group: {
+      // 分组列表
+      list: () =>
+        request({
+          url: 'admin/file/group',
+          method: 'GET',
+        }),
+      // 添加分组
+      add: (data) =>
+        request({
+          url: 'admin/file/group',
+          method: 'POST',
+          data,
+          options: {
+            showSuccessMessage: true,
+          },
+        }),
+      // 编辑分组
+      edit: (id, data) =>
+        request({
+          url: `admin/file/group/${id}`,
+          method: 'PUT',
+          data,
+          options: {
+            showSuccessMessage: true,
+          },
+        }),
+      // 删除分组
+      delete: (id) =>
+        request({
+          url: `admin/file/group/${id}`,
+          method: 'DELETE',
+          options: {
+            showSuccessMessage: true,
+          },
+        }),
+    },
+  },
 };

+ 0 - 37
src/app/admin/views/auth/adminLog/detail.vue

@@ -1,37 +0,0 @@
-<template>
-  <el-container>
-    <el-main>
-      <el-form :model="modal.params.detail" label-position="left" label-width="100px">
-        <el-form-item label="标题"> 内容 </el-form-item>
-        <el-form-item label="ID">
-          {{ modal.params.detail.id }}
-        </el-form-item>
-        <el-form-item label="管理员昵称">
-          {{ modal.params.detail.nickname }}
-        </el-form-item>
-        <el-form-item label="链接">
-          {{ modal.params.detail.url }}
-        </el-form-item>
-        <el-form-item label="描述">
-          {{ modal.params.detail.description }}
-        </el-form-item>
-        <el-form-item label="操作内容">
-          {{ modal.params.detail.params }}
-        </el-form-item>
-        <el-form-item label="IP">
-          {{ modal.params.detail.ip }}
-        </el-form-item>
-        <el-form-item label="user agent">
-          {{ modal.params.detail.useragent }}
-        </el-form-item>
-        <el-form-item label="创建时间">
-          {{ modal.params.detail.create_time }}
-        </el-form-item>
-      </el-form>
-    </el-main>
-  </el-container>
-</template>
-
-<script setup>
-  const props = defineProps(['modal']);
-</script>

+ 0 - 230
src/app/admin/views/auth/adminLog/index.vue

@@ -1,230 +0,0 @@
-<template>
-  <el-container class="adminlog-view panel-block">
-    <el-header class="sa-header">
-      <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 class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
-          <el-button class="sa-button-refresh" icon="Search" @click="openFilter"></el-button>
-        </div>
-      </div>
-    </el-header>
-    <el-main class="sa-p-0" v-loading="loading">
-      <el-table class="sa-table" height="100%" :data="table.data" @row-dblclick="detailRow" stripe>
-        <template #empty>
-          <sa-empty />
-        </template>
-        <el-table-column prop="id" label="ID" min-width="100"> </el-table-column>
-        <el-table-column label="访问地址" min-width="160">
-          <template #default="scope">
-            <span class="sa-table-line-1">
-              {{ scope.row.url || '-' }}
-            </span>
-          </template>
-        </el-table-column>
-        <el-table-column label="描述" min-width="260">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.description }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="请求参数" min-width="260">
-          <template #default="scope">
-            <div class="sa-flex">
-              <el-input v-model="scope.row.params" disabled>
-                <template #append>
-                  <span class="cursor-pointer" @click="useClip(scope.row.params)">复制</span>
-                </template>
-              </el-input>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="IP" min-width="140">
-          <template #default="scope">
-            <div class="table-ip">
-              {{ scope.row.ip || '-' }}
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="管理员" min-width="160">
-          <template #default="scope">
-            <sa-user-profile :user="scope.row.admin" :id="scope.row.admin_id" />
-          </template>
-        </el-table-column>
-        <el-table-column label="创建时间" min-width="172">
-          <template #default="scope">
-            {{ scope.row.create_time || '-' }}
-          </template>
-        </el-table-column>
-        <el-table-column fixed="right" label="操作">
-          <template #default="scope">
-            <el-button class="is-link" type="primary" @click="detailRow(scope.row)">详情</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-main>
-    <sa-view-bar>
-      <template #right>
-        <sa-pagination :pageData="pageData" @updateFn="getData" />
-      </template>
-    </sa-view-bar>
-  </el-container>
-</template>
-
-<script>
-  export default {
-    name: 'admin.auth.adminlog',
-  };
-</script>
-
-<script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import admin from '@/app/admin/api';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { useSearch } from '@/sheep/components/sa-table/sa-search/useSearch';
-  import useClip from '@/sheep/utils/clipboard.js';
-  import AdminLogDetail from './detail.vue';
-  import { composeFilter } from '@/sheep/utils';
-  import { cloneDeep } from 'lodash';
-
-  const filterParams = reactive({
-    tools: {
-      keyword: {
-        type: 'tinputprepend',
-        placeholder: '请输入查询内容',
-        field: 'keyword',
-        keyword: {
-          field: 'id',
-          value: '',
-        },
-        options: [
-          {
-            label: 'ID',
-            value: 'id',
-          },
-          {
-            label: '访问地址',
-            value: 'url',
-          },
-          {
-            label: '描述',
-            value: 'description',
-          },
-        ],
-      },
-      admin: {
-        type: 'tinputprepend',
-        placeholder: '请输入查询内容',
-        field: 'admin',
-        admin: {
-          field: 'admin_id',
-          value: '',
-        },
-        options: [
-          {
-            label: '管理员ID',
-            value: 'admin_id',
-          },
-          {
-            label: '账号',
-            value: 'admin.account',
-          },
-          {
-            label: '昵称',
-            value: 'admin.nickname',
-          },
-          {
-            label: '手机号',
-            value: 'admin.mobile',
-          },
-        ],
-      },
-      create_time: {
-        type: 'tdatetimerange',
-        label: '更新时间',
-        field: 'create_time',
-        value: [],
-      },
-    },
-    data: {
-      keyword: { field: 'id', value: '' },
-      admin: { field: 'admin_id', value: '' },
-      create_time: [],
-    },
-    conditionLabel: {},
-  });
-  const { openFilter, deleteFilter } = useSearch({
-    filterParams,
-    getData,
-  });
-
-  const { pageData } = usePagination();
-
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-  });
-  const loading = ref(true);
-  // 获取
-  async function getData(page) {
-    loading.value = true;
-    if (page) pageData.page = page;
-    let tempSearch = cloneDeep(filterParams.data);
-    let search = composeFilter(tempSearch, {
-      url: 'like',
-      description: 'like',
-      'admin.account': 'like',
-      'admin.nickname': 'like',
-      'admin.mobile': 'like',
-      create_time: 'range',
-    });
-    const { error, data } = await admin.auth.adminLog.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      ...search,
-      order: table.order,
-      sort: table.sort,
-    });
-    if (error === 0) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
-    }
-    loading.value = false;
-  }
-
-  function detailRow(row) {
-    useModal(
-      AdminLogDetail,
-      {
-        title: '详情',
-        type: 'detail',
-        detail: row,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>
-<style lang="scss" scoped>
-  .adminlog-view {
-    .table-ip {
-      color: var(--el-color-primary);
-    }
-  }
-</style>

+ 0 - 297
src/app/admin/views/config/basic.vue

@@ -1,297 +0,0 @@
-<template>
-  <el-container class="panel-block">
-    <el-header class="sa-header">
-      <el-tabs class="sa-tabs" v-model="configType" @tab-change="getData">
-        <el-tab-pane v-for="c in configList" :key="c" :label="c.label" :name="c.api"></el-tab-pane>
-      </el-tabs>
-    </el-header>
-    <el-main class="sa-p-t-30">
-      <el-row :gutter="16">
-        <el-col :xs="24" :sm="20" :md="18" :lg="14" :xl="14">
-          <template v-if="configType == 'admin.config.basic.site'">
-            <el-form
-              :model="state.site.model"
-              :rules="state.site.rules"
-              ref="siteRef"
-              label-width="180px"
-            >
-              <el-form-item label="Logo" prop="logo">
-                <sa-uploader v-model="state.site.model.logo" fileType="image"></sa-uploader>
-              </el-form-item>
-              <el-form-item label="站点名称" prop="name">
-                <el-input placeholder="请输入站点名称" v-model="state.site.model.name"></el-input>
-              </el-form-item>
-              <el-form-item label="版本号" prop="version">
-                <el-input placeholder="请输入版本号" v-model="state.site.model.version"></el-input>
-              </el-form-item>
-              <el-form-item label="版权信息">
-                <el-row :gutter="16" class="version-copy">
-                  <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-                    <el-form-item prop="copyright">
-                      <el-input
-                        placeholder="请输入版权信息"
-                        v-model="state.site.model.copyright"
-                      ></el-input>
-                    </el-form-item>
-                  </el-col>
-                  <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-                    <el-form-item prop="copytime">
-                      <el-input
-                        placeholder="请输入版权信息"
-                        v-model="state.site.model.copytime"
-                      ></el-input>
-                    </el-form-item>
-                  </el-col>
-                </el-row>
-              </el-form-item>
-              <!-- <el-form-item label="全站Cdn地址" prop="cdnurl">
-                <el-input
-                  placeholder="请输入全站Cdn地址"
-                  v-model="state.site.model.cdnurl"
-                ></el-input>
-              </el-form-item> -->
-              <el-form-item label="备案号" prop="beian">
-                <el-input placeholder="请输入备案号" v-model="state.site.model.beian"></el-input>
-              </el-form-item>
-              <el-form-item label="工信部网址" prop="beian_url">
-                <el-input
-                  placeholder="请输入工信部网址"
-                  v-model="state.site.model.beian_url"
-                ></el-input>
-              </el-form-item>
-            </el-form>
-          </template>
-          <template v-if="configType == 'admin.config.basic.login'">
-            <el-form
-              :model="state.login.model"
-              :rules="state.login.rules"
-              ref="loginRef"
-              label-width="180px"
-            >
-              <el-form-item label="登录背景" prop="bg">
-                <sa-uploader v-model="state.login.model.bg" fileType="image"></sa-uploader>
-              </el-form-item>
-              <el-form-item label="登录验证码" prop="captcha" required>
-                <el-radio-group v-model="state.login.model.captcha">
-                  <el-radio label="none">关闭验证码</el-radio>
-                  <el-radio label="code">图片验证码</el-radio>
-                  <!-- <el-radio label="geetest">极验验证码</el-radio> -->
-                </el-radio-group>
-              </el-form-item>
-              <template v-if="state.login.model.captcha == 'code'">
-                <div class="sa-title--line sa-m-b-30">图片验证码</div>
-                <el-form-item label="算数验证码" prop="captcha_code.math" required>
-                  <el-switch
-                    v-model="state.login.model.captcha_code.math"
-                    :active-value="1"
-                    :inactive-value="0"
-                  ></el-switch>
-                </el-form-item>
-              </template>
-              <template v-if="state.login.model.captcha == 'geetest'">
-                <div class="sa-title--line sa-m-b-30">极验验证码</div>
-                <el-form-item label="极验Captchald" prop="captcha_geetest.captcha_id">
-                  <el-input
-                    placeholder="请输入Captchald"
-                    v-model="state.login.model.captcha_geetest.captcha_id"
-                  ></el-input>
-                </el-form-item>
-                <el-form-item label="极验PrivateKey" prop="captcha_geetest.private_key">
-                  <el-input
-                    placeholder="请输入PrivateKey"
-                    v-model="state.login.model.captcha_geetest.private_key"
-                  ></el-input>
-                </el-form-item>
-              </template>
-            </el-form>
-          </template>
-          <template v-if="configType == 'admin.config.basic.user'">
-            <el-form
-              :model="state.user.model"
-              :rules="state.user.rules"
-              ref="userRef"
-              label-width="180px"
-            >
-              <el-form-item label="默认头像" prop="avatar">
-                <sa-uploader v-model="state.user.model.avatar" fileType="image"></sa-uploader>
-              </el-form-item>
-              <el-form-item label="默认昵称" prop="nickname">
-                <el-input
-                  placeholder="请输入默认昵称"
-                  v-model="state.user.model.nickname"
-                ></el-input>
-              </el-form-item>
-            </el-form>
-          </template>
-          <template v-if="configType == 'admin.config.basic.mail'">
-            <el-form
-              :model="state.mail.model"
-              :rules="state.mail.rules"
-              ref="mailRef"
-              label-width="180px"
-            >
-              <el-form-item label="邮件服务器" prop="smtp_host">
-                <el-input
-                  placeholder="请输入邮件服务器"
-                  v-model="state.mail.model.smtp_host"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="邮件服务器端口" prop="smtp_port">
-                <el-input
-                  placeholder="请输入邮件服务器端口"
-                  v-model="state.mail.model.smtp_port"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="邮件服务用户名" prop="smtp_user">
-                <el-input
-                  placeholder="请输入邮件服务用户名"
-                  v-model="state.mail.model.smtp_user"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="邮件服务密码" prop="smtp_pass">
-                <el-input
-                  placeholder="请输入邮件服务密码"
-                  v-model="state.mail.model.smtp_pass"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="发件人邮箱" prop="from">
-                <el-input placeholder="请输入发件人邮箱" v-model="state.mail.model.from"></el-input>
-              </el-form-item>
-              <el-form-item label="发件人姓名" prop="from_name">
-                <el-input
-                  placeholder="请输入发件人姓名"
-                  v-model="state.mail.model.from_name"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="邮件服务验证方式">
-                <el-select
-                  v-model="state.mail.model.verify_type"
-                  placeholder="请选择邮件服务验证方式"
-                >
-                  <el-option
-                    v-for="item in mailTemplate"
-                    :key="item.value"
-                    :label="item.name"
-                    :value="item.value"
-                  ></el-option>
-                </el-select>
-              </el-form-item>
-            </el-form>
-          </template>
-        </el-col>
-      </el-row>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button @click="getData">重置</el-button>
-      <el-button type="primary" @click="onUpdate">保存</el-button>
-    </el-footer>
-  </el-container>
-</template>
-
-<script>
-  export default {
-    name: 'admin.config.basic',
-  };
-</script>
-
-<script setup>
-  import { reactive, ref, onMounted, computed, getCurrentInstance } from 'vue';
-  import admin from '@/app/admin/api';
-  import { checkAuth } from '@/sheep/directives/auth';
-
-  const { proxy } = getCurrentInstance();
-
-  const configList = computed(() => {
-    const config = [
-      { label: '站点配置', api: 'admin.config.basic.site' },
-      { label: '登录配置', api: 'admin.config.basic.login' },
-      { label: '用户配置', api: 'admin.config.basic.user' },
-      { label: '邮件配置', api: 'admin.config.basic.mail' },
-    ];
-    return config.filter((c) => {
-      if (checkAuth(c.api)) return c;
-    });
-  });
-  const configType = ref(configList.value.length > 0 ? configList.value[0].api : '');
-
-  const mailTemplate = [
-    { name: '无', value: '' },
-    { name: 'SSL', value: 'ssl' },
-    { name: 'TLS', value: 'tls' },
-  ];
-
-  // 表单数据
-  const state = reactive({
-    site: {
-      model: {},
-      // rules: {
-      //   logo: [{ required: true, message: "请选择系统Logo", trigger: "blur" }],
-      //   name: [{ required: true, message: "请输入系统名称", trigger: "blur" }],
-      //   version: [{ required: true, message: "请输入版本号", trigger: "blur" }],
-      //   copyright: [
-      //     { required: true, message: "请输入版权信息", trigger: "blur" },
-      //   ],
-      //   copytime: [
-      //     { required: true, message: "请输入版权时间", trigger: "blur" },
-      //   ],
-      //   cdnurl: [
-      //     { required: true, message: "请输入全站Cdn地址", trigger: "blur" },
-      //   ],
-      //   beian: [{ required: true, message: "请输入备案号", trigger: "blur" }],
-      //   beian_url: [
-      //     { required: true, message: "请输入工信部网址", trigger: "blur" },
-      //   ],
-      // },
-    },
-    login: {
-      model: {},
-      rules: {
-        bg: [{ required: true, message: '请选择登录背景', trigger: 'blur' }],
-        captcha_geetest: {
-          captcha_id: [{ required: true, message: '请输入极验CaptchaId', trigger: 'blur' }],
-          private_key: [{ required: true, message: '请输入极验PrivateKey', trigger: 'blur' }],
-        },
-      },
-    },
-    user: {
-      model: {},
-      rules: {
-        avatar: [{ required: true, message: '请选择默认头像', trigger: 'blur' }],
-        nickname: [{ required: true, message: '请输入默认昵称', trigger: 'blur' }],
-      },
-    },
-    mail: {
-      model: {},
-      rules: {
-        smtp_host: [{ required: true, message: '请输入邮件服务器', trigger: 'blur' }],
-        smtp_port: [{ required: true, message: '请输入邮件服务器端口', trigger: 'blur' }],
-        smtp_user: [{ required: true, message: '请输入邮件服务用户名', trigger: 'blur' }],
-        smtp_pass: [{ required: true, message: '请输入邮件服务密码', trigger: 'blur' }],
-        from: [{ required: true, message: '请输入发件人邮箱', trigger: 'blur' }],
-        from_name: [{ required: true, message: '请输入发件人姓名', trigger: 'blur' }],
-      },
-    },
-  });
-
-  // 获取配置信息
-  async function getData() {
-    let type = configType.value.split('.').pop();
-    const { data } = await admin.config.basic[type]();
-    state[type].model = data;
-  }
-
-  // 提交保存
-  function onUpdate() {
-    let type = configType.value.split('.').pop();
-    const form = proxy.$refs[type + 'Ref'];
-    form.validate((valid) => {
-      valid && admin.config.basic[type](state[type].model);
-    });
-  }
-
-  onMounted(() => {
-    if (configList.value.length > 0) {
-      getData();
-    }
-  });
-</script>

+ 0 - 459
src/app/admin/views/config/easysms.vue

@@ -1,459 +0,0 @@
-<template>
-  <el-container class="panel-block">
-    <el-header class="sa-header">
-      <el-tabs class="sa-tabs">
-        <el-tab-pane label="短信配置"></el-tab-pane>
-      </el-tabs>
-    </el-header>
-    <el-main class="sa-p-t-30">
-      <el-row :gutter="16">
-        <el-col :xs="24" :sm="20" :md="18" :lg="14" :xl="14">
-          <el-form
-            :model="easysms.model"
-            :rules="easysms.rules"
-            ref="easysmsRef"
-            label-width="180px"
-          >
-            <el-form-item label="默认发送网关" prop="gateways" required>
-              <el-radio-group v-model="easysms.model.gateways">
-                <el-radio label="aliyun">阿里云</el-radio>
-                <el-radio label="qcloud">腾讯云</el-radio>
-                <el-radio label="huawei">华为云</el-radio>
-                <el-radio label="smsbao">短信宝</el-radio>
-              </el-radio-group>
-            </el-form-item>
-            <!-- 阿里云 -->
-            <template v-if="easysms.model.gateways == 'aliyun'">
-              <div class="sa-title--line sa-m-b-30">阿里云</div>
-              <el-form-item label="AccessKeyId" prop="gateways_aliyun.access_key_id">
-                <el-input
-                  placeholder="请输入AccessKeyId"
-                  v-model="easysms.model.gateways_aliyun.access_key_id"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="AccessKeySecret" prop="gateways_aliyun.access_key_secret">
-                <el-input
-                  placeholder="请输入AccessKeySecret"
-                  v-model="easysms.model.gateways_aliyun.access_key_secret"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="短信签名" prop="gateways_aliyun.sign_name">
-                <el-input
-                  placeholder="请输入短信签名"
-                  v-model="easysms.model.gateways_aliyun.sign_name"
-                ></el-input>
-              </el-form-item>
-              <!-- template 内容 -->
-              <el-form-item label="短信模板">
-                <div class="warning"> 请不要随意修改和删除下面的默认模板事件 </div>
-                <div class="sa-template-wrap">
-                  <div class="title sa-flex">
-                    <div class="key">事件</div>
-                    <div class="value">模板ID</div>
-                    <div class="oper">操作</div>
-                  </div>
-                  <sa-draggable
-                    v-model="easysms.model.gateways_aliyun.template"
-                    :animation="300"
-                    handle=".sortable-drag"
-                    item-key="element"
-                  >
-                    <template #item="{ element, index }">
-                      <div class="item">
-                        <el-form-item
-                          class="key"
-                          :prop="'gateways_aliyun.template.' + index + '.event'"
-                          :rules="templateRules.event"
-                        >
-                          <el-input placeholder="请输入键" v-model="element.event"></el-input>
-                        </el-form-item>
-                        <el-form-item
-                          class="value"
-                          :prop="'gateways_aliyun.template.' + index + '.value'"
-                          :rules="templateRules.value"
-                        >
-                          <el-input placeholder="请输入值" v-model="element.value"></el-input>
-                        </el-form-item>
-                        <el-form-item class="oper">
-                          <el-button @click="onDeleteTemplate('aliyun', index)" type="danger" plain
-                            >删除</el-button
-                          >
-                          <sa-svg class="sa-m-l-8 sortable-drag" name="sa-round"></sa-svg>
-                        </el-form-item>
-                      </div>
-                    </template>
-                  </sa-draggable>
-                  <el-button
-                    @click="onAddTemplate('aliyun')"
-                    class="sa-m-l-16"
-                    type="primary"
-                    plain
-                    icon="Plus"
-                    >追加</el-button
-                  >
-                </div>
-              </el-form-item>
-            </template>
-            <!-- 腾讯云 -->
-            <template v-if="easysms.model.gateways == 'qcloud'">
-              <div class="sa-title--line sa-m-b-30">腾讯云</div>
-              <!-- <el-form-item label="AppKey" prop="gateways_qcloud.app_key">
-                <el-input
-                  placeholder="请输入AppKey"
-                  v-model="easysms.model.gateways_qcloud.app_key"
-                ></el-input>
-              </el-form-item> -->
-              <el-form-item label="SdkAppId" prop="gateways_qcloud.sdk_app_id">
-                <el-input
-                  placeholder="请输入SdkAppId"
-                  v-model="easysms.model.gateways_qcloud.sdk_app_id"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="SecretId" prop="gateways_qcloud.secret_id">
-                <el-input
-                  placeholder="请输入SecretId"
-                  v-model="easysms.model.gateways_qcloud.secret_id"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="SecretKey" prop="gateways_qcloud.secret_key">
-                <el-input
-                  placeholder="请输入SecretKey"
-                  v-model="easysms.model.gateways_qcloud.secret_key"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="短信签名" prop="gateways_qcloud.sign_name">
-                <el-input
-                  placeholder="请输入短信签名"
-                  v-model="easysms.model.gateways_qcloud.sign_name"
-                ></el-input>
-              </el-form-item>
-              <!-- template 内容 -->
-              <el-form-item label="短信模板">
-                <div class="warning"> 请不要随意修改和删除下面的默认模板事件 </div>
-                <div class="sa-template-wrap">
-                  <div class="title sa-flex">
-                    <div class="key">事件</div>
-                    <div class="value">模板ID</div>
-                    <div class="oper">操作</div>
-                  </div>
-                  <sa-draggable
-                    v-model="easysms.model.gateways_qcloud.template"
-                    :animation="300"
-                    handle=".sortable-drag"
-                    item-key="element"
-                  >
-                    <template #item="{ element, index }">
-                      <div class="item">
-                        <el-form-item
-                          class="key"
-                          :prop="'gateways_qcloud.template.' + index + '.event'"
-                          :rules="templateRules.event"
-                        >
-                          <el-input placeholder="请输入键" v-model="element.event"></el-input>
-                        </el-form-item>
-                        <el-form-item
-                          class="value"
-                          :prop="'gateways_qcloud.template.' + index + '.value'"
-                          :rules="templateRules.value"
-                        >
-                          <el-input placeholder="请输入值" v-model="element.value"></el-input>
-                        </el-form-item>
-                        <el-form-item class="oper">
-                          <el-button @click="onDeleteTemplate('qcloud', index)" type="danger" plain
-                            >删除</el-button
-                          >
-                          <sa-svg class="sa-m-l-8 sortable-drag" name="sa-round"></sa-svg>
-                        </el-form-item>
-                      </div>
-                    </template>
-                  </sa-draggable>
-                  <el-button
-                    @click="onAddTemplate('qcloud')"
-                    class="sa-m-l-16"
-                    type="primary"
-                    plain
-                    icon="Plus"
-                    >追加</el-button
-                  >
-                </div>
-              </el-form-item>
-            </template>
-            <!-- 华为云 -->
-            <template v-if="easysms.model.gateways == 'huawei'">
-              <div class="sa-title--line sa-m-b-30">华为云</div>
-              <el-form-item label="Endpoint" prop="gateways_huawei.endpoint">
-                <el-input
-                  placeholder="请输入Endpoint"
-                  v-model="easysms.model.gateways_huawei.endpoint"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="AppKey" prop="gateways_huawei.app_key">
-                <el-input
-                  placeholder="请输入AppKey"
-                  v-model="easysms.model.gateways_huawei.app_key"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="AppSecret" prop="gateways_huawei.app_secret">
-                <el-input
-                  placeholder="请输入AppSecret"
-                  v-model="easysms.model.gateways_huawei.app_secret"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="短信签名" prop="gateways_huawei.sign_name">
-                <el-input
-                  placeholder="请输入短信签名"
-                  v-model="easysms.model.gateways_huawei.sign_name"
-                ></el-input>
-              </el-form-item>
-              <!-- template 内容 -->
-              <el-form-item label="短信模板">
-                <div class="warning"> 请不要随意修改和删除下面的默认模板事件 </div>
-                <div class="sa-template-wrap">
-                  <div class="title sa-flex">
-                    <div class="key">事件</div>
-                    <div class="value">模板ID</div>
-                    <div class="oper">操作</div>
-                  </div>
-                  <sa-draggable
-                    v-model="easysms.model.gateways_huawei.template"
-                    :animation="300"
-                    handle=".sortable-drag"
-                    item-key="element"
-                  >
-                    <template #item="{ element, index }">
-                      <div class="item">
-                        <el-form-item
-                          class="key"
-                          :prop="'gateways_huawei.template.' + index + '.event'"
-                          :rules="templateRules.event"
-                        >
-                          <el-input placeholder="请输入键" v-model="element.event"></el-input>
-                        </el-form-item>
-                        <el-form-item
-                          class="value"
-                          :prop="'gateways_huawei.template.' + index + '.value'"
-                          :rules="templateRules.value"
-                        >
-                          <el-input placeholder="请输入值" v-model="element.value"></el-input>
-                        </el-form-item>
-                        <el-form-item class="oper">
-                          <el-button @click="onDeleteTemplate('huawei', index)" type="danger" plain
-                            >删除</el-button
-                          >
-                          <sa-svg class="sa-m-l-8 sortable-drag" name="sa-round"></sa-svg>
-                        </el-form-item>
-                      </div>
-                    </template>
-                  </sa-draggable>
-                  <el-button
-                    @click="onAddTemplate('huawei')"
-                    class="sa-m-l-16"
-                    type="primary"
-                    plain
-                    icon="Plus"
-                    >追加</el-button
-                  >
-                </div>
-              </el-form-item>
-            </template>
-            <!-- 短信宝 -->
-            <template v-if="easysms.model.gateways == 'smsbao'">
-              <div class="sa-title--line sa-m-b-30">短信宝</div>
-              <el-form-item label="账号" prop="gateways_smsbao.user">
-                <el-input
-                  placeholder="请输入账号"
-                  v-model="easysms.model.gateways_smsbao.user"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="密码" prop="gateways_smsbao.password">
-                <el-input
-                  placeholder="请输入密码"
-                  v-model="easysms.model.gateways_smsbao.password"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="短信签名" prop="gateways_smsbao.sign_name">
-                <el-input
-                  placeholder="请输入短信签名"
-                  v-model="easysms.model.gateways_smsbao.sign_name"
-                ></el-input>
-              </el-form-item>
-            </template>
-          </el-form>
-        </el-col>
-      </el-row>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button @click="getData">重置</el-button>
-      <el-button type="primary" @click="onUpdate">保存</el-button>
-    </el-footer>
-  </el-container>
-</template>
-
-<script>
-  export default {
-    name: 'admin.config.easysms',
-  };
-</script>
-
-<script setup>
-  import { onMounted, reactive, ref, unref } from 'vue';
-  import admin from '@/app/admin/api';
-  import SaDraggable from 'vuedraggable';
-
-  const easysmsRef = ref();
-  const easysms = reactive({
-    model: {},
-    rules: {
-      gateways_aliyun: {
-        access_key_id: [
-          {
-            required: true,
-            message: '请输入AccessKeyId',
-            trigger: 'blur',
-          },
-        ],
-        access_key_secret: [
-          {
-            required: true,
-            message: '请输入AccessKeySecret',
-            trigger: 'blur',
-          },
-        ],
-        sign_name: [
-          {
-            required: true,
-            message: '请输入短信签名',
-            trigger: 'blur',
-          },
-        ],
-      },
-      gateways_qcloud: {
-        app_key: [
-          {
-            required: true,
-            message: '请输入AppKey',
-            trigger: 'blur',
-          },
-        ],
-        sdk_app_id: [
-          {
-            required: true,
-            message: '请输入SdkAppId',
-            trigger: 'blur',
-          },
-        ],
-        sign_name: [
-          {
-            required: true,
-            message: '请输入短信签名',
-            trigger: 'blur',
-          },
-        ],
-      },
-      gateways_huawei: {
-        // 华为云
-        app_key: [
-          {
-            required: true,
-            message: '请输入AppKey',
-            trigger: 'blur',
-          },
-        ],
-        app_secret: [
-          {
-            required: true,
-            message: '请输入AppSecret',
-            trigger: 'blur',
-          },
-        ],
-        endpoint: [
-          {
-            required: true,
-            message: '请输入Endpoint',
-            trigger: 'blur',
-          },
-        ],
-        sign_name: [
-          {
-            required: true,
-            message: '请输入短信签名',
-            trigger: 'blur',
-          },
-        ],
-      },
-      gateways_smsbao: {
-        user: [
-          {
-            required: true,
-            message: '请输入账号',
-            trigger: 'blur',
-          },
-        ],
-        password: [
-          {
-            required: true,
-            message: '请输入密码',
-            trigger: 'blur',
-          },
-        ],
-        sign_name: [
-          {
-            required: true,
-            message: '请输入短信签名',
-            trigger: 'blur',
-          },
-        ],
-      },
-    },
-  });
-
-  async function getData() {
-    const { data } = await admin.config.easysms();
-    easysms.model = data;
-  }
-  // 更新
-  function onUpdate() {
-    unref(easysmsRef).validate(async (valid) => {
-      valid && admin.config.easysms(easysms.model);
-    });
-  }
-
-  const templateRules = {
-    event: [
-      {
-        required: true,
-        message: '请输入键',
-        trigger: 'blur',
-      },
-    ],
-    value: [
-      {
-        required: true,
-        message: '请输入值',
-        trigger: 'blur',
-      },
-    ],
-  };
-
-  function onAddTemplate(filed) {
-    easysms.model['gateways_' + filed]['template'].push({
-      event: '',
-      value: '',
-    });
-  }
-  function onDeleteTemplate(filed, index) {
-    easysms.model['gateways_' + filed]['template'].splice(index, 1);
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>
-
-<style lang="scss" scoped>
-  .warning {
-    margin-bottom: 8px;
-    line-height: 32px;
-    font-size: 14px;
-  }
-</style>

+ 0 - 301
src/app/admin/views/notification/config/edit.vue

@@ -1,301 +0,0 @@
-<template>
-  <el-container>
-    <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
-        <el-form-item
-          label="模板类型"
-          v-if="
-            props.modal.params.platform == 'WechatOfficialAccount' ||
-            props.modal.params.platform == 'WechatMiniProgram'
-          "
-        >
-          <el-radio-group v-model="form.model.type">
-            <el-radio label="default">默认配置</el-radio>
-            <el-radio label="custom">自定义配置</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <div
-          v-if="
-            (form.model.type == 'default' &&
-              props.modal.params.platform == 'WechatOfficialAccount') ||
-            (form.model.type == 'default' && props.modal.params.platform == 'WechatMiniProgram')
-          "
-        >
-          <el-form-item label="通知标题">
-            <el-input
-              v-model="form.model.name"
-              placeholder="注册完成给上级发送"
-              disabled
-            ></el-input>
-          </el-form-item>
-          <el-form-item label="模板编号" v-if="props.modal.params.platform != 'WechatMiniProgram'">
-            <el-input v-model="form.model.wechat.temp_no" placeholder="" disabled></el-input>
-          </el-form-item>
-          <el-form-item label="模板Id">
-            <el-input v-model="form.model.content.template_id" disabled>
-              <template v-if="checkAuth('admin.notification.config.gettemplateid')" #append>
-                <span
-                  class="cursor-pointer"
-                  @click="getTemplateId('0')"
-                  v-if="!form.model.content.template_id"
-                  >立即获取</span
-                >
-                <el-popover
-                  v-model:visible="refundPopover"
-                  :width="250"
-                  trigger="click"
-                  v-if="form.model.content.template_id"
-                >
-                  <div class="sa-flex sa-m-b-16">
-                    <el-icon class="question sa-m-r-8">
-                      <QuestionFilled />
-                    </el-icon>
-                    <div>确定要重新获取模板Id吗?</div>
-                  </div>
-                  <div class="sa-flex">
-                    <el-checkbox
-                      v-model="isDelete"
-                      label="删除旧模板"
-                      class="sa-m-r-20"
-                      true-label="1"
-                      false-label="0"
-                    />
-                    <el-button size="small" @click="refundPopover = false">取消</el-button>
-                    <el-button size="small" type="primary" @click="getTemplateId(isDelete)"
-                      >确定</el-button
-                    >
-                  </div>
-                  <template #reference>
-                    <span>重新获取</span>
-                  </template>
-                </el-popover>
-              </template>
-            </el-input>
-          </el-form-item>
-          <el-form-item label="模板" class="field">
-            <div v-for="i in form.model.wechat.fields" :key="i" class="sa-flex">
-              <div v-if="i.name">{{ i.name }}:</div>
-              <div>{{ `{{${i.template_field}.DATA` }}}}</div>
-            </div>
-          </el-form-item>
-        </div>
-        <div v-if="form.model.type == 'custom' || props.modal.params.platform == 'Sms'">
-          <el-form-item label="模板消息Id">
-            <el-input
-              v-model="form.model.content.template_id"
-              placeholder="请输入模版消息Id"
-            ></el-input>
-          </el-form-item>
-          <div
-            v-for="(item, index) in form.model.content.fields"
-            :key="item"
-            class="sa-flex sa-m-b-24"
-          >
-            <div class="field-name sa-flex sa-row-right sa-p-r-12">
-              <span v-if="item.field">{{ item.name }}</span>
-              <el-input
-                v-else
-                v-model="item.name"
-                placeholder="请输入名称"
-                class="sa-w-100"
-              ></el-input>
-            </div>
-            <el-input
-              v-model="item.template_field"
-              class="sa-w-168 sa-m-r-24"
-              placeholder="请输入模版名称"
-            ></el-input>
-            <el-input v-model="item.value" class="sa-w-168" placeholder="请输入默认值"></el-input>
-            <div
-              v-if="!item.field"
-              class="field-del sa-m-l-12 sa-flex cursor-pointer"
-              @click.stop="fieldDel(index)"
-            >
-              删除
-            </div>
-          </div>
-          <el-form-item>
-            <el-button class="sa-m-l-12" icon="Plus" @click="addContent">添加</el-button>
-          </el-form-item>
-        </div>
-        <div v-if="props.modal.params.platform == 'Email'">
-          <div v-for="item in fieldList.data.fields" :key="item" class="sa-flex sa-m-b-24">
-            <div class="field-name sa-flex sa-row-right sa-p-r-12">
-              <span v-if="item.field">{{ item.name }}</span>
-            </div>
-            <el-input
-              v-model="item.field"
-              class="sa-w-168 sa-m-r-24"
-              placeholder="请输入模版名称"
-            ></el-input>
-          </div>
-          <div class="warning-title"> 请按照如下格式在文档中插入要显示的字段 p:{字段名} </div>
-          <sa-editor v-model:content="form.model.content"></sa-editor>
-        </div>
-      </el-form>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button v-auth="'admin.notification.config.edit'" type="primary" @click="confirm"
-        >更新</el-button
-      >
-    </el-footer>
-  </el-container>
-</template>
-<script setup>
-  import { onMounted, reactive, ref, unref } from 'vue';
-  import admin from '@/app/admin/api';
-  import SaEditor from '@/sheep/components/sa-editor/sa-editor.vue';
-  import { checkAuth } from '@/sheep/directives/auth';
-  import { cloneDeep } from 'lodash';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
-  const value1 = ref(false);
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      type: 'custom',
-      name: '',
-      wechat: {},
-      content: '',
-    },
-    rules: {},
-  });
-  const fieldList = reactive({
-    data: {},
-  });
-  const refundPopover = ref(false);
-  const isDelete = ref('1');
-  //删除
-  function fieldDel(index) {
-    form.model.content.fields.splice(index, 1);
-  }
-  function addContent() {
-    form.model.content.fields.push({
-      name: '',
-      template_field: '',
-      value: '',
-    });
-  }
-  //获取模板id
-  async function getTemplateId(e) {
-    refundPopover.value = false;
-
-    const { data } = await admin.notification.getTemplateId(
-      props.modal.params.event,
-      props.modal.params.platform,
-      { is_delete: e, template_id: e == 1 ? form.model.content.template_id : '' },
-    );
-    form.model.content.template_id = data;
-  }
-  // 获取详情
-  async function getDetail() {
-    const { data } = await admin.notification.config(
-      props.modal.params.event,
-      props.modal.params.platform,
-    );
-    form.model = data;
-    if (props.modal.params.platform == 'Email') {
-      fieldList.data = data.content;
-      form.model.content = data.content_text;
-    } else {
-      form.model.content.fields.forEach((e) => {
-        if (!e.value) {
-          e['value'] = '';
-        }
-        if (!e.template_field) {
-          e['template_field'] = '';
-        }
-      });
-    }
-  }
-
-  // 表单关闭时提交
-  function confirm() {
-    // 表单验证
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      delete form.model.content_text;
-      let submitForm = cloneDeep(form.model);
-      const { error } = await admin.notification.config(
-        props.modal.params.event,
-        props.modal.params.platform,
-        submitForm,
-      );
-      if (error == 0) {
-        emit('modalCallBack', { event: 'confirm' });
-      }
-    });
-  }
-
-  onMounted(() => {
-    getDetail();
-  });
-</script>
-<style lang="scss" scoped>
-  .header {
-    width: 100%;
-    height: 40px;
-    color: #434343;
-    padding-left: 16px;
-    background: var(--sa-background-hex-hover);
-    margin: 0 0 16px 0;
-    font-weight: 500;
-    font-size: 14px;
-  }
-  .icon-warning {
-    width: 14px;
-    height: 14px;
-    display: flex;
-    margin-left: 8px;
-  }
-  .openswitch {
-    color: var(--el-color-primary);
-  }
-  .switch-width {
-    width: 40px;
-  }
-  .warning-text {
-    font-size: 14px;
-    line-height: 16px;
-    color: var(--sa-font);
-  }
-  .add-richtext {
-    color: var(--el-color-primary);
-    font-size: 14px;
-    line-height: 16px;
-  }
-  .question {
-    color: #faad14;
-    font-size: 16px;
-  }
-  .field-name {
-    width: 100px;
-    height: 32px;
-  }
-  .field-del {
-    color: #ff4d4f;
-    height: 32px;
-  }
-  .warning-title {
-    color: rgb(255, 89, 89);
-    margin-bottom: 10px;
-  }
-  :deep() {
-    .field {
-      .el-form-item__content {
-        display: block;
-        border: 1px solid #d9d9d9;
-        max-width: 360px;
-        background: var(--sa-background-hex-hover);
-        border-radius: 4px;
-        padding: 6px 0 2px 12px;
-        color: var(--sa-place);
-      }
-    }
-  }
-</style>

+ 0 - 327
src/app/admin/views/notification/config/index.vue

@@ -1,327 +0,0 @@
-<template>
-  <el-container class="notificationConfig-page panel-block">
-    <el-header class="sa-header">
-      <el-tabs class="sa-tabs" v-model="configType" @tab-change="getData()">
-        <el-tab-pane label="通知用户" name="user"></el-tab-pane>
-        <el-tab-pane label="通知管理员" name="admin"></el-tab-pane>
-      </el-tabs>
-      <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">消息配置</div>
-        <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
-      </div>
-      <el-alert class="sa-alert">
-        <template #title>
-          <div class="sa-m-b-6">
-            1、消息通知仅用于向用户发送重要的服务通知,只能用于符合其要求的服务场景如:拼团成功通知、商品发货通知等。不支持广告等营销类消息以及其它所有可能对用户造成骚扰的消息;
-          </div>
-          <div class="sa-m-b-6">
-            2、小程序和公众号需要选择 生活服务/百货/超市/便利店 类目;
-          </div>
-          <div class="sa-m-b-6">3、公众号和小程序必须选择上面对应的类目才可以自动获取模板。</div>
-          <div>4、目前公众号类目模板库有待完善,部分模板无法自动获取,请自行在公众号后台->模板消息->挑选模板,并在商城后台->消息通知->对应消息的自定义配置处配置</div>
-        </template>
-      </el-alert>
-    </el-header>
-    <el-main class="sa-p-0 sa-p-t-20">
-      <div class="sa-table-wrap" v-loading="loading">
-        <template v-if="table.data.length > 0">
-          <el-table height="100%" class="sa-table" :data="table.data" row-key="id" stripe>
-            <el-table-column label="消息类别" min-width="160" align="center">
-              <template #default="scope">
-                <span class="sa-table-line-1">
-                  {{ scope.row.name || '-' }}
-                </span>
-              </template>
-            </el-table-column>
-            <el-table-column min-width="344">
-              <template #header>
-                <div class="sa-flex sa-row-center">
-                  <img
-                    src="/static/images/platform/wechat/officialAccount.png"
-                    class="header-icon sa-m-r-8"
-                  />
-                  <div>公众号模版通知</div>
-                </div>
-              </template>
-              <template #default="scope">
-                <div class="sa-flex sa-row-center">
-                  <div v-if="scope.row.channels">
-                    <el-button
-                      v-auth="'admin.notification.config.detail'"
-                      type="primary"
-                      color="#07C160"
-                      class="edit-btn"
-                      :disabled="!scope.row.channels.includes('WechatOfficialAccount')"
-                      @click="editRow(scope.row.event, 'WechatOfficialAccount')"
-                      >编辑配置</el-button
-                    >
-                  </div>
-                  <div class="sa-flex" v-if="scope.row.configs">
-                    <div class="official-time sa-flex sa-row-center sa-m-r-12">
-                      已发送{{ scope.row.configs.WechatOfficialAccount?.send_num || 0 }}次
-                    </div>
-                    <el-button v-auth="'admin.notification.config.setstatus'" link>
-                      <el-switch
-                        v-if="scope.row.channels.includes('WechatOfficialAccount')"
-                        v-model="scope.row.configs.WechatOfficialAccount.status"
-                        active-value="enable"
-                        inactive-value="disabled"
-                        @change="setStatus($event, scope.row.event, 'WechatOfficialAccount')"
-                      />
-                      <el-switch v-else model-value="false" disabled />
-                      <span
-                        class="sa-m-l-8"
-                        :class="
-                          scope.row.configs.WechatOfficialAccount?.status == 'enable' &&
-                          'open-switch'
-                        "
-                        >{{
-                          scope.row.configs.WechatOfficialAccount?.status == 'enable'
-                            ? '开启'
-                            : '关闭'
-                        }}
-                      </span>
-                    </el-button>
-                  </div>
-                </div>
-              </template>
-            </el-table-column>
-            <el-table-column min-width="344">
-              <template #header>
-                <div class="sa-flex sa-row-center">
-                  <img
-                    src="/static/images/platform/wechat/miniProgram.png"
-                    class="header-icon sa-m-r-8"
-                  />
-                  <div>微信小程序通知</div>
-                </div>
-              </template>
-              <template #default="scope">
-                <div class="sa-flex sa-row-center">
-                  <div v-if="scope.row.channels">
-                    <el-button
-                      v-auth="'admin.notification.config.detail'"
-                      type="primary"
-                      color="#6F74E9"
-                      class="edit-btn"
-                      :disabled="!scope.row.channels.includes('WechatMiniProgram')"
-                      @click="editRow(scope.row.event, 'WechatMiniProgram')"
-                      >编辑配置</el-button
-                    >
-                  </div>
-                  <div class="sa-flex" v-if="scope.row.configs">
-                    <div class="official-time mini-time sa-flex sa-row-center sa-m-r-12">
-                      已发送{{ scope.row.configs.WechatMiniProgram?.send_num || 0 }}次
-                    </div>
-                    <el-button v-auth="'admin.notification.config.setstatus'" link>
-                      <el-switch
-                        v-if="scope.row.channels.includes('WechatMiniProgram')"
-                        v-model="scope.row.configs.WechatMiniProgram.status"
-                        active-value="enable"
-                        inactive-value="disabled"
-                        @change="setStatus($event, scope.row.event, 'WechatMiniProgram')"
-                      />
-                      <el-switch v-else model-value="false" disabled />
-                      <span
-                        class="sa-m-l-8"
-                        :class="
-                          scope.row.configs.WechatMiniProgram?.status == 'enable' && 'open-switch'
-                        "
-                        >{{
-                          scope.row.configs.WechatMiniProgram?.status == 'enable' ? '开启' : '关闭'
-                        }}
-                      </span>
-                    </el-button>
-                  </div>
-                </div>
-              </template>
-            </el-table-column>
-            <el-table-column min-width="344">
-              <template #header>
-                <div class="sa-flex sa-row-center">
-                  <img src="/static/images/notification/sms.png" class="header-icon sa-m-r-8" />
-                  <div>短信通知</div>
-                </div>
-              </template>
-              <template #default="scope">
-                <div class="sa-flex sa-row-center">
-                  <div v-if="scope.row.channels">
-                    <el-button
-                      v-auth="'admin.notification.config.detail'"
-                      type="primary"
-                      color="#806AF6"
-                      class="edit-btn"
-                      :disabled="!scope.row.channels.includes('Sms')"
-                      @click="editRow(scope.row.event, 'Sms')"
-                      >编辑配置</el-button
-                    >
-                  </div>
-                  <div class="sa-flex" v-if="scope.row.configs">
-                    <div class="official-time sms-time sa-flex sa-row-center sa-m-r-12">
-                      已发送{{ scope.row.configs.Sms?.send_num || 0 }}次
-                    </div>
-                    <el-button v-auth="'admin.notification.config.setstatus'" link>
-                      <el-switch
-                        v-if="scope.row.channels.includes('Sms')"
-                        v-model="scope.row.configs.Sms.status"
-                        active-value="enable"
-                        inactive-value="disabled"
-                        @change="setStatus($event, scope.row.event, 'Sms')"
-                      />
-                      <el-switch v-else model-value="false" disabled />
-                      <span
-                        class="sa-m-l-8"
-                        :class="scope.row.configs.Sms?.status == 'enable' && 'open-switch'"
-                        >{{ scope.row.configs.Sms?.status == 'enable' ? '开启' : '关闭' }}</span
-                      >
-                    </el-button>
-                  </div>
-                </div>
-              </template>
-            </el-table-column>
-            <el-table-column min-width="344">
-              <template #header>
-                <div class="sa-flex sa-row-center">
-                  <img src="/static/images/notification/email.png" class="header-icon sa-m-r-8" />
-                  <div>邮件通知</div>
-                </div>
-              </template>
-              <template #default="scope">
-                <div class="sa-flex sa-row-center">
-                  <div v-if="scope.row.channels">
-                    <el-button
-                      v-auth="'admin.notification.config.detail'"
-                      type="primary"
-                      color="#409EFF"
-                      class="edit-btn"
-                      :disabled="!scope.row.channels.includes('Email')"
-                      @click="editRow(scope.row.event, 'Email')"
-                      >编辑配置</el-button
-                    >
-                  </div>
-                  <div class="sa-flex" v-if="scope.row.configs">
-                    <div class="official-time email-time sa-flex sa-row-center sa-m-r-12">
-                      已发送{{ scope.row.configs.Email?.send_num || 0 }}次
-                    </div>
-                    <el-button v-auth="'admin.notification.config.setstatus'" link>
-                      <el-switch
-                        v-if="scope.row.channels.includes('Email')"
-                        v-model="scope.row.configs.Email.status"
-                        active-value="enable"
-                        inactive-value="disabled"
-                        @change="setStatus($event, scope.row.event, 'Email')"
-                      />
-                      <el-switch v-else model-value="false" disabled />
-                      <span
-                        class="sa-m-l-8"
-                        :class="scope.row.configs.Email?.status == 'enable' && 'open-switch'"
-                        >{{ scope.row.configs.Email?.status == 'enable' ? '开启' : '关闭' }}</span
-                      >
-                    </el-button>
-                  </div>
-                </div>
-              </template>
-            </el-table-column>
-          </el-table>
-        </template>
-        <template v-if="table.data.length == 0 && !loading">
-          <sa-empty></sa-empty>
-        </template>
-      </div>
-    </el-main>
-  </el-container>
-</template>
-<script setup>
-  import { onMounted, reactive, ref, computed } from 'vue';
-  import admin from '@/app/admin/api';
-  import { useModal } from '@/sheep/hooks';
-  import ConfigEdit from './edit.vue';
-
-  const configType = ref('user');
-  const loading = ref(true);
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  async function getData() {
-    loading.value = true;
-    let receiver_type = configType.value;
-    const { error, data } = await admin.notification.config(receiver_type, null);
-    error === 0 && (table.data = data);
-    loading.value = false;
-  }
-  // 状态
-  async function setStatus(val, e, type) {
-    await admin.notification.setStatus(e, type, { status: val });
-    getData();
-  }
-  function editRow(e, channel) {
-    useModal(
-      ConfigEdit,
-      {
-        title: '编辑配置',
-        type: 'edit',
-        event: e,
-        platform: channel,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-  onMounted(() => {
-    getData();
-  });
-</script>
-<style lang="scss" scoped>
-  .notificationConfig-page {
-    .el-main {
-      background: var(--sa-background-assist);
-      .sa-table-wrap {
-        height: 100%;
-      }
-      .header-icon {
-        width: 16px;
-        height: 16px;
-      }
-      .edit-btn {
-        border-radius: 4px 0 0 4px;
-        color: var(--sa-background-assist);
-      }
-      .official-time {
-        width: 140px;
-        height: 32px;
-        background: rgba(7, 193, 96, 0.1);
-        border-radius: 0 4px 4px 0;
-        color: #07c160;
-      }
-      .mini-time {
-        background: rgba(111, 116, 233, 0.1);
-        color: #6f74e9;
-      }
-      .sms-time {
-        background: rgba(128, 106, 246, 0.1);
-        color: #806af6;
-      }
-      .email-time {
-        background: rgba(64, 158, 255, 0.1);
-        color: #409eff;
-      }
-      .open-switch {
-        color: var(--el-color-primary);
-      }
-    }
-    :deep() {
-      .el-alert__content {
-        padding: 0;
-      }
-      // .el-alert--info.is-light {
-      //   background: var(--sa-background-hex-hover);
-      // }
-    }
-  }
-</style>

+ 0 - 314
src/app/file/admin/config.vue

@@ -1,314 +0,0 @@
-<template>
-  <el-container class="panel-block">
-    <el-header class="sa-header">
-      <el-tabs class="sa-tabs">
-        <el-tab-pane label="附件配置"></el-tab-pane>
-      </el-tabs>
-    </el-header>
-    <el-main class="sa-p-t-30">
-      <el-row :gutter="16">
-        <el-col :xs="24" :sm="20" :md="18" :lg="14" :xl="14">
-          <el-form
-            :model="filesystem.model"
-            :rules="filesystem.rules"
-            ref="filesystemRef"
-            label-width="180px"
-          >
-            <el-form-item label="存储引擎" prop="driver" required>
-              <el-radio-group v-model="filesystem.model.driver">
-                <el-radio label="public">本地储存</el-radio>
-                <el-radio label="aliyun">阿里云存储</el-radio>
-                <el-radio label="qcloud">腾讯云存储</el-radio>
-                <el-radio label="qiniu">七牛云储存</el-radio>
-              </el-radio-group>
-            </el-form-item>
-            <!-- 默认 -->
-            <el-form-item label="允许上传类型" prop="extensions">
-              <el-input
-                placeholder="请输入允许上传类型(空格符为逗号)"
-                v-model="filesystem.model.extensions"
-              ></el-input>
-            </el-form-item>
-            <el-form-item label="文件上传限制" prop="filesize">
-              <el-input placeholder="请输入文件上传限制" v-model="filesystem.model.filesize">
-                <template #append>M</template>
-              </el-input>
-            </el-form-item>
-            <el-form-item label="图片上传限制" prop="imagesize">
-              <el-input placeholder="请输入图片上传限制" v-model="filesystem.model.imagesize">
-                <template #append>M</template>
-              </el-input>
-            </el-form-item>
-            <!-- 本地存储 -->
-            <template v-if="filesystem.model.driver == 'public'">
-              <div class="sa-title--line sa-m-b-30">本地存储</div>
-              <el-form-item label="访问域名">
-                <el-input
-                  placeholder="请输入访问域名 默认使用当前域名"
-                  v-model="filesystem.model.disks.public.url"
-                ></el-input>
-              </el-form-item>
-            </template>
-            <!-- 阿里云 -->
-            <template v-if="filesystem.model.driver == 'aliyun'">
-              <div class="sa-title--line sa-m-b-30">阿里云存储</div>
-              <div>
-                <el-form-item label="访问域名" prop="disks.aliyun.url">
-                  <el-input
-                    placeholder="请输入访问域名"
-                    v-model="filesystem.model.disks.aliyun.url"
-                  ></el-input>
-                </el-form-item>
-                <el-form-item label="AccessId" prop="disks.aliyun.access_id">
-                  <el-input
-                    placeholder="请输入AccessId"
-                    v-model="filesystem.model.disks.aliyun.access_id"
-                  ></el-input>
-                </el-form-item>
-                <el-form-item label="AccessSecret" prop="disks.aliyun.access_secret">
-                  <el-input
-                    placeholder="请输入AccessSecret"
-                    v-model="filesystem.model.disks.aliyun.access_secret"
-                  ></el-input>
-                </el-form-item>
-                <el-form-item label="Bucket" prop="disks.aliyun.bucket">
-                  <el-input
-                    placeholder="请输入Bucket"
-                    v-model="filesystem.model.disks.aliyun.bucket"
-                  ></el-input>
-                </el-form-item>
-                <el-form-item label="Endpoint" prop="disks.aliyun.endpoint">
-                  <el-input
-                    placeholder="请输入Endpoint"
-                    v-model="filesystem.model.disks.aliyun.endpoint"
-                  ></el-input>
-                </el-form-item>
-              </div>
-            </template>
-            <!-- 腾讯云 -->
-            <div v-if="filesystem.model.driver == 'qcloud'">
-              <div class="sa-title--line sa-m-b-30">腾讯云存储</div>
-              <el-form-item label="访问域名" prop="disks.qcloud.url">
-                <el-input
-                  placeholder="请输入访问域名"
-                  v-model="filesystem.model.disks.qcloud.url"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="所属区域" prop="disks.qcloud.region">
-                <el-input
-                  placeholder="请输入所属区域"
-                  v-model="filesystem.model.disks.qcloud.region"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="AppId" prop="disks.qcloud.app_id">
-                <el-input
-                  placeholder="请输入AppId"
-                  v-model="filesystem.model.disks.qcloud.app_id"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="SecretId" prop="disks.qcloud.secret_id">
-                <el-input
-                  placeholder="请输入SecretId"
-                  v-model="filesystem.model.disks.qcloud.secret_id"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="SecretKey" prop="disks.qcloud.secret_key">
-                <el-input
-                  placeholder="请输入SecretKey"
-                  v-model="filesystem.model.disks.qcloud.secret_key"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="Bucket" prop="disks.qcloud.bucket">
-                <el-input
-                  placeholder="请输入Bucket"
-                  v-model="filesystem.model.disks.qcloud.bucket"
-                ></el-input>
-              </el-form-item>
-            </div>
-            <!-- 七牛云 -->
-            <div v-if="filesystem.model.driver == 'qiniu'">
-              <div class="sa-title--line sa-m-b-30">七牛云存储</div>
-              <el-form-item label="访问域名" prop="disks.qiniu.url">
-                <el-input
-                  placeholder="请输入访问域名"
-                  v-model="filesystem.model.disks.qiniu.url"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="AccessKey" prop="disks.qiniu.access_key">
-                <el-input
-                  placeholder="请输入AccessKey"
-                  v-model="filesystem.model.disks.qiniu.access_key"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="SecretKey" prop="disks.qiniu.secret_key">
-                <el-input
-                  placeholder="请输入SecretKey"
-                  v-model="filesystem.model.disks.qiniu.secret_key"
-                ></el-input>
-              </el-form-item>
-              <el-form-item label="Bucket" prop="disks.qiniu.bucket">
-                <el-input
-                  placeholder="请输入Bucket"
-                  v-model="filesystem.model.disks.qiniu.bucket"
-                ></el-input>
-              </el-form-item>
-            </div>
-          </el-form>
-        </el-col>
-      </el-row>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button @click="getData">重置</el-button>
-      <el-button type="primary" @click="onUpdate">保存</el-button>
-    </el-footer>
-  </el-container>
-</template>
-<script>
-  export default {
-    name: 'file.admin.config',
-  };
-</script>
-<script setup>
-  import { reactive, ref, onMounted, unref } from 'vue';
-  import file from '@/app/file/api';
-
-  // 附件数据
-  const filesystemRef = ref();
-  const filesystem = reactive({
-    model: {},
-    rules: {
-      extensions: [{ required: true, message: '请输入允许上传类型', trigger: 'blur' }],
-      filesize: [{ required: true, message: '请输入文件上传限制', trigger: 'blur' }],
-      imagesize: [{ required: true, message: '请输入图片上传限制', trigger: 'blur' }],
-      disks: {
-        aliyun: {
-          access_id: [
-            {
-              required: true,
-              message: '请输入AccessId',
-              trigger: 'blur',
-            },
-          ],
-          access_secret: [
-            {
-              required: true,
-              message: '请输入AccessSecret',
-              trigger: 'blur',
-            },
-          ],
-          bucket: [
-            {
-              required: true,
-              message: '请输入Bucket',
-              trigger: 'blur',
-            },
-          ],
-          endpoint: [
-            {
-              required: true,
-              message: '请输入Endpoint',
-              trigger: 'blur',
-            },
-          ],
-          url: [
-            {
-              required: true,
-              message: '请输入访问域名',
-              trigger: 'blur',
-            },
-          ],
-        },
-        qcloud: {
-          app_id: [
-            {
-              required: true,
-              message: '请输入AppId',
-              trigger: 'blur',
-            },
-          ],
-          secret_bucket: [
-            {
-              required: true,
-              message: '请输入Bucket',
-              trigger: 'blur',
-            },
-          ],
-          region: [
-            {
-              required: true,
-              message: '请输入所属区域',
-              trigger: 'blur',
-            },
-          ],
-          secret_id: [
-            {
-              required: true,
-              message: '请输入SecretId',
-              trigger: 'blur',
-            },
-          ],
-          secret_key: [
-            {
-              required: true,
-              message: '请输入SecretKey',
-              trigger: 'blur',
-            },
-          ],
-          url: [
-            {
-              required: true,
-              message: '请输入访问域名',
-              trigger: 'blur',
-            },
-          ],
-        },
-        qiniu: {
-          access_key: [
-            {
-              required: true,
-              message: '请输入AccessKey',
-              trigger: 'blur',
-            },
-          ],
-          bucket: [
-            {
-              required: true,
-              message: '请输入Bucket',
-              trigger: 'blur',
-            },
-          ],
-          secret_key: [
-            {
-              required: true,
-              message: '请输入SecretKey',
-              trigger: 'blur',
-            },
-          ],
-          url: [
-            {
-              required: true,
-              message: '请输入访问域名',
-              trigger: 'blur',
-            },
-          ],
-        },
-      },
-    },
-  });
-
-  // 获取
-  async function getData() {
-    const { data } = await file.config.filesystem();
-    filesystem.model = data;
-  }
-  // 更新
-  function onUpdate() {
-    unref(filesystemRef).validate((valid) => {
-      valid && file.config.filesystem(filesystem.model);
-    });
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>

+ 0 - 13
src/app/file/admin/index.vue

@@ -1,13 +0,0 @@
-<template>
-  <sa-filemanager />
-</template>
-
-<script>
-  export default {
-    name: 'file.admin',
-  };
-</script>
-
-<script setup>
-  import SaFilemanager from '@/sheep/components/sa-file/sa-filemanager.vue';
-</script>

+ 0 - 55
src/app/file/api/index.js

@@ -1,55 +0,0 @@
-import { request } from '@/sheep/request';
-import { CRUD } from '@/sheep/request/crud';
-import { isEmpty } from 'lodash';
-
-export default {
-  // 文件管理
-  ...CRUD('file/admin/file'),
-  upload: (params, data) =>
-    request({
-      url: 'file/admin/file/upload',
-      method: 'POST',
-      headers: {
-        'Content-Type': 'multipart/form-data',
-      },
-      timeout: 80000,
-      params,
-      data,
-    }),
-  uploadCert: (data) =>
-    request({
-      url: 'file/admin/file/uploadCert',
-      method: 'POST',
-      headers: {
-        'Content-Type': 'multipart/form-data',
-      },
-      data,
-    }),
-  rename: (id, data) =>
-    request({
-      url: `file/admin/file/rename/${id}`,
-      method: 'POST',
-      data,
-    }),
-  move: (id, data) =>
-    request({
-      url: `file/admin/file/move/${id}`,
-      method: 'POST',
-      data,
-    }),
-  group: {
-    ...CRUD('file/admin/group'),
-  },
-  config: {
-    // 附件配置
-    filesystem: (data) =>
-      request({
-        url: 'file/admin/config/filesystem',
-        method: isEmpty(data) ? 'GET' : 'PUT',
-        data,
-        options: {
-          showSuccessMessage: !isEmpty(data),
-        },
-      }),
-  },
-};

+ 0 - 35
src/app/file/routes/index.js

@@ -1,35 +0,0 @@
-import Layout from '@/sheep/layouts/index.vue';
-
-export default {
-  path: '/file',
-  name: 'file',
-  component: Layout,
-  children: [
-    {
-      path: 'admin',
-      name: 'file.admin',
-      redirect: '/file/admin/index',
-      meta: {
-        title: '文件管理',
-      },
-      children: [
-        {
-          path: 'index',
-          name: 'file.admin.index',
-          component: () => import('@/app/file/admin/index.vue'),
-          meta: {
-            title: '文件管理',
-          },
-        },
-        {
-          path: 'config',
-          name: 'file.admin.config',
-          component: () => import('@/app/file/admin/config.vue'),
-          meta: {
-            title: '附件配置',
-          },
-        },
-      ],
-    },
-  ],
-};

+ 0 - 30
src/app/install/api/index.js

@@ -1,30 +0,0 @@
-import { request } from '@/sheep/request';
-
-export default {
-  check: () =>
-    request({
-      url: '/admin/install/check',
-      method: 'POST',
-    }),
-  init: () =>
-    request({
-      url: '/admin/install',
-      method: 'GET',
-    }),
-  install: (data) =>
-    request({
-      url: '/admin/install',
-      method: 'POST',
-      data,
-      options: {
-        showSuccessMessage: true,
-      },
-      timeout: 60000,
-    }),
-  checkLicenseKey: (params) =>
-    request({
-      url: '/admin/install/checkLicenseKey',
-      method: 'POST',
-      params,
-    }),
-};

+ 0 - 835
src/app/install/index.vue

@@ -1,835 +0,0 @@
-<template>
-  <el-container v-loading="state.loading">
-    <el-header class="header-box">
-      <div class="install-header sa-flex sa-col-center sa-row-between">
-        <div class="">
-          <img class="install-header-logo" src="/static/images/install/logo.png" />
-        </div>
-        <div class="install-header-list sa-flex sa-row-center">
-          <div class="item-title" :class="{ 'activite-title': state.currentStep === 'protocol' }">
-            用户协议
-            <div class="item-line"></div>
-          </div>
-          <div class="item-title" :class="{ 'activite-title': state.currentStep === 'checkEnv' }">
-            检测环境
-            <div class="item-line"></div>
-          </div>
-          <div class="item-title" :class="{ 'activite-title': state.currentStep === 'license' }">
-            授权码
-            <div class="item-line"></div>
-          </div>
-          <div class="item-title" :class="{ 'activite-title': state.currentStep === 'setting' }">
-            配置
-            <div class="item-line"></div>
-          </div>
-          <div class="item-title" :class="{ 'activite-title': state.currentStep === 'install' }">
-            安装
-            <div class="item-line"></div>
-          </div>
-        </div>
-        <div class="install-header-link sa-flex sa-row-right">
-          <button class="sa-reset-button official-btn" @click="onOfficialSite">官网</button>
-        </div>
-      </div>
-    </el-header>
-    <el-main>
-      <div class="main-box">
-        <el-row>
-          <el-col :md="3" :lg="5"></el-col>
-          <el-col v-if="!state.installed" :md="18" :lg="14">
-            <!-- 阅读协议 -->
-            <div class="main-content sa-p-24" v-if="state.currentStep === 'protocol'">
-              <div class="sa-flex sa-m-b-38">
-                <div class="main-line sa-m-r-10"></div>
-                <div class="main-content-headline">用户协议</div>
-              </div>
-
-              <div class="richtext-title">
-                <div class="richtext-title">
-                  作者:SheepJS团队<br />
-                  更新时间:2022-10-12<br /><br />
-                  感谢您选择SheepJS - Shopro商城(以下简称Shopro,Shopro基于 PHP +
-                  MySQL的技术开发,全部源码开放。
-                  为了使你正确并合法的使用本软件,请你在使用前务必阅读清楚下面的协议条款:<br /><br />
-                  一、本授权协议适用且仅适用于Shopro商城(Shopro商城是由SheepJS团队开发并支持的一套完整B2C商城系统.
-                  以下简称Shopro)任何版本,SheepJS官方对本授权协议的最终解释权。<br /><br />
-                  二、协议许可的权利<br />
-                  您可以在支付商业域名授权完成并完全遵守本最终用户授权协议的基础上,将本软件应用于商业用途,而不必支付其它软件版权授权费用。
-                  您可以在协议规定的约束和限制范围内修改Shopro源代码或界面风格以适应您的网站要求。
-                  您拥有使用本软件构建的网站全部内容所有权,并独立承担与这些内容的相关法律义务。
-                  获得商业授权之后,您可以将本软件应用于商业用途,同时依据所购买的授权类型中确定的技术支持内容,自购买时刻起,在技术支持期限内拥有通过指定的方式获得指定范围内的技术支持服务。商业授权用户享有反映和提出意见的权力,相关意见将被作为首要考虑,但没有一定被采纳的承诺或保证。<br /><br />
-                  三、协议规定的约束和限制<br />
-                  未获商业授权之前,不得将本软件用于商业用途(包括但不限于企业网站、经营性网站、以营利为目的或实现盈利的网站)。
-                  未经官方许可,不得对本软件或与之关联的商业授权进行出租、出售、抵押或发放子许可证。
-                  未经官方许可,禁止在Shopro的整体或任何部分基础上以发展任何派生版本、修改版本或第三方版本用于重新分发。
-                  如果您未能遵守本协议的条款,您的授权将被终止,所被许可的权利将被收回,并承担相应法律责任。<br /><br />
-                  四、有限担保和免责声明<br />
-                  本软件及所附带的文件是作为不提供任何明确的或隐含的赔偿或担保的形式提供的。
-                  用户出于自愿而使用本软件,您必须了解使用本软件的风险,在尚未购买产品技术服务之前,我们不承诺对免费用户提供任何形式的技术支持、使用担保,也不承担任何因使用本软件而产生问题的相关责任。
-                  电子文本形式的授权协议如同双方书面签署的协议一样,具有完全的和等同的法律效力。您一旦开始确认本协议并安装
-                  Shopro,即被视为完全理解并接受本协议的各项条款,在享有上述条款授予的权力的同时,受到相关的约束和限制。协议许可范围以外的行为,将直接违反本授权协议并构成侵权,我们有权随时终止授权,责令停止损害,并保留追究相关责任的权力。
-                  如果本软件带有其它软件的整合API示范例子包,这些文件版权不属于本软件官方,并且这些文件是没经过授权发布的,请参考相关软件的使用许可合法的使用。<br /><br />
-                  2021~2022 SheepJS™团队版权所有并保留所有权利。
-                </div>
-              </div>
-            </div>
-            <!-- 检查环境 -->
-            <div class="main-content sa-p-24" v-if="state.currentStep === 'checkEnv'">
-              <div class="sa-flex sa-m-b-28">
-                <div class="main-line sa-m-r-10"></div>
-                <div class="main-content-headline">系统环境</div>
-              </div>
-              <div class="sa-flex-col sa-col-center">
-                <div
-                  v-for="i in state.checkEnvResult.output"
-                  :key="i"
-                  class="sa-flex sa-col-center sa-m-b-28"
-                >
-                  <div class="output-title">
-                    {{ i.item }}
-                  </div>
-                  <div class="icon-box">
-                    <el-icon class="success-icon" v-if="i.error === 0"
-                      ><CircleCheckFilled
-                    /></el-icon>
-                    <el-icon class="filled-icon" v-if="i.error === 1"
-                      ><CircleCloseFilled
-                    /></el-icon>
-                    <el-icon class="warning-icon" v-if="i.error === 2"><WarningFilled /></el-icon>
-                  </div>
-                  <div class="output-msg">
-                    {{ i.msg }}
-                  </div>
-                </div>
-              </div>
-            </div>
-            <!-- 授权码 -->
-            <div v-if="state.currentStep === 'license'">
-              <div class="main-content sa-p-24">
-                <div class="sa-flex sa-m-b-28">
-                  <div class="main-line sa-m-r-10"></div>
-                  <div class="main-content-headline">授权信息</div>
-                </div>
-                <el-form
-                  :model="form.model"
-                  :rules="form.rules"
-                  ref="formRef"
-                  label-width="120px"
-                  label-position="right"
-                >
-                  <el-form-item label="授权码" prop="license_key">
-                    <el-input
-                      class="sa-w-240"
-                      :rows="4"
-                      type="textarea"
-                      placeholder="请填写或粘贴您的授权码"
-                      v-model="form.model.license_key"
-                    ></el-input>
-                  </el-form-item>
-                </el-form>
-                <div class="license-text sa-m-t-40">
-                  1.请填写您已备案的授权码,该授权码需与当前站点的线上域名保持一致
-                  <div class="sa-flex">
-                    2.如遗失或暂无授权码,请前往
-                    <button class="sa-reset-button buy-link" @click="onBuy">查询或购买</button>
-                  </div>
-                  3.请勿分享或使用其他任何形式获取的授权码,以此带来的纠纷或法律问题将由您或贵司自行承担<br />
-                  4.尊重原创,支持正版,我们将保持持续的更新与服务
-                </div>
-              </div>
-            </div>
-            <!-- 配置 -->
-            <div v-if="state.currentStep === 'setting'">
-              <div>
-                <el-collapse v-model="state.showMysql">
-                  <el-collapse-item name="showMysql">
-                    <template #title>
-                      <div class="sa-flex">
-                        <div class="main-line sa-m-r-10"></div>
-                        <div class="main-content-headline">MySQL</div>
-                      </div>
-                      <div class="main-content-headline"></div>
-                    </template>
-                    <el-form
-                      :model="form.model"
-                      :rules="form.rules"
-                      ref="formRef"
-                      label-width="120px"
-                      label-position="right"
-                    >
-                      <el-form-item label="数据库地址" prop="mysql_hostname">
-                        <el-input class="sa-w-240" v-model="form.model.mysql_hostname"></el-input>
-                      </el-form-item>
-                      <el-form-item label="端口" prop="mysql_hostport">
-                        <el-input class="sa-w-240" v-model="form.model.mysql_hostport"></el-input>
-                      </el-form-item>
-                      <el-form-item label="数据库名" prop="mysql_database">
-                        <el-input class="sa-w-240" v-model="form.model.mysql_database"></el-input>
-                      </el-form-item>
-                      <el-form-item label="登录账号" prop="mysql_username">
-                        <el-input class="sa-w-240" v-model="form.model.mysql_username"></el-input>
-                      </el-form-item>
-                      <el-form-item label="登录密码">
-                        <el-input class="sa-w-240" v-model="form.model.mysql_password"></el-input>
-                      </el-form-item>
-                      <el-form-item label="数据库表前缀">
-                        <el-input class="sa-w-240" v-model="form.model.mysql_prefix"></el-input>
-                      </el-form-item>
-                      <el-form-item label="安装测试数据">
-                        <el-switch
-                          class="sa-w-240"
-                          v-model="form.model.is_test_data"
-                          style="--el-switch-on-color: #806af6; --el-switch-off-color: #bfbfbf"
-                          :active-value="1"
-                          :inactive-value="0"
-                        />
-                      </el-form-item>
-                    </el-form>
-                  </el-collapse-item>
-                </el-collapse>
-              </div>
-              <div class="redis-box sa-m-t-24">
-                <el-collapse v-model="state.showRedis" @change="onRedis">
-                  <el-collapse-item name="showRedis">
-                    <template #title>
-                      <div class="redis-box-header sa-flex sa-row-between">
-                        <div class="sa-flex">
-                          <div class="main-line sa-m-r-10"></div>
-                          <div class="redis-box-title">Redis</div>
-                        </div>
-                        <el-switch
-                          v-model="form.model.check_redis"
-                          style="--el-switch-on-color: #806af6; --el-switch-off-color: #bfbfbf"
-                          :active-value="1"
-                          :inactive-value="0"
-                        />
-                      </div>
-                    </template>
-                    <div></div>
-                    <el-form
-                      :model="form.model"
-                      :rules="form.rules"
-                      ref="formRef"
-                      label-width="120px"
-                      label-position="right"
-                    >
-                      <el-form-item label="redis地址" prop="redis_host">
-                        <el-input class="sa-w-240" v-model="form.model.redis_host"></el-input>
-                      </el-form-item>
-                      <el-form-item label="端口" prop="redis_port">
-                        <el-input class="sa-w-240" v-model="form.model.redis_port"></el-input>
-                      </el-form-item>
-                      <el-form-item label="连接密码">
-                        <el-input class="sa-w-240" v-model="form.model.redis_password"></el-input>
-                      </el-form-item>
-                      <el-form-item label="db库1-15" prop="redis_select">
-                        <el-input class="sa-w-240" v-model="form.model.redis_select"></el-input>
-                      </el-form-item>
-                    </el-form>
-                  </el-collapse-item>
-                </el-collapse>
-              </div>
-              <div class="main-content sa-p-24 sa-m-t-24">
-                <div class="sa-flex sa-m-b-28">
-                  <div class="main-line sa-m-r-10"></div>
-                  <div class="main-content-headline">登录信息</div>
-                </div>
-                <el-form
-                  :model="form.model"
-                  :rules="form.rules"
-                  ref="formRef"
-                  label-width="120px"
-                  label-position="right"
-                >
-                  <el-form-item label="登录账号" prop="admin_account">
-                    <el-input class="sa-w-240" v-model="form.model.admin_account"></el-input>
-                  </el-form-item>
-                  <el-form-item label="登录密码" prop="admin_password">
-                    <el-input class="sa-w-240" v-model="form.model.admin_password"></el-input>
-                  </el-form-item>
-                </el-form>
-              </div>
-            </div>
-            <!-- 安装成功 -->
-            <div
-              class="install-box sa-flex-col sa-row-center sa-col-center"
-              v-if="state.currentStep === 'install'"
-            >
-              <div class="ss-m-b-60">
-                <img class="install-img" src="/static/images/install/img-01.png" />
-              </div>
-              <div class="content sa-flex-col sa-row-center sa-col-center">
-                <div class="content-title sa-m-t-32 sa-m-b-18">系统信息</div>
-                <div class="content-msg sa-flex sa-row-between sa-m-b-40">
-                  <div class="content-msg-title">
-                    <div class="sa-m-b-12">后台访问地址:</div>
-                    <div class="sa-flex">
-                      <div>{{ pathname }}</div>
-                      <button class="sa-reset-button sa-m-l-10" @click="onCopy()">复制</button>
-                    </div>
-                  </div>
-                  <div class="content-msg-title">
-                    <div class="sa-m-b-12">登录账号:</div>
-                    <div>{{ form.model.admin_account }}</div>
-                  </div>
-                </div>
-                <button class="sa-reason-button sa-m-b-24 goback-btn" @click="onHome">
-                  进入后台
-                </button>
-              </div>
-              <div class="sa-m-t-24">
-                <div class="sa-flex sa-row-center install-foot-title sa-m-b-12">
-                  <a class="link sa-m-r-60" href="https://www.sheepjs.com/" target="_blank"
-                    >官方网站</a
-                  >
-                  <a class="link" href="https://doc.sheepjs.com/" target="_blank">开发文档</a>
-                </div>
-                <div class="install-foot-title"> Copyright © 星品科技 All Rights Reserverd. </div>
-              </div>
-            </div>
-          </el-col>
-          <el-col v-else :md="18" :lg="14">
-            <!-- 已安装 -->
-            <div class="install-msg">
-              {{ state.installErrorMessage }}
-            </div>
-          </el-col>
-          <el-col :md="3" :lg="5"></el-col>
-        </el-row>
-      </div>
-    </el-main>
-    <el-footer>
-      <div class="foot-box" v-if="state.currentStep != 'install'">
-        <el-row>
-          <el-col :md="3" :lg="5"></el-col>
-          <el-col :md="18" :lg="14">
-            <div
-              v-if="state.installed"
-              class="foot-content sa-flex sa-row-right sa-col-center sa-p-r-20"
-            >
-              <el-button type="primary" @click="onHome">返回首页</el-button>
-            </div>
-            <div v-else class="foot-content sa-flex sa-row-right sa-col-center sa-p-r-20">
-              <el-button v-if="showLastStep" type="default" plain @click="onLastStep"
-                >上一步</el-button
-              >
-              <el-button v-if="state.currentStep === 'protocol'" type="primary" @click="onAgree"
-                >同意并继续</el-button
-              >
-
-              <el-button
-                v-if="state.currentStep === 'checkEnv' && !state.loading"
-                type="default"
-                plain
-                @click="onCheckEnv"
-                >重新检测</el-button
-              >
-              <el-button
-                type="default"
-                plain
-                v-if="state.currentStep === 'checkEnv' && state.loading"
-                >检测中...</el-button
-              >
-              <el-button
-                v-if="
-                  state.currentStep === 'checkEnv' &&
-                  state.checkEnvResult.error === 0 &&
-                  !state.loading
-                "
-                type="primary"
-                @click="onNextStep('license')"
-                >下一步</el-button
-              >
-
-              <el-button
-                v-if="state.currentStep === 'license'"
-                type="primary"
-                @click="onCheckLicense"
-                >验证授权</el-button
-              >
-
-              <el-button
-                v-if="state.currentStep === 'setting'"
-                type="primary"
-                @click="onSubmitInstall"
-                >确认安装</el-button
-              >
-
-              <el-button
-                type="default"
-                plain
-                v-if="state.currentStep === 'setting' && state.loading"
-                >安装中...</el-button
-              >
-            </div>
-          </el-col>
-          <el-col :md="3" :lg="5"></el-col>
-        </el-row>
-      </div>
-    </el-footer>
-  </el-container>
-</template>
-<script>
-  export default {
-    name: 'install.admin',
-  };
-</script>
-<script setup>
-  import { reactive, onMounted, unref, ref, computed } from 'vue';
-  import installApi from '@/app/install/api';
-  import { cloneDeep } from 'lodash';
-  import handleClipboard from '@/sheep/utils/clipboard';
-
-  // 步进映射
-  const stepMap = ['protocol', 'checkEnv', 'license', 'setting', 'install'];
-
-  const state = reactive({
-    loading: true, // 加载状态
-    installed: true, // 应用是否安装
-    installErrorMessage: '', // 安装错误信息
-    currentStep: 'protocol', // 当前步骤
-    checkEnvResult: {},
-    showMysql: 'showMysql',
-    showRedis: 'showRedis',
-  });
-
-  // 是否显示上一步
-  const showLastStep = computed(() => {
-    const currentIndex = stepMap.findIndex((item) => state.currentStep === item);
-    if (currentIndex > 0 && currentIndex < stepMap.length) {
-      return true;
-    }
-    return false;
-  });
-
-  // 点击上一步
-  function onLastStep() {
-    const currentIndex = stepMap.findIndex((item) => state.currentStep === item);
-    state.currentStep = stepMap[currentIndex - 1];
-  }
-
-  // 点击下一步
-  function onNextStep(tabName) {
-    state.currentStep = tabName;
-  }
-
-  function onRedis(e) {
-    if (e[0] === 'showRedis') {
-      form.model.check_redis = 1;
-    } else {
-      form.model.check_redis = 0;
-    }
-  }
-
-  function onCopy() {
-    handleClipboard(pathname);
-  }
-
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      license_key: '',
-
-      mysql_database: '',
-      mysql_hostname: '',
-      mysql_hostport: '',
-      mysql_password: '',
-      mysql_prefix: '',
-      mysql_username: '',
-
-      check_redis: 0,
-      redis_host: '',
-      redis_password: '',
-      redis_port: '',
-      redis_select: '',
-
-      admin_account: '',
-      admin_password: '',
-    },
-    rules: {
-      mysql_hostname: [{ required: true, message: '请输入数据库地址', trigger: 'blur' }],
-      mysql_hostport: [{ required: true, message: '请输入端口', trigger: 'blur' }],
-      mysql_database: [{ required: true, message: '请输入数据库名', trigger: 'blur' }],
-      mysql_username: [{ required: true, message: '请输入登录账号', trigger: 'blur' }],
-      redis_host: [{ required: true, message: '请输入redis地址', trigger: 'blur' }],
-      redis_port: [{ required: true, message: '请输入端口', trigger: 'blur' }],
-      redis_select: [{ required: true, message: '请输入登录账号', trigger: 'blur' }],
-      admin_account: [{ required: true, message: '请输入登录账号', trigger: 'blur' }],
-      admin_password: [{ required: true, message: '请输入登录密码', trigger: 'blur' }],
-      license_key: [{ required: true, message: '请输入授权码', trigger: 'blur' }],
-    },
-  });
-
-  const pathname = window.location.origin + window.location.pathname;
-
-  // 去官网
-  function onOfficialSite() {
-    window.open('https://www.sheepjs.com', '_blank');
-  }
-  // 去授权
-  function onBuy() {
-    window.open('https://www.sheepjs.com/buy', '_blank');
-  }
-  // 同意并继续
-  function onAgree() {
-    state.currentStep = 'checkEnv';
-    onCheckEnv();
-  }
-
-  // 返回首页
-  function onHome() {
-    window.location.href = window.location.pathname;
-  }
-
-  // 检测服务端环境
-  async function onCheckEnv() {
-    state.loading = true;
-    const { error, data } = await installApi.check();
-    setTimeout(function () {
-      state.loading = false;
-    }, 300);
-    if (error === 0) {
-      state.checkEnvResult = data;
-    }
-  }
-
-  // 验证授权
-  async function onCheckLicense() {
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      let res = await installApi.checkLicenseKey({ license_key: form.model.license_key });
-      if (res.error === 0) {
-        state.currentStep = 'setting';
-      }
-    });
-  }
-
-  async function getInit() {
-    const res = await installApi.init();
-    state.loading = false;
-    if (res.error === 0) {
-      form.model = {
-        ...form.model,
-        ...res.data.mysql,
-        ...res.data.redis,
-      };
-      state.installed = false;
-    } else {
-      state.installErrorMessage = res.msg || '网络出现问题';
-    }
-
-    if (form.model.check_redis === 1) {
-      state.showRedis = 'showRedis';
-    }
-  }
-
-  // 表单关闭时提交
-  function onSubmitInstall() {
-    // state.loading = true;
-    // 表单验证
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      state.loading = true;
-      let submitForm = cloneDeep(form.model);
-      submitForm.is_install = 1;
-      const { error } = await installApi.install(submitForm);
-      if (error === 0) {
-        state.currentStep = 'install';
-      }
-      state.loading = false;
-    });
-  }
-
-  onMounted(() => {
-    getInit();
-  });
-</script>
-<style lang="scss" scoped>
-  a:visited {
-    color: #fff;
-  }
-  .el-collapse {
-    border: none;
-    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.08);
-    border-radius: 8px;
-    padding: 0 24px;
-    --el-collapse-content-bg-color: #ffffff;
-    --el-collapse-header-bg-color: #ffffff;
-  }
-  .redis-box {
-    :deep(.el-collapse-item__arrow) {
-      display: none;
-    }
-    .redis-box-header {
-      width: 100%;
-    }
-    .redis-box-title {
-      color: #262626;
-      font-weight: 600 !important;
-      font-size: 18px;
-      line-height: 20px;
-    }
-  }
-  .el-form-item {
-    justify-content: center;
-  }
-  :deep() {
-    .el-form-item__content {
-      flex: none;
-    }
-    .el-collapse-item__header {
-      border-bottom: none;
-      font-weight: 600;
-    }
-    .el-collapse-item__wrap {
-      border-bottom: none;
-    }
-    .el-form-item__label {
-      font-size: 16px;
-    }
-    .el-textarea__inner {
-      font-size: 16px;
-    }
-    .el-form-item__error {
-      font-size: 14px;
-    }
-  }
-  .el-menu--horizontal > .el-menu-item.is-active {
-    border-bottom: 3px solid #ffffff;
-  }
-  .header-box {
-    z-index: 2001;
-    --el-header-padding: 0;
-  }
-  .install-header {
-    width: 100%;
-    height: 60px;
-    background: var(--el-color-primary);
-    box-sizing: border-box;
-    padding: 0 40px;
-    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.16);
-    position: fixed;
-    left: 0;
-    top: 0;
-    z-index: 10;
-  }
-  .official-btn {
-    color: #fdfdfd;
-    font-weight: 500;
-    font-size: 20px;
-    line-height: 24px;
-  }
-  .install-header-logo {
-    width: 146px;
-    height: 32px;
-  }
-  .install-header-link {
-    width: 146px;
-  }
-  .install-header-list {
-    width: 100%;
-    .menu-box {
-      border-bottom: 0;
-    }
-
-    .item-title {
-      font-size: 18px;
-      margin-right: 40px;
-      height: 60px;
-      display: flex;
-      align-items: center;
-      color: #d7d1fb;
-      cursor: default;
-      :last-child {
-        margin-right: 0;
-      }
-    }
-    .activite-title {
-      color: #fdfdfd;
-      position: relative;
-      .item-line {
-        position: absolute;
-        left: 50%;
-        transform: translateX(-50%);
-        bottom: 0;
-        width: 40px;
-        height: 3px;
-        background: #ffffff;
-      }
-    }
-  }
-  .install-box {
-    width: 100%;
-    height: 628px;
-    background: var(--el-color-primary) url('/static/images/install/bg.png') no-repeat top center /
-      100% auto;
-    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.08);
-    border-radius: 8px;
-    .install-img {
-      width: 200px;
-      height: 200px;
-    }
-    .content {
-      width: 72%;
-      background: #8d79f7;
-      border-radius: 12px;
-      .content-title {
-        font-weight: 500;
-        font-size: 24px;
-        line-height: 24px;
-        color: #fdfdfd;
-      }
-      .content-msg {
-        width: 80%;
-        .content-msg-title {
-          font-weight: 500;
-          font-size: 16px;
-          line-height: 18px;
-          color: #fdfdfd;
-        }
-      }
-      .goback-btn {
-        width: 104px;
-        height: 40px;
-        background: #fdfdfd;
-        border-radius: 4px;
-        color: #806af6;
-        font-weight: 500;
-        font-size: 16px;
-        line-height: 16px;
-      }
-    }
-    .install-foot-title {
-      font-weight: 500;
-      font-size: 12px;
-      line-height: 18px;
-      color: #fdfdfd;
-      .link {
-        color: #fff;
-      }
-    }
-  }
-  .main-line {
-    width: 4px;
-    height: 30px;
-    background: var(--el-color-primary);
-  }
-  .main-content-headline {
-    color: #262626;
-    font-weight: 600;
-    font-size: 18px;
-    line-height: 20px;
-  }
-  .license-text {
-    color: #8c8c8c;
-    font-weight: 400;
-    font-size: 16px;
-    line-height: 32px;
-  }
-  .buy-link {
-    color: #806af6;
-  }
-  .install-msg {
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    height: calc(100vh - 160px);
-  }
-  .main-content {
-    width: 100%;
-    background: #ffffff;
-    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.08);
-    border-radius: 8px;
-
-    .richtext-title {
-      color: #595959;
-      font-weight: 400;
-      font-size: 16px;
-      line-height: 24px;
-      width: inherit;
-      text-align: justify;
-      // white-space: pre-line;
-      box-sizing: border-box;
-      overflow: hidden;
-      word-wrap: break-word;
-    }
-    .richtext-title-headline {
-      text-align: center;
-    }
-    .icon-box {
-      margin: 0 16px 0 32px;
-    }
-    .success-icon {
-      color: #52c41a;
-      font-size: 20px;
-    }
-    .filled-icon {
-      color: #ff4d4f;
-      font-size: 20px;
-    }
-    .warning-icon {
-      color: #faad14;
-      font-size: 20px;
-    }
-    .output-title {
-      font-weight: 500;
-      font-size: 16px;
-      line-height: 20px;
-      color: #434343;
-      width: 220px;
-      display: flex;
-      justify-content: flex-end;
-    }
-    .output-msg {
-      font-weight: 400;
-      font-size: 16px;
-      line-height: 18px;
-      color: #595959;
-      width: 300px;
-      word-wrap: break-word;
-    }
-  }
-  .foot-box {
-    position: fixed;
-    left: 0;
-    bottom: 0;
-    width: 100%;
-    height: 60px;
-    background: #fdfdfd;
-    box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.08);
-    z-index: 2001;
-    .foot-content {
-      height: 60px;
-    }
-  }
-  @media only screen and (max-width: 768px) {
-    .install-header-list {
-      display: none;
-    }
-    .install-header {
-      padding: 0 30px;
-    }
-    .foot-content {
-      margin: 0 24px;
-    }
-    .icon-box {
-      margin: 0 12px 0 20px !important;
-    }
-    .success-icon {
-      font-size: 14px !important;
-    }
-    .filled-icon {
-      font-size: 14px !important;
-    }
-    .warning-icon {
-      font-size: 14px !important;
-    }
-    .output-title {
-      font-size: 14px !important;
-    }
-    .output-msg {
-      font-size: 14px !important;
-    }
-  }
-</style>

+ 0 - 12
src/app/install/routes/index.js

@@ -1,12 +0,0 @@
-import Layout from '@/sheep/layouts/content.vue';
-
-export default {
-  path: '/install',
-  name: 'install',
-  component: () => import('@/app/install/index.vue'),
-  meta: {
-    title: '安装',
-    login: false,
-    taskbar: false,
-  },
-};

+ 0 - 128
src/app/shop/admin/app/app.service.js

@@ -1,128 +0,0 @@
-import Content from '@/sheep/layouts/content.vue';
-import { request } from '@/sheep/request';
-import { CRUD, RECYCLE } from '@/sheep/request/crud';
-
-const route = {
-  path: 'app',
-  name: 'shop.admin.app',
-  component: Content,
-  meta: {
-    title: '应用',
-  },
-  children: [
-    {
-      path: 'scoreshop',
-      name: 'shop.admin.app.scoreshop',
-      component: () => import('./scoreShop/index.vue'),
-      meta: {
-        title: '积分商城',
-      },
-    },
-    {
-      path: 'mplive',
-      name: 'shop.admin.app.mplive',
-      component: () => import('./mplive/index.vue'),
-      meta: {
-        title: '小程序直播',
-      },
-    },
-  ],
-};
-
-const api = {
-  scoreShop: {
-    ...CRUD('shop/admin/app/scoreShop', ['list', 'add', 'edit', 'delete']),
-    ...RECYCLE('shop/admin/app/scoreShop'),
-    getSkuPrices: (id) =>
-      request({
-        url: '/shop/admin/app/scoreShop/skuPrices/' + id,
-        method: 'GET',
-      }),
-    skus: (id) =>
-      request({
-        url: `/shop/admin/app/scoreShop/skus/${id}`,
-        method: 'GET',
-      }),
-    select: (params, type = 'page') =>
-      request({
-        url: `shop/admin/app/scoreShop/select?type=${type}`,
-        method: 'GET',
-        params,
-      }),
-  },
-  mplive: {
-    room: {
-      ...CRUD('shop/admin/app/mplive/room', ['list', 'detail', 'delete']),
-      add: (data) =>
-        request({
-          url: '/shop/admin/app/mplive/room',
-          method: 'POST',
-          timeout: 80000,
-          data,
-        }),
-      edit: (id, data) =>
-        request({
-          url: '/shop/admin/app/mplive/room/' + id,
-          method: 'PUT',
-          timeout: 80000,
-          data,
-        }),
-      select: (params) =>
-        request({
-          url: `shop/admin/app/mplive/room/select`,
-          method: 'GET',
-          params,
-        }),
-      sync: () =>
-        request({
-          url: '/shop/admin/app/mplive/room/sync',
-          method: 'GET',
-        }),
-      pushUrl: (id) =>
-        request({
-          url: '/shop/admin/app/mplive/room/pushUrl/' + id,
-          method: 'GET',
-        }),
-      qrcode: (id) =>
-        request({
-          url: '/shop/admin/app/mplive/room/qrcode/' + id,
-          method: 'GET',
-        }),
-      playback: (id) =>
-        request({
-          url: '/shop/admin/app/mplive/room/playback/' + id,
-          method: 'GET',
-        }),
-    },
-    goods: {
-      ...CRUD('shop/admin/app/mplive/goods', ['list', 'detail', 'delete']),
-      add: (data) =>
-        request({
-          url: '/shop/admin/app/mplive/goods',
-          method: 'POST',
-          timeout: 80000,
-          data,
-        }),
-      edit: (id, data) =>
-        request({
-          url: '/shop/admin/app/mplive/goods/' + id,
-          method: 'PUT',
-          timeout: 80000,
-          data,
-        }),
-      audit: (id, data) =>
-        request({
-          url: '/shop/admin/app/mplive/goods/audit/' + id,
-          method: 'POST',
-          data,
-        }),
-      status: (id) =>
-        request({
-          url: '/shop/admin/app/mplive/goods/status/' + id,
-          method: 'GET',
-        }),
-    }
-  },
-};
-
-export { route, api };

+ 0 - 267
src/app/shop/admin/app/mplive/goods/edit.vue

@@ -1,267 +0,0 @@
-<template>
-  <el-container>
-    <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="120px">
-        <div class="sa-color--warning sa-m-l-50 sa-m-b-18"
-          >在小程序直播控制台添加的商品,不支持通过此系统操作。</div
-        >
-        <el-form-item label="商品来源">
-          <el-radio-group v-model="form.model.type">
-            <el-radio :label="0">我的小程序</el-radio>
-            <el-radio :label="1">其他小程序</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="商品选择" prop="goods_id" v-if="form.model.type === 0">
-          <el-button type="primary" class="is-link" @click="selectGoods" v-if="!form.model.goods_id"
-            >选择商品</el-button
-          >
-          <div class="sa-template-content sa-template-goods" v-if="form.model.goods_id">
-            <div class="header sa-flex">
-              <div class="item">商品信息</div>
-              <div class="oper">操作</div>
-            </div>
-            <div class="list sa-flex">
-              <div class="item sa-flex">
-                <sa-image :url="goods.image" size="40"></sa-image>
-                <div class="sa-m-l-8">
-                  <div class="title">{{ goods.title }}</div>
-                </div>
-              </div>
-              <div class="oper">
-                <el-button class="is-link" type="danger" @click="deleteItems()">移除</el-button>
-              </div>
-            </div>
-          </div>
-        </el-form-item>
-        <el-form-item label="商品封面">
-          <sa-uploader v-model="form.model.cover_img_url" fileType="image" isCropper></sa-uploader>
-          <div class="desc sa-m-l-12"> 图片尺寸最大300像素*300像素 </div>
-        </el-form-item>
-        <el-form-item label="商品名称" prop="name">
-          <el-input
-            v-model="form.model.name"
-            :maxlength="14"
-            :minlength="3"
-            show-word-limit
-            placeholder="请输入商品名称"
-          ></el-input>
-        </el-form-item>
-        <el-form-item label="价格形式" prop="price_type">
-          <el-radio-group v-model="form.model.price_type">
-            <el-radio :label="1">一口价</el-radio>
-            <el-radio :label="2">价格区间</el-radio>
-            <el-radio :label="3">显示折扣价</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="价格">
-          <div class="sa-m-r-8" v-if="form.model.price_type !== 1">{{
-            form.model.price_type === 2 ? '最小价格' : '市场价'
-          }}</div>
-          <el-input
-            v-model="form.model.price"
-            placeholder="请输入价格"
-            class="sa-w-160"
-            type="number"
-            :step="0.01"
-            :min="0"
-            :precision="2"
-          >
-            <template #append>元</template>
-          </el-input>
-          <div class="sa-m-l-16 sa-m-r-8" v-if="form.model.price_type !== 1">{{
-            form.model.price_type === 2 ? '最大价格' : '现价'
-          }}</div>
-          <el-input
-            v-model="form.model.price2"
-            placeholder="请输入价格"
-            class="sa-w-160"
-            type="number"
-            :step="0.01"
-            :min="0"
-            :precision="2"
-            v-if="form.model.price_type !== 1"
-          >
-            <template #append>元</template>
-          </el-input>
-        </el-form-item>
-        <el-form-item label="APPID" prop="third_party_appid" v-if="form.model.type === 1">
-          <el-input
-            v-model="form.model.third_party_appid"
-            placeholder="请输入该小程序APPID"
-          ></el-input>
-        </el-form-item>
-
-        <el-form-item label="商品路径" prop="url">
-          <div>
-            <el-input v-model="form.model.url" placeholder="请输入商品路径"></el-input>
-            <div class="desc">
-              请确保小程序页面路径可被访问。例如:pages/goods/index?query=value
-            </div>
-          </div>
-        </el-form-item>
-      </el-form>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button
-        v-if="modal.params.type == 'add'"
-        v-auth="'shop.admin.app.mplive.goods.add'"
-        type="primary"
-        @click="confirm"
-        >保存</el-button
-      >
-      <el-button
-        v-if="modal.params.type == 'edit'"
-        v-auth="'shop.admin.app.mplive.goods.edit'"
-        type="primary"
-        @click="confirm"
-        >更新</el-button
-      >
-    </el-footer>
-  </el-container>
-</template>
-<script setup>
-  import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from '../../app.service';
-  import { cloneDeep } from 'lodash';
-  import GoodsSelect from '@/app/shop/admin/goods/goods/select.vue';
-  import { useModal } from '@/sheep/hooks';
-  import { api as goodsApi } from '@/app/shop/admin/goods/goods.service';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      type: 0,
-      name: '',
-      price_type: 1,
-      third_party_appid: '',
-      price: '',
-      price2: '',
-      cover_img_url: '',
-    },
-    rules: {
-      name: [{ validator: checkTitle, trigger: 'change' }],
-      cover_img_url: [{ required: true, message: '请选择商品封面', trigger: 'change' }],
-    },
-  });
-  const goods = reactive({
-    id: '', // 商品id
-    image: '', // 商品图片
-    title: '', // 商品名称
-  });
-  const loading = ref(false);
-
-  function checkTitle(rule, value, callback) {
-    if (!value) {
-      return callback(new Error('请输入商品名称'));
-    }
-    const length =
-      value.match(/[^ -~]/g) == null ? value.length : value.length + value.match(/[^ -~]/g).length;
-    if (length < 6 || length > 28) {
-      callback(new Error('直播标题必须为3-14个字(一个字等于两个英文字符或特殊字符)'));
-    } else {
-      callback();
-    }
-  }
-  // 获取商品信息
-  async function getGoodsList(id) {
-    const { data } = await goodsApi.goods.select(
-      {
-        search: JSON.stringify({ id }),
-      },
-      'select',
-    );
-    goods.image = data[0].image;
-    goods.title = data[0].title;
-  }
-
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
-    const { error, data } = await api.mplive.goods.detail(id);
-    if (error === 0) {
-      form.model = data;
-      getGoodsList(form.model.goods_id);
-    }
-    loading.value = false;
-  }
-
-  // 表单关闭时提交
-  async function confirm() {
-    // 表单验证
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      loading.value = true;
-      let submitForm = cloneDeep(form.model);
-      const { error } =
-        props.modal.params.type == 'add'
-          ? await api.mplive.goods.add(submitForm)
-          : await api.mplive.goods.edit(props.modal.params.id, submitForm);
-      if (error == 0) {
-        emit('modalCallBack', { event: 'confirm' });
-      }
-      loading.value = false;
-    });
-  }
-  //选择商品
-  function selectGoods() {
-    let id = '';
-    useModal(
-      GoodsSelect,
-      {
-        title: '选择商品',
-        id,
-      },
-      {
-        confirm: (res) => {
-          goods.image = res.data.image;
-          goods.title = res.data.title;
-          goods.id = res.data.id;
-
-          form.model.cover_img_url = res.data.image;
-          form.model.name = res.data.title;
-          form.model.url = 'pages/goods/index?id=' + goods.id;
-          if (res.data.price.length === 2) {
-            form.model.price_type = 2;
-            form.model.price = res.data.price[0];
-            form.model.price2 = res.data.price[1];
-          } else {
-            if (Number(res.data.original_price)) {
-              form.model.price_type = 3;
-              form.model.price = res.data.original_price;
-              form.model.price2 = res.data.price[0];
-            } else {
-              form.model.price_type = 1;
-              form.model.price = res.data.price[0];
-            }
-          }
-          form.model.goods_id = res.data.id;
-        },
-      },
-    );
-  }
-  function deleteItems() {
-    form.model.goods_id = '';
-  }
-  async function init() {
-    if (props.modal.params.id) {
-      await getDetail(props.modal.params.id);
-    }
-  }
-
-  onMounted(() => {
-    init();
-  });
-</script>
-<style lang="scss" scoped>
-  .desc {
-    font-size: 14px;
-    color: #999999;
-  }
-</style>

+ 0 - 294
src/app/shop/admin/app/mplive/goods/index.vue

@@ -1,294 +0,0 @@
-<template>
-  <el-container class="panel-block">
-    <el-header class="sa-header">
-      <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 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.app.mplive.goods.add'"
-            class="sa-button-refresh"
-            type="primary"
-            icon="Plus"
-            @click="addGoods"
-            >添加商品</el-button
-          >
-        </div>
-      </div>
-    </el-header>
-    <el-main class="sa-p-0" v-loading="loading">
-      <el-table height="100%" class="sa-table" :data="goods.data" stripe>
-        <template #empty>
-          <sa-empty />
-        </template>
-        <el-table-column prop="id" label="ID" min-width="80"></el-table-column>
-        <el-table-column label="商品来源" min-width="120" align="center">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.type_text }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="商品封面" min-width="80" align="center">
-          <template #default="scope">
-            <div class="sa-flex sa-row-center">
-              <sa-preview :url="scope.row.cover_img_url" size="30"></sa-preview>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="商品名称" min-width="220" align="center">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.name }}</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.price_type_text }}</div>
-          </template>
-        </el-table-column>
-
-        <el-table-column label="价格" min-width="200" align="center">
-          <template #default="scope">
-            <div class="sa-table-line-1">
-              <div class="sa-flex sa-row-center">
-                <div :class="{ 'price-title': scope.row.price_type === 3 }"
-                  >{{ scope.row.price }}元</div
-                >
-                <div>
-                  {{ formatPrice(scope.row.price2, scope.row.price_type) }}
-                </div>
-              </div>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="商品路径" min-width="300" align="center">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.url }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="审核状态" min-width="120" align="center" fixed="right">
-          <template #default="scope">
-            <div class="sa-flex sa-row-center">
-              <div
-                class="sa-table-line-1"
-                :class="
-                  scope.row.audit_status === 2
-                    ? 'sa-color--success'
-                    : scope.row.audit_status === 3
-                      ? 'sa-color--danger'
-                      : scope.row.audit_status === 1
-                        ? 'sa-color--warning'
-                        : 'sa-color--info'
-                "
-                >{{ scope.row.audit_status_text }}</div
-              >
-              <el-button
-                class="is-link"
-                icon="RefreshRight"
-                @click="getStatus(scope.row.id)"
-              ></el-button>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" min-width="200" fixed="right">
-          <template #default="scope">
-            <el-button
-              v-auth="'shop.admin.app.mplive.goods.detail'"
-              class="is-link"
-              type="primary"
-              @click="editGoods(scope.row.id)"
-              >编辑</el-button
-            >
-            <el-button
-              v-auth="'shop.admin.app.mplive.goods.audit'"
-              class="is-link"
-              type="primary"
-              @click="check(scope.row.id, 'resubmit')"
-              v-if="scope.row.audit_status === 0 || scope.row.audit_status === 3"
-              >提交审核</el-button
-            >
-            <el-button
-              v-auth="'shop.admin.app.mplive.goods.audit'"
-              class="is-link"
-              type="primary"
-              @click="check(scope.row.id, 'reset')"
-              v-if="scope.row.audit_status === 1"
-              >撤回审核</el-button
-            >
-            <el-popconfirm
-              width="fit-content"
-              confirm-button-text="确认"
-              cancel-button-text="取消"
-              title="确认删除这条记录?"
-              @confirm="deleteGoods(scope.row.id)"
-            >
-              <template #reference>
-                <el-button
-                  v-auth="'shop.admin.app.mplive.goods.delete'"
-                  class="is-link"
-                  type="danger"
-                >
-                  删除
-                </el-button>
-              </template>
-            </el-popconfirm>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-main>
-    <sa-view-bar>
-      <template #right>
-        <sa-pagination :pageData="pageData" @updateFn="getData" />
-      </template>
-    </sa-view-bar>
-  </el-container>
-</template>
-<script>
-  export default {
-    name: 'shop.admin.mplive.goods',
-  };
-</script>
-<script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../../app.service';
-  import GoodsEdit from './edit.vue';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { useSearch } from '@/sheep/components/sa-table/sa-search/useSearch';
-  import { composeFilter } from '@/sheep/utils';
-  import { cloneDeep } from 'lodash';
-
-  const loading = ref(true);
-  function formatPrice(price, type) {
-    if (type === 1) {
-      return '';
-    } else if (type === 2) {
-      return '~' + price + '元';
-    } else {
-      return price + '元';
-    }
-  }
-  const filterParams = reactive({
-    tools: {
-      name: {
-        type: 'tinput',
-        field: 'name',
-        value: '',
-        label: '商品名称',
-        placeholder: '请输入商品名称',
-      },
-    },
-    data: {
-      name: '',
-    },
-    conditionLabel: {},
-  });
-  const { openFilter, deleteFilter } = useSearch({ filterParams, getData });
-  const { pageData } = usePagination();
-
-  //商品库表格
-  const goods = reactive({
-    data: [],
-    order: '',
-    sort: '',
-  });
-  // 获取商品库数据
-  async function getData(page) {
-    loading.value = true;
-    if (page) pageData.page = page;
-    let tempSearch = cloneDeep(filterParams.data);
-    let search = composeFilter(tempSearch, {
-      name: 'like',
-    });
-    const { error, data } = await api.mplive.goods.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      order: goods.order,
-      sort: goods.sort,
-      ...search,
-    });
-    if (error === 0) {
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
-      goods.data = data.data;
-    }
-    loading.value = false;
-  }
-
-  //添加商品
-  function addGoods() {
-    useModal(
-      GoodsEdit,
-      {
-        title: '添加商品',
-        type: 'add',
-        width: '80%',
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-  //编辑商品
-  function editGoods(id) {
-    useModal(
-      GoodsEdit,
-      {
-        title: '编辑商品库',
-        type: 'edit',
-        id: id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-  // 删除商品
-  async function deleteGoods(id) {
-    loading.value = true;
-    await api.mplive.goods.delete(id);
-    loading.value = false;
-    getData();
-  }
-  //刷新状态
-  async function getStatus(id) {
-    const { error } = await api.mplive.goods.status(id);
-    if (error === 0) {
-      getData();
-    }
-  }
-
-  //审核
-  async function check(id, act) {
-    const { error } = await api.mplive.goods.audit(id, {
-      act,
-    });
-    if (error === 0) {
-      getData();
-    }
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>
-<style lang="scss" scoped>
-  .price-title {
-    text-decoration: line-through;
-    margin-right: 8px;
-    color: var(--sa-subfont);
-  }
-  .live-qrcode {
-    width: 150px;
-    height: 150px;
-  }
-</style>

+ 0 - 34
src/app/shop/admin/app/mplive/index.vue

@@ -1,34 +0,0 @@
-<template>
-  <el-container class="panel-block">
-    <el-header class="sa-header">
-      <el-tabs class="sa-tabs" v-model="dispatchType">
-        <el-tab-pane label="直播间管理" name="live"></el-tab-pane>
-        <el-tab-pane label="商品库管理" name="goods"></el-tab-pane>
-      </el-tabs>
-    </el-header>
-    <room v-if="dispatchType === 'live'"></room>
-    <goods v-if="dispatchType === 'goods'"></goods>
-  </el-container>
-</template>
-<script>
-  export default {
-    name: 'shop.admin.mplive',
-  };
-</script>
-<script setup>
-  import {ref} from 'vue';
-  import room from './room/index.vue';
-  import goods from './goods/index.vue';
-  const dispatchType = ref('live');
-</script>
-<style lang="scss" scoped>
-  .price-title {
-    text-decoration: line-through;
-    margin-right: 8px;
-    color: var(--sa-subfont);
-  }
-  .live-qrcode {
-    width: 150px;
-    height: 150px;
-  }
-</style>

+ 0 - 302
src/app/shop/admin/app/mplive/room/edit.vue

@@ -1,302 +0,0 @@
-<template>
-  <el-container v-loading="loading">
-    <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="120px">
-        <el-form-item label="直播类型" prop="type">
-          <div>
-            <el-radio-group v-model="form.model.type">
-              <el-radio :label="0">手机直播</el-radio>
-              <el-radio :label="1">推流设备直播</el-radio>
-            </el-radio-group>
-            <div class="desc" v-if="form.model.type === 0">通过“小程序直播”小程序开播</div>
-            <div class="desc" v-if="form.model.type === 1"
-              >通过第三方推流设备发起直播,请自行定义画面宽高比</div
-            >
-          </div>
-        </el-form-item>
-        <!-- <el-form-item label="播放方式" prop="way">
-            <div>
-              <el-radio-group v-model="form.model.way">
-                <el-radio label="column">竖屏</el-radio>
-                <el-radio label="row">横屏</el-radio>
-              </el-radio-group>
-              <div class="desc"
-                >横屏:视频宽高比为16:9、4:3、1.85:1 ;竖屏:视频宽高比为9:16、2:3</div
-              >
-            </div>
-          </el-form-item> -->
-        <el-form-item label="直播间标题" prop="name">
-          <el-input v-model="form.model.name" placeholder="请输入直播间标题"></el-input>
-        </el-form-item>
-        <el-form-item label="背景图" prop="cover_img">
-          <sa-uploader
-            v-model="form.model.cover_img"
-            fileType="image"
-            :filesize="2048"
-          ></sa-uploader>
-          <div class="desc sa-m-l-12"> 直播间背景图,图片建议像素1080*1920,大小不超过2M </div>
-        </el-form-item>
-        <el-form-item label="分享图" prop="share_img">
-          <sa-uploader
-            v-model="form.model.share_img"
-            fileType="image"
-            :filesize="1024"
-          ></sa-uploader>
-          <div class="desc sa-m-l-12"> 直播间分享图,图片建议像素800*640,大小不超过1M </div>
-        </el-form-item>
-        <el-form-item label="封面图" prop="feeds_img">
-          <sa-uploader
-            v-model="form.model.feeds_img"
-            fileType="image"
-            :filesize="100"
-          ></sa-uploader>
-          <div class="desc sa-m-l-12">
-            购物直播频道封面图,图片建议像素800*800,大小不超过100KB
-          </div>
-        </el-form-item>
-        <el-form-item label="开播时间" prop="date_time">
-          <div>
-            <el-date-picker
-              v-model="form.model.date_time"
-              type="datetimerange"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              format="YYYY-MM-DD HH:mm:ss"
-              :default-time="defaultTime"
-              range-separator="至"
-              start-placeholder="开始时间"
-              end-placeholder="结束时间"
-              prefix-icon="Calendar"
-              :editable="false"
-              :disabled-date="disabledDate"
-            />
-            <div class="desc">
-              开播时间需要在当前时间的30分钟后 并且 开始时间不能在 6 个月后<br />
-              开播时间和结束时间间隔不得短于30分钟,不得超过72小时<br />
-              直播间在开始时间前也可以开播,并且到结束时间后不会强制结束。<br />
-              若到结束时间仍未开播,则直播间无法再开播。
-            </div>
-          </div>
-        </el-form-item>
-        <el-form-item label="主播昵称" prop="anchor_name">
-          <el-input v-model="form.model.anchor_name" placeholder="请输入主播昵称"></el-input>
-        </el-form-item>
-        <el-form-item label="主播微信账号" prop="anchor_wechat">
-          <div>
-            <el-input
-              v-model="form.model.anchor_wechat"
-              placeholder="请输入主播微信账号"
-            ></el-input>
-            <div class="desc">
-              每个直播间需要绑定一个用作核实主播身份,不会展示给观众。<br />
-              主播微信号,如果未实名认证,需要先前往“小程序直播”小程序进行实名验证。
-            </div>
-
-            <el-popover :width="180" trigger="click">
-              <template #reference>
-                <el-button class="is-link" type="primary">小程序认证</el-button>
-              </template>
-              <img class="qrcode-img" src="/static/images/wechatMplive/live-qrcode.png" />
-            </el-popover>
-          </div>
-        </el-form-item>
-        <el-form-item label="主播副号" prop="sub_anchor_wechat">
-          <el-input v-model="form.model.sub_anchor_wechat" placeholder="请输入主播副号"></el-input>
-        </el-form-item>
-        <el-form-item label="官方收录">
-          <div>
-            <el-switch
-              v-model="form.model.is_feeds_public"
-              :active-value="1"
-              :inactive-value="0"
-            ></el-switch>
-            <div class="desc">
-              开启后本场直播将有可能被官方推荐。<br />
-              此项设置在直播间创建完成后可以在控制台内修改。
-            </div>
-          </div>
-        </el-form-item>
-
-        <el-form-item label="允许点赞">
-          <el-radio-group v-model="form.model.close_like">
-            <el-radio :label="0">开启</el-radio>
-            <el-radio :label="1">关闭</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="展示商品货架">
-          <el-radio-group v-model="form.model.close_goods">
-            <el-radio :label="0">开启</el-radio>
-            <el-radio :label="1">关闭</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="允许评论">
-          <el-radio-group v-model="form.model.close_comment">
-            <el-radio :label="0">开启</el-radio>
-            <el-radio :label="1">关闭</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="允许回放">
-          <el-radio-group v-model="form.model.close_replay">
-            <el-radio :label="0">开启</el-radio>
-            <el-radio :label="1">关闭</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="打开客服">
-          <el-radio-group v-model="form.model.close_kf">
-            <el-radio :label="0">开启</el-radio>
-            <el-radio :label="1">关闭</el-radio>
-          </el-radio-group>
-        </el-form-item>
-      </el-form>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button
-        v-if="modal.params.type == 'add'"
-        v-auth="'shop.admin.app.mplive.room.add'"
-        type="primary"
-        @click="confirm"
-        >保存</el-button
-      >
-      <el-button
-        v-if="modal.params.type == 'edit'"
-        v-auth="'shop.admin.app.mplive.room.edit'"
-        type="primary"
-        @click="confirm"
-        >更新</el-button
-      >
-    </el-footer>
-  </el-container>
-</template>
-<script setup>
-  import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from '../../app.service';
-  import { cloneDeep } from 'lodash';
-  import dayjs from 'dayjs';
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      way: 'column',
-      type: 0,
-      name: '',
-      date_time: '',
-      anchor_name: '',
-      anchor_wechat: '',
-      sub_anchor_wechat: '',
-      is_feeds_public: 1,
-      close_kf: 0,
-      close_replay: 0,
-      close_comment: 0,
-      close_goods: 0,
-      close_like: 0,
-      feeds_img: '',
-      share_img: '',
-      cover_img: '',
-    },
-    rules: {
-      type: [{ required: true, message: '请选择直播类型', trigger: 'change' }],
-      way: [{ required: true, message: '请选择播放方式', trigger: 'change' }],
-      feeds_img: [{ required: true, message: '请选择封面图', trigger: 'change' }],
-      share_img: [{ required: true, message: '请选择分享图', trigger: 'change' }],
-      cover_img: [{ required: true, message: '请选择背景图', trigger: 'change' }],
-      date_time: [{ required: true, message: '请选择开播时间', trigger: 'change' }],
-      name: [{ validator: checkTitle, trigger: 'change' }],
-      anchor_name: [{ validator: checkNickname, trigger: 'change' }],
-      anchor_wechat: [{ required: true, message: '请输入主播微信账号', trigger: 'blur' }],
-    },
-  });
-  const loading = ref(false);
-  //获取默认开始时间为当前时间后40分钟
-  const defaultTime = ref([
-    new Date(new Date().getTime() + 40 * 60 * 1000),
-    new Date(2000, 2, 1, 23, 59, 59),
-  ]);
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
-    const { error, data } = await api.mplive.room.detail(id);
-    if (error === 0) {
-      form.model = data;
-      form.model.date_time = [
-        dayjs(data.start_time * 1000).format('YYYY-MM-DD HH:mm:ss'),
-        dayjs(data.end_time * 1000).format('YYYY-MM-DD HH:mm:ss'),
-      ];
-    }
-    loading.value = false;
-  }
-  // 禁止时间
-  function disabledDate(time) {
-    return time.getTime() < Date.now() - 86400000;
-  }
-  // 表单关闭时提交
-  async function confirm() {
-    // 表单验证
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      loading.value = true;
-      let submitForm = cloneDeep(form.model);
-      submitForm.start_time = Number(new Date(form.model.date_time[0]).getTime() / 1000);
-      submitForm.end_time = Number(new Date(form.model.date_time[1]).getTime() / 1000);
-      delete submitForm.date_time;
-      const { error } =
-        props.modal.params.type == 'add'
-          ? await api.mplive.room.add(submitForm)
-          : await api.mplive.room.edit(props.modal.params.id, submitForm);
-      if (error == 0) {
-        emit('modalCallBack', { event: 'confirm' });
-      }
-      loading.value = false;
-    });
-  }
-  function checkTitle(rule, value, callback) {
-    if (!value) {
-      return callback(new Error('请输入直播间标题'));
-    }
-    const length =
-      value.match(/[^ -~]/g) == null ? value.length : value.length + value.match(/[^ -~]/g).length;
-    if (length < 6 || length > 34) {
-      callback(new Error('直播标题必须为3-17个字(一个字等于两个英文字符或特殊字符)'));
-    } else {
-      callback();
-    }
-  }
-  function checkNickname(rule, value, callback) {
-    if (!value) {
-      return callback(new Error('请输入主播昵称'));
-    }
-    const length =
-      value.match(/[^ -~]/g) == null ? value.length : value.length + value.match(/[^ -~]/g).length;
-    if (length < 4 || length > 30) {
-      callback(new Error('直播标题必须为2-15个字(一个字等于两个英文字符或特殊字符)'));
-    } else {
-      callback();
-    }
-  }
-  async function init() {
-    if (props.modal.params.id) {
-      await getDetail(props.modal.params.id);
-    }
-  }
-
-  onMounted(() => {
-    init();
-  });
-</script>
-<style lang="scss" scoped>
-  .desc {
-    font-weight: normal;
-    font-size: 12px;
-    line-height: 18px;
-    color: var(--sa-subfont);
-  }
-
-  .qrcode-img {
-    width: 150px;
-    height: 150px;
-  }
-</style>

+ 0 - 322
src/app/shop/admin/app/mplive/room/index.vue

@@ -1,322 +0,0 @@
-<template>
-  <el-container class="panel-block">
-    <el-header class="sa-header">
-      <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">
-          <span class="left">直播间管理</span>
-        </div>
-        <div>
-          <el-popover placement="bottom" :width="174" trigger="click">
-            <template #reference>
-              <el-button class="sa-button-refresh">主播端</el-button>
-            </template>
-            <img class="live-qrcode" src="/static/images/wechatMplive/live-qrcode.png" />
-          </el-popover>
-
-          <el-button
-            v-auth="'shop.admin.app.mplive.room.sync'"
-            class="sa-button-refresh"
-            icon="RefreshRight"
-            @click="sync"
-            :loading="loading"
-            >同步直播间</el-button
-          >
-          <el-button
-            v-auth="'shop.admin.app.mplive.room.add'"
-            class="sa-button-refresh"
-            type="primary"
-            icon="Plus"
-            @click="addRow"
-            >创建直播间</el-button
-          >
-        </div>
-      </div>
-    </el-header>
-    <el-main class="sa-p-0" v-loading="loading">
-      <el-table height="100%" class="sa-table" :data="live.data" stripe @sort-change="fieldFilter">
-        <template #empty>
-          <sa-empty />
-        </template>
-        <el-table-column label="房间ID" min-width="120" align="center" sortable="custom">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.roomid }}</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.type_text }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="直播间标题" min-width="200" align="center">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.name }}</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.anchor_name }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="状态" min-width="120" align="center">
-          <template #default="scope">
-            <div
-              class="sa-table-line-1"
-              :class="
-                scope.row.status === 101
-                  ? 'sa-color--success'
-                  : scope.row.status === 102
-                  ? 'sa-color--warning'
-                  : scope.row.status === 105
-                  ? 'sa-color--info'
-                  : 'sa-color--danger'
-              "
-              >{{ scope.row.status_text }}</div
-            >
-          </template>
-        </el-table-column>
-        <el-table-column label="背景图" min-width="80" align="center">
-          <template #default="scope">
-            <div class="sa-flex sa-row-center">
-              <sa-preview :url="scope.row.cover_img" size="30"></sa-preview>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="分享图" min-width="80" align="center">
-          <template #default="scope">
-            <div class="sa-flex sa-row-center">
-              <sa-preview :url="scope.row.share_img" size="30"></sa-preview>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="封面图" min-width="80" align="center">
-          <template #default="scope">
-            <div class="sa-flex sa-row-center">
-              <sa-preview :url="scope.row.feeds_img" size="30"></sa-preview>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="开播时间" min-width="200" align="center">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{
-              dayjs(scope.row.start_time * 1000).format('YYYY-MM-DD HH:mm')
-            }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="结束时间" min-width="200" align="center">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{
-              dayjs(scope.row.end_time * 1000).format('YYYY-MM-DD HH:mm')
-            }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" min-width="340" fixed="right" align="center">
-          <template #default="scope">
-            <el-button
-              v-auth="'shop.admin.app.mplive.room.detail'"
-              class="is-link"
-              type="primary"
-              @click="editRow(scope.row.roomid)"
-              >编辑</el-button
-            >
-            <el-button
-              v-auth="'shop.admin.app.mplive.room.pushUrl'"
-              class="is-link"
-              type="primary"
-              @click="pushUrl(scope.row.roomid)"
-              v-if="scope.row.type === 1 && (scope.row.status === 101 || scope.row.status === 102)"
-              >推流地址</el-button
-            >
-            <el-button
-              v-auth="'shop.admin.app.mplive.room.qrcode'"
-              class="is-link"
-              type="primary"
-              @click="shareQrcode(scope.row.roomid)"
-              v-if="scope.row.status === 101 || scope.row.status === 102"
-              >分享二维码</el-button
-            >
-            <el-button
-              v-auth="'shop.admin.app.mplive.room.playback'"
-              class="is-link"
-              type="primary"
-              @click="playBack(scope.row.roomid)"
-              v-if="scope.row.status === 103"
-              >回放</el-button
-            >
-            <el-popconfirm
-              width="fit-content"
-              confirm-button-text="确认"
-              cancel-button-text="取消"
-              title="确认删除这条记录?"
-              @confirm="deleteApi(scope.row.roomid)"
-            >
-              <template #reference>
-                <el-button
-                  v-auth="'shop.admin.app.mplive.room.delete'"
-                  class="is-link"
-                  type="danger"
-                >
-                  删除
-                </el-button>
-              </template>
-            </el-popconfirm>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-main>
-  </el-container>
-</template>
-<script>
-  export default {
-    name: 'shop.admin.mplive.room',
-  };
-</script>
-<script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../../app.service';
-  import liveEdit from './edit.vue';
-  import PushUrl from './pushUrl.vue';
-  import ShareQrcode from './qrcode.vue';
-  import dayjs from 'dayjs';
-  import { useModal } from '@/sheep/hooks';
-  import PlayBack from './playback.vue';
-
-  const loading = ref(true);
-
-  // 直播间表格
-  const live = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  // 获取直播间数据
-  async function getLiveData() {
-    loading.value = true;
-    const { error, data } = await api.mplive.room.list({
-      order: live.order,
-      sort: live.sort,
-    });
-    if (error === 0) {
-      live.data = data;
-    }
-    loading.value = false;
-  }
-
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    live.order = order == 'ascending' ? 'asc' : 'desc';
-    live.sort = prop;
-    getLiveData();
-  }
-
-  //添加直播间
-  function addRow() {
-    useModal(
-      liveEdit,
-      {
-        title: '添加直播间',
-        type: 'add',
-      },
-      {
-        confirm: () => {
-          getLiveData();
-        },
-      },
-    );
-  }
-  //编辑直播间
-  function editRow(id) {
-    useModal(
-      liveEdit,
-      {
-        title: '编辑直播间',
-        type: 'edit',
-        id: id,
-      },
-      {
-        confirm: () => {
-          getLiveData();
-        },
-      },
-    );
-  }
-  // 删除直播间
-  async function deleteApi(id) {
-    loading.value = true;
-    await api.mplive.room.delete(id);
-    loading.value = false;
-    getLiveData();
-  }
-  //推流地址
-  async function pushUrl(id) {
-    useModal(
-      PushUrl,
-      {
-        title: '推流地址',
-        id: id,
-      },
-      {
-        confirm: () => {
-          getLiveData();
-        },
-      },
-    );
-  }
-
-  //分享二维码
-  async function shareQrcode(id) {
-    useModal(
-      ShareQrcode,
-      {
-        title: '分享二维码',
-        id: id,
-      },
-      {
-        confirm: () => {
-          getLiveData();
-        },
-      },
-    );
-  }
-
-  //同步直播间
-  async function sync() {
-    loading.value = true;
-    const { error, data } = await api.mplive.room.sync();
-    if (error === 0) {
-      getLiveData();
-    }
-    loading.value = false;
-  }
-  //直播回放
-  async function playBack(id) {
-    useModal(
-      PlayBack,
-      {
-        title: '直播回放列表',
-        id: id,
-      },
-      {
-        confirm: () => {
-          getLiveData();
-        },
-      },
-    );
-  }
-
-  onMounted(() => {
-    getLiveData();
-  });
-</script>
-<style lang="scss" scoped>
-  .price-title {
-    text-decoration: line-through;
-    margin-right: 8px;
-    color: var(--sa-subfont);
-  }
-
-  .live-qrcode {
-    width: 144px;
-    height: 144px;
-  }
-</style>

+ 0 - 118
src/app/shop/admin/app/mplive/room/playback.vue

@@ -1,118 +0,0 @@
-<template>
-    <el-container v-loading="loading">
-      <el-main>
-        <el-table :data="table.data" class="sa-table" stripe>
-          <template #empty>
-            <sa-empty />
-          </template>
-          <el-table-column label="回放片段" min-width="140">
-            <template #default="scope">
-              <div class="sa-table-line-1">{{ '片段' + scope.row.index || '-' }}</div>
-            </template>
-          </el-table-column>
-          <el-table-column label="创建时间" min-width="140">
-            <template #default="scope">
-              <div class="sa-table-line-1">{{
-                dayjs(scope.row.create_time).format('YYYY-MM-DD HH:mm:ss') || '-'
-              }}</div>
-            </template>
-          </el-table-column>
-          <el-table-column label="过期时间" min-width="140">
-            <template #default="scope">
-              <div class="sa-table-line-1">{{
-                dayjs(scope.row.expire_time).format('YYYY-MM-DD HH:mm:ss') || '-'
-              }}</div>
-            </template>
-          </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="120">
-            <template #default="scope">
-              <el-button class="is-link" type="primary" @click="play(scope.row.media_url)"
-                >播放</el-button
-              >
-            </template>
-          </el-table-column>
-        </el-table>
-        <!-- <sa-view-bar>
-          <template #right>
-            <sa-pagination :pageData="pageData" @updateFn="getData" />
-          </template>
-        </sa-view-bar> -->
-      </el-main>
-    </el-container>
-  </template>
-  <script setup>
-    import { api } from '../../app.service';
-    import { onMounted, reactive, ref } from 'vue';
-    import dayjs from 'dayjs';
-    import { usePagination } from '@/sheep/hooks';
-    const props = defineProps({
-      modal: {
-        type: Object,
-      },
-    });
-    // 表格状态
-    const table = reactive({
-      data: [],
-      order: '',
-      sort: '',
-      selected: [],
-    });
-    const loading = ref(false);
-    const { pageData } = usePagination();
-    async function getData(id) {
-      loading.value = true;
-      const { error, data } = await api.mplive.playback(id, {
-        page: pageData.page,
-        list_rows: pageData.list_rows,
-        order: table.order,
-        sort: table.sort,
-      });
-      if (error === 0) {
-        table.data = data;
-        table.data.forEach((item, index) => {
-          table.data[index].index = index + 1;
-        });
-      }
-      loading.value = false;
-    }
-    function play(url) {
-      window.open(url);
-    }
-  
-    async function init() {
-      if (props.modal.params.id) {
-        await getData(props.modal.params.id);
-      }
-    }
-  
-    onMounted(() => {
-      init();
-    });
-  </script>
-  <style lang="scss" scoped>
-    .program {
-      width: 360px;
-      height: 260px;
-      background: #f5f5f5;
-      border-radius: 4px;
-    }
-    .path {
-      width: 360px;
-      height: 260px;
-      background: #f5f5f5;
-      border-radius: 4px;
-    }
-    .title {
-      font-size: 14px;
-      line-height: 20px;
-      color: var(--sa-subtitle);
-    }
-    .desc {
-      color: var(--sa-subfont);
-      font-size: 14px;
-      line-height: 20px;
-      word-break: break-all;
-      word-wrap: break-word;
-    }
-  </style>
-  

+ 0 - 85
src/app/shop/admin/app/mplive/room/pushUrl.vue

@@ -1,85 +0,0 @@
-<template>
-    <el-container v-loading="loading">
-      <el-main>
-        <div class="title sa-m-b-16">推流地址</div>
-        <div class="sa-flex sa-m-b-4">
-          <div class="desc sa-m-r-4">在第三方推流应用中,以下地址进行推流</div>
-          <el-button class="is-link" type="primary" @click="onCopy">复制链接</el-button>
-        </div>
-        <div class="desc sa-m-b-16">{{ state.pushUrl }}</div>
-        <div class="subtitle sa-m-b-40">此地址为当前直播间唯一推流地址,不要泄露给第三方。</div>
-        <div class="title sa-m-b-16">备注</div>
-        <div class="desc sa-m-b-4">服务器地址:{{ state.serverAddress }}</div>
-        <div class="desc sa-m-b-16">串流密钥:{{ state.key }}</div>
-        <div class="sa-flex">
-          <div class="subtitle sa-m-r-4">推流直播操作详见</div>
-          <el-button class="is-link" type="primary" @click="onJump">指引</el-button>
-        </div>
-      </el-main>
-    </el-container>
-  </template>
-  <script setup>
-    import { api } from '../../app.service';
-    import handleClipboard from '@/sheep/utils/clipboard';
-    import { onMounted, reactive, ref } from 'vue';
-  
-    const props = defineProps({
-      modal: {
-        type: Object,
-      },
-    });
-    const loading = ref(false);
-    const state = reactive({
-      pushUrl: '', // 推流地址
-      serverAddress: '', // 服务器地址
-      key: '', // 串流密钥
-    });
-    async function getDetail(id) {
-      loading.value = true;
-      const { error, data } = await api.mplive.room.pushUrl(id);
-      if (error === 0) {
-        state.pushUrl = data.pushAddr;
-        state.serverAddress = state.pushUrl.split('/live/')[0] + '/live/';
-        state.key = state.pushUrl.split('/live/')[1];
-      }
-      loading.value = false;
-    }
-  
-    async function init() {
-      if (props.modal.params.id) {
-        await getDetail(props.modal.params.id);
-      }
-    }
-    //复制推流地址
-    function onCopy() {
-      handleClipboard(state.pushUrl);
-    }
-  
-    function onJump() {
-      window.open('https://docs.qq.com/doc/DV0hoWHZRdm9oT2pp');
-    }
-  
-    onMounted(() => {
-      init();
-    });
-  </script>
-  <style lang="scss" scoped>
-    .title {
-      font-size: 16px;
-      line-height: 24px;
-      color: var(--sa-title);
-    }
-    .desc {
-      font-size: 14px;
-      line-height: 20px;
-      color: var(--sa-subtitle);
-      word-break: break-all;
-      word-wrap: break-word;
-    }
-    .subtitle {
-      color: var(--sa-subfont);
-      font-size: 14px;
-      line-height: 20px;
-    }
-  </style>
-  

+ 0 - 102
src/app/shop/admin/app/mplive/room/qrcode.vue

@@ -1,102 +0,0 @@
-<template>
-    <el-container v-loading="loading">
-      <el-main>
-        <div class="sa-flex sa-flex-wrap sa-row-between">
-          <div class="program sa-flex sa-col-top sa-row-between sa-p-16 sa-m-b-30">
-            <div>
-              <div class="title sa-m-b-8">直播间小程序码</div>
-              <div class="desc sa-m-b-24">小程序码不带参数</div>
-              <el-button type="primary" @click="saveImg">保存图片</el-button>
-            </div>
-            <sa-image class="" :url="state.cdnUrl" size="120"></sa-image>
-          </div>
-          <div class="path sa-p-16 sa-m-b-30">
-            <div class="title sa-m-b-16">直播间页面路径</div>
-            <div class="desc sa-m-b-24">{{ state.path }}</div>
-            <el-button type="primary" @click="onCopy">复制链接</el-button>
-            <div class="desc sa-m-t-40">链接是直播间原始页面路径,如需加入参数,详见<el-button class="is-link" type="primary"
-                @click="onJump">使用方法</el-button></div>
-          </div>
-        </div>
-      </el-main>
-    </el-container>
-  </template>
-  <script setup>
-  import { api } from '../../app.service';
-  import handleClipboard from '@/sheep/utils/clipboard';
-  import { onMounted, reactive, ref } from 'vue';
-  
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
-  const loading = ref(false);
-  const state = reactive({
-    cdnUrl: '', // 小程序码
-    path: '', // 页面路径
-    key: '', // 串流密钥
-  });
-  async function getDetail(id) {
-    loading.value = true;
-    const { error, data } = await api.mplive.room.qrcode(id);
-    if (error === 0) {
-      state.cdnUrl = data.cdnUrl;
-      state.path = data.pagePath;
-    }
-    loading.value = false;
-  }
-  
-  async function init() {
-    if (props.modal.params.id) {
-      await getDetail(props.modal.params.id);
-    }
-  }
-  //复制推流地址
-  function onCopy() {
-    handleClipboard(state.path);
-  }
-  function saveImg() {
-    window.open(state.cdnUrl);
-  }
-  
-  function onJump() {
-    window.open(
-      'https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/liveplayer/live-player-plugin.html',
-    );
-  }
-  
-  onMounted(() => {
-    init();
-  });
-  </script>
-  <style lang="scss" scoped>
-  .program {
-    width: 360px;
-    height: 260px;
-    background: #f5f5f5;
-    border-radius: 4px;
-  }
-  
-  .path {
-    width: 360px;
-    height: 260px;
-    background: #f5f5f5;
-    border-radius: 4px;
-  }
-  
-  .title {
-    font-size: 14px;
-    line-height: 20px;
-    color: var(--sa-subtitle);
-  }
-  
-  .desc {
-    color: var(--sa-subfont);
-    font-size: 14px;
-    line-height: 20px;
-    word-break: break-all;
-    word-wrap: break-word;
-  }
-  </style>
-  

+ 0 - 126
src/app/shop/admin/app/mplive/select.vue

@@ -1,126 +0,0 @@
-<template>
-    <el-container class="panel-block">
-        <el-main v-loading="loading">
-            <el-table height="100%" class="sa-table" :data="state.data" stripe @selection-change="onSelectionChange">
-                <template #empty>
-                    <sa-empty />
-                </template>
-                <el-table-column type="selection" :selectable="isSelectable" width="48"></el-table-column>
-                <el-table-column label="房间ID" min-width="120" align="center">
-                    <template #default="scope">
-                        <div class="sa-table-line-1">{{ scope.row.roomid }}</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.type_text }}</div>
-                    </template>
-                </el-table-column>
-                <el-table-column label="直播间标题" min-width="200" align="center">
-                    <template #default="scope">
-                        <div class="sa-table-line-1">{{ scope.row.name }}</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.anchor_name }}</div>
-                    </template>
-                </el-table-column>
-                <el-table-column label="状态" min-width="120" align="center">
-                    <template #default="scope">
-                        <div class="sa-table-line-1" :class="
-                            scope.row.status === 101
-                                ? 'sa-color--success'
-                                : scope.row.status === 102
-                                    ? 'sa-color--warning'
-                                    : scope.row.status === 105
-                                        ? 'sa-color--info'
-                                        : 'sa-color--danger'
-                        ">{{ scope.row.status_text }}</div>
-                    </template>
-                </el-table-column>
-                <el-table-column label="背景图" min-width="80" align="center">
-                    <template #default="scope">
-                        <div class="sa-flex sa-row-center">
-                            <sa-preview :url="scope.row.cover_img" size="30"></sa-preview>
-                        </div>
-                    </template>
-                </el-table-column>
-                <el-table-column label="分享图" min-width="80" align="center">
-                    <template #default="scope">
-                        <div class="sa-flex sa-row-center">
-                            <sa-preview :url="scope.row.share_img" size="30"></sa-preview>
-                        </div>
-                    </template>
-                </el-table-column>
-                <el-table-column label="封面图" min-width="80" align="center">
-                    <template #default="scope">
-                        <div class="sa-flex sa-row-center">
-                            <sa-preview :url="scope.row.feeds_img" size="30"></sa-preview>
-                        </div>
-                    </template>
-                </el-table-column>
-                <el-table-column label="开播时间" min-width="200" align="center">
-                    <template #default="scope">
-                        <div class="sa-table-line-1">{{
-                            dayjs(scope.row.start_time * 1000).format('YYYY-MM-DD HH:mm')
-                        }}</div>
-                    </template>
-                </el-table-column>
-                <el-table-column label="结束时间" min-width="200" align="center">
-                    <template #default="scope">
-                        <div class="sa-table-line-1">{{
-                            dayjs(scope.row.end_time * 1000).format('YYYY-MM-DD HH:mm')
-                        }}</div>
-                    </template>
-                </el-table-column>
-            </el-table>
-        </el-main>
-        <el-footer class="sa-footer--submit">
-            <el-button type="primary" @click="onConfirm">确定</el-button>
-        </el-footer>
-    </el-container>
-</template>
-
-<script setup>
-import { onMounted, reactive, ref } from 'vue';
-import { api } from '../app.service';
-import dayjs from 'dayjs';
-
-const emit = defineEmits(['modalCallBack']);
-const props = defineProps(['modal']);
-
-const loading = ref(true);
-
-const state = reactive({
-    data: [],
-    selected: [],
-});
-
-async function getData() {
-    loading.value = true;
-    const { error, data } = await api.mplive.room.select();
-    if (error === 0) {
-        state.data = data;
-    }
-    loading.value = false;
-}
-
-function isSelectable(row) {
-    return row.status === 101 || row.status === 102 || row.status === 103
-}
-function onSelectionChange(val) {
-    state.selected = val
-}
-
-function onConfirm() {
-    emit('modalCallBack', {
-        event: 'confirm',
-        data: state.selected,
-    });
-}
-
-onMounted(() => {
-    getData();
-});
-</script>

+ 0 - 196
src/app/shop/admin/app/scoreShop/edit.vue

@@ -1,196 +0,0 @@
-<template>
-  <el-container>
-    <el-main>
-      <el-button v-if="isEmpty(goods.data) && modal.params.type == 'add'" @click="selectGoods"
-        >选择商品</el-button
-      >
-      <template v-if="!isEmpty(goods.data)">
-        <div class="title sa-m-b-20">商品名称:{{ goods.data.title }}</div>
-        <div class="sku-table-wrap">
-          <table class="sku-table" rules="none">
-            <thead>
-              <tr>
-                <th class="sku-item" v-for="ss in goods.skus" :key="ss">
-                  {{ ss.name }}
-                </th>
-                <th class="sku-item">是否参与</th>
-                <th class="sku-item">商品价格</th>
-                <th class="sku-item">可兑换数量</th>
-                <th class="sku-item">兑换积分</th>
-                <th class="sku-item">兑换价格</th>
-              </tr>
-            </thead>
-            <tbody>
-              <tr v-for="(sp, spindex) in goods.skuPrice" :key="sp">
-                <td class="sku-item" v-for="st in sp.goods_sku_text" :key="st">
-                  {{ st }}
-                </td>
-                <td class="sku-item">
-                  <el-switch
-                    v-model="goods.score_sku_prices[spindex].status"
-                    active-value="up"
-                    inactive-value="down"
-                  />
-                </td>
-                <td class="sku-item">{{ sp.price }}</td>
-                <th class="sku-item">
-                  <el-input
-                    v-if="goods.score_sku_prices[spindex].status == 'up'"
-                    type="number"
-                    v-model="goods.score_sku_prices[spindex].stock"
-                  ></el-input>
-                </th>
-                <th class="sku-item">
-                  <el-input
-                    v-if="goods.score_sku_prices[spindex].status == 'up'"
-                    type="number"
-                    v-model="goods.score_sku_prices[spindex].score"
-                  ></el-input>
-                </th>
-                <th class="sku-item">
-                  <el-input
-                    v-if="goods.score_sku_prices[spindex].status == 'up'"
-                    type="number"
-                    v-model="goods.score_sku_prices[spindex].price"
-                  ></el-input>
-                </th>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-      </template>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button
-        v-if="modal.params.type == 'add'"
-        v-auth="'shop.admin.app.scoreshop.add'"
-        type="primary"
-        @click="modelBack"
-        >保存</el-button
-      >
-      <el-button
-        v-if="modal.params.type == 'edit'"
-        v-auth="'shop.admin.app.scoreshop.edit'"
-        type="primary"
-        @click="modelBack"
-        >更新</el-button
-      >
-    </el-footer>
-  </el-container>
-</template>
-<script setup>
-  import { onMounted, reactive } from 'vue';
-  import { api } from '../app.service';
-  import { api as goodsApi } from '@/app/shop/admin/goods/goods.service';
-  import { useModal } from '@/sheep/hooks';
-  import { isEmpty } from 'lodash';
-  import { ElMessage } from 'element-plus';
-  import GoodsSelect from '../../goods/goods/select.vue';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps(['modal']);
-
-  function selectGoods() {
-    useModal(
-      GoodsSelect,
-      {
-        title: '选择商品',
-        ftype: 'score_shop',
-      },
-      {
-        confirm: (res) => {
-          goods.data = res.data;
-          getSkus(res.data.id);
-        },
-      },
-    );
-  }
-
-  const goods = reactive({
-    data: props.modal.params.data || {},
-    skus: [],
-    skuPrice: [],
-    score_sku_prices: [],
-  });
-
-  async function getSkus(id) {
-    const { data } = await api.scoreShop.skus(id);
-    goods.skus = data.skus;
-    goods.skuPrice = data.sku_prices;
-    goods.score_sku_prices = data.score_sku_prices;
-  }
-
-  async function modelBack() {
-    let flag = true;
-    goods.score_sku_prices.forEach((s) => {
-      if (s.status == 'up' && s.score <= 0) {
-        flag = false;
-      }
-    });
-    if (!flag) {
-      ElMessage({
-        message: '兑换积分必须大于0',
-        type: 'warning',
-      });
-      return false;
-    }
-
-    const { error } =
-      props.modal.params.type == 'add'
-        ? await api.scoreShop.add({
-            goods_id: goods.data.id,
-            sku_prices: goods.score_sku_prices,
-          })
-        : await api.scoreShop.edit(goods.data.id, {
-            sku_prices: goods.score_sku_prices,
-          });
-    if (error == 0) {
-      emit('modalCallBack', { event: 'confirm' });
-    }
-  }
-
-  onMounted(() => {
-    if (props.modal.params.type == 'edit') {
-      getSkus(props.modal.params.data.id);
-    }
-  });
-</script>
-<style lang="scss" scoped>
-  .title {
-    line-height: 22px;
-    font-size: 14px;
-    font-weight: 400;
-    color: var(--sa-subtitle);
-  }
-  .sku-table-wrap {
-    width: 100%;
-    overflow: auto;
-    .sku-table {
-      font-size: 12px;
-      font-weight: 500;
-      thead {
-        line-height: 40px;
-        background: var(--sa-table-header-bg);
-        color: var(--subtitle);
-      }
-      tbody {
-        tr {
-          line-height: 48px;
-          color: var(--sa-font);
-          &:nth-of-type(2n) {
-            background: var(--sa-table-striped);
-          }
-        }
-      }
-      th,
-      td {
-        padding: 0 16px;
-        text-align: left;
-      }
-    }
-    .sku-item {
-      min-width: 100px;
-      flex-shrink: 0;
-    }
-  }
-</style>

+ 0 - 340
src/app/shop/admin/app/scoreShop/index.vue

@@ -1,340 +0,0 @@
-<template>
-  <el-container class="scoreshop-view panel-block">
-    <el-header class="sa-header">
-      <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">
-          积分商城
-          <search-condition
-            :conditionLabel="filterParams.conditionLabel"
-            @deleteFilter="deleteFilter"
-          ></search-condition>
-        </div>
-        <div>
-          <el-button 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.app.scoreshop.add'"
-            icon="Plus"
-            type="primary"
-            @click="addRow"
-            >新建</el-button
-          >
-          <el-button
-            v-auth="'shop.admin.app.scoreshop.recyclebin'"
-            type="danger"
-            icon="Delete"
-            plain
-            @click="openRecyclebin"
-            >回收站</el-button
-          >
-        </div>
-      </div>
-    </el-header>
-    <el-main class="sa-p-0">
-      <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
-        <el-table
-          height="100%"
-          class="sa-table"
-          :data="table.data"
-          :expand-row-keys="expandRowKeys"
-          row-key="id"
-          stripe
-        >
-          <template #empty>
-            <sa-empty />
-          </template>
-          <el-table-column type="expand">
-            <template #default="props">
-              <el-table class="sa-table sa-expand-table" :data="skuPrices.data" stripe>
-                <el-table-column width="48"></el-table-column>
-                <el-table-column min-width="80"></el-table-column>
-                <el-table-column min-width="440">
-                  <template #default="scope">
-                    <div class="sa-flex">
-                      <sa-preview :url="scope.row.image || props.row.image" size="32"></sa-preview>
-                      <div class="sku-text sa-m-l-12">
-                        {{ scope.row.goods_sku_text?.join('/') }}
-                      </div>
-                    </div>
-                  </template>
-                </el-table-column>
-                <el-table-column prop="score_price" min-width="200"></el-table-column>
-                <el-table-column prop="sales" min-width="100"></el-table-column>
-                <el-table-column prop="stock" min-width="100"></el-table-column>
-                <el-table-column min-width="120"></el-table-column>
-              </el-table>
-            </template>
-          </el-table-column>
-          <el-table-column prop="id" label="ID" min-width="80"></el-table-column>
-          <el-table-column label="商品信息" min-width="440">
-            <template #default="scope">
-              <goods-item
-                :goods="{
-                  image: scope.row.image,
-                  title: scope.row.title,
-                  subtitle: scope.row.subtitle,
-                }"
-                mode="scoreShop"
-              >
-                <template #sku>
-                  <el-button
-                    v-if="scope.row.is_sku"
-                    v-auth="'shop.admin.app.scoreshop.skuprices'"
-                    link
-                  >
-                    <div class="sku" @click.stop="expandRow(scope.row.id)">
-                      <span>多规格</span>
-                      <el-icon
-                        :class="[
-                          'expand-arrow sa-m-l-4',
-                          expandRowKeys.includes(scope.row.id)
-                            ? 'expand-arrow-up'
-                            : 'expand-arrow-down',
-                        ]"
-                      >
-                        <ArrowDown />
-                      </el-icon>
-                    </div>
-                  </el-button>
-                </template>
-              </goods-item>
-            </template>
-          </el-table-column>
-          <el-table-column label="积分现金" min-width="200">
-            <template #default="scope">
-              <div v-if="scope.row.score_price" class="sa-flex">
-                {{ scope.row.score_price.score }}积分
-                <div v-if="Number(scope.row.score_price.price)"
-                  >+¥{{ scope.row.score_price.price }}</div
-                >
-              </div>
-              <div v-else>-</div>
-            </template>
-          </el-table-column>
-          <el-table-column prop="score_sales" label="销量" min-width="100"></el-table-column>
-          <el-table-column prop="score_stock" label="库存" min-width="100"></el-table-column>
-          <el-table-column label="操作" min-width="120" fixed="right">
-            <template #default="scope">
-              <el-button
-                v-auth="'shop.admin.app.scoreshop.skus'"
-                class="is-link"
-                type="primary"
-                @click="editRow(scope.row)"
-                >编辑</el-button
-              >
-              <el-popconfirm
-                width="fit-content"
-                confirm-button-text="确认"
-                cancel-button-text="取消"
-                title="确认删除这条记录?"
-                @confirm="deleteRow(scope.row.id)"
-              >
-                <template #reference>
-                  <el-button
-                    v-auth="'shop.admin.app.scoreshop.delete'"
-                    class="is-link"
-                    type="danger"
-                    >删除</el-button
-                  >
-                </template>
-              </el-popconfirm>
-            </template>
-          </el-table-column>
-        </el-table>
-      </div>
-    </el-main>
-    <sa-view-bar>
-      <template #right>
-        <sa-pagination :pageData="pageData" @updateFn="getData" />
-      </template>
-    </sa-view-bar>
-  </el-container>
-</template>
-<script>
-  export default {
-    name: 'shop.admin.app.scoreshop',
-  };
-</script>
-<script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../app.service';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { useSearch } from '@/sheep/components/sa-table/sa-search/useSearch';
-  import { composeFilter } from '@/sheep/utils';
-  import GoodsItem from '@/app/shop/components/goods-item.vue';
-  import ScoreShopEdit from './edit.vue';
-  import ScoreShopRecyclebin from './recyclebin.vue';
-  import { cloneDeep } from 'lodash';
-
-  const filterParams = reactive({
-    tools: {
-      keyword: {
-        type: 'tinputprepend',
-        label: '请输入查询内容',
-        field: 'keyword',
-        keyword: {
-          field: 'title',
-          value: '',
-        },
-        options: [
-          {
-            label: '商品名称',
-            value: 'title',
-          },
-          {
-            label: '商品副标题',
-            value: 'subtitle',
-          },
-        ],
-      },
-    },
-    data: {
-      keyword: { field: 'title', value: '' },
-    },
-    conditionLabel: {},
-  });
-  const { openFilter, deleteFilter } = useSearch({ filterParams, getData });
-
-  const loading = ref(true);
-
-  // 表格
-  const table = reactive({
-    data: [],
-  });
-
-  const { pageData } = usePagination();
-
-  // 获取数据
-  async function getData(page) {
-    loading.value = true;
-    if (page) pageData.page = page;
-    let tempSearch = cloneDeep(filterParams.data);
-    let search = composeFilter(tempSearch, {
-      title: 'like',
-      subtitle: 'like',
-    });
-    const { error, data } = await api.scoreShop.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      ...search,
-    });
-    if (error === 0) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
-      if (expandRowKeys.length > 0) {
-        getSkuPrices(expandRowKeys[0]);
-      }
-    }
-    loading.value = false;
-  }
-
-  function addRow() {
-    useModal(
-      ScoreShopEdit,
-      {
-        title: '兑换规则',
-        type: 'add',
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-  function editRow(row) {
-    useModal(
-      ScoreShopEdit,
-      {
-        title: '兑换规则',
-        type: 'edit',
-        data: {
-          id: row.id,
-          title: row.title,
-        },
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-  async function deleteRow(id) {
-    await api.scoreShop.delete(id);
-    getData();
-  }
-  function openRecyclebin() {
-    useModal(
-      ScoreShopRecyclebin,
-      {
-        title: '回收站',
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  let expandRowKeys = reactive([]);
-  function expandRow(id) {
-    if (expandRowKeys.includes(id)) {
-      expandRowKeys.length = 0;
-    } else {
-      expandRowKeys.length = 0;
-      expandRowKeys.push(id);
-      getSkuPrices(id);
-    }
-  }
-
-  const skuPrices = reactive({
-    data: [],
-  });
-  async function getSkuPrices(id) {
-    const { data } = await api.scoreShop.getSkuPrices(id);
-    skuPrices.data = data;
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>
-<style lang="scss" scoped>
-  .scoreshop-view {
-    .el-main {
-      .sa-table-wrap {
-        margin-left: -48px;
-        overflow: hidden;
-        height: 100%;
-        .sku {
-          width: fit-content;
-          height: 20px;
-          line-height: 1;
-          display: flex;
-          align-items: center;
-          padding: 0 8px;
-          font-size: 12px;
-          color: #fff;
-          background: var(--el-color-primary);
-          border-radius: 10px;
-          cursor: pointer;
-        }
-      }
-      .sa-expand-table {
-        :deep() {
-          .el-table__header-wrapper {
-            display: none;
-          }
-        }
-        .sku-text {
-          font-size: 12px;
-          color: var(--sa-font);
-        }
-      }
-    }
-  }
-</style>

+ 0 - 184
src/app/shop/admin/app/scoreShop/recyclebin.vue

@@ -1,184 +0,0 @@
-<template>
-  <el-container class="recyclebin-view">
-    <el-main v-loading="loading">
-      <el-table
-        :data="table.data"
-        @selection-change="changeSelection"
-        @sort-change="fieldFilter"
-        class="sa-table"
-        stripe
-      >
-        <template #empty>
-          <sa-empty />
-        </template>
-        <el-table-column type="selection" width="48" align="center"></el-table-column>
-        <el-table-column sortable="custom" prop="id" label="ID" min-width="100"></el-table-column>
-        <el-table-column label="名称" min-width="100">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.title || '-' }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column
-          sortable="custom"
-          prop="delete_time"
-          label="删除时间"
-          min-width="172"
-        ></el-table-column>
-        <el-table-column fixed="right" label="操作" min-width="120">
-          <template #default="scope">
-            <el-button
-              v-auth="'shop.admin.app.scoreshop.restore'"
-              class="is-link"
-              type="primary"
-              @click="restoreRow(scope.row.id)"
-              >还原</el-button
-            >
-            <el-popconfirm
-              width="fit-content"
-              confirm-button-text="确认"
-              cancel-button-text="取消"
-              title="确认销毁这条记录?"
-              @confirm="destroyRow(scope.row.id)"
-            >
-              <template #reference>
-                <el-button v-auth="'shop.admin.app.scoreshop.destroy'" class="is-link" type="danger"
-                  >销毁</el-button
-                >
-              </template>
-            </el-popconfirm>
-          </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 setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../app.service';
-  import { ElMessageBox } from 'element-plus';
-  import { usePagination } from '@/sheep/hooks';
-
-  const loading = ref(true);
-
-  // 表格状态
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-
-  const { pageData } = usePagination();
-
-  // 获取数据
-  async function getData() {
-    loading.value = true;
-    const { data } = await api.scoreShop.recyclebin({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      order: table.order,
-      sort: table.sort,
-    });
-    table.data = data.data;
-    pageData.page = data.current_page;
-    pageData.list_rows = data.per_page;
-    pageData.total = data.total;
-    loading.value = false;
-  }
-
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
-
-  // table 批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
-
-  // 批量操作
-  const batchHandleTools = [
-    {
-      type: 'restore',
-      label: '还原',
-      auth: 'shop.admin.app.scoreshop.restore',
-      buttonType: 'primary',
-    },
-    {
-      type: 'destroy',
-      label: '销毁',
-      auth: 'shop.admin.app.scoreshop.destroy',
-      buttonType: 'danger',
-    },
-    {
-      type: 'all',
-      label: '清空回收站',
-      auth: 'shop.admin.app.scoreshop.destroy',
-      buttonType: 'danger',
-      operType: 'all',
-    },
-  ];
-  async function batchHandle(type) {
-    let ids = [];
-    table.selected.forEach((row) => {
-      ids.push(row.id);
-    });
-    switch (type) {
-      case 'all':
-        ElMessageBox.confirm('此操作将清空回收站, 是否继续?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning',
-        }).then(() => {
-          destroyRow('all');
-        });
-        break;
-      case 'destroy':
-        ElMessageBox.confirm('此操作将销毁, 是否继续?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning',
-        }).then(() => {
-          destroyRow(ids.join(','));
-        });
-        break;
-      case 'restore':
-        ElMessageBox.confirm('此操作将还原, 是否继续?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning',
-        }).then(() => {
-          restoreRow(ids.join(','));
-        });
-        break;
-    }
-  }
-  // 销毁
-  async function destroyRow(id) {
-    await api.scoreShop.destroy(id);
-    getData();
-  }
-  // 还原
-  async function restoreRow(id) {
-    await api.scoreShop.restore(id);
-    getData();
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>

+ 0 - 120
src/app/shop/admin/app/scoreShop/select.vue

@@ -1,120 +0,0 @@
-<template>
-  <el-container class="panel-block">
-    <el-main>
-      <el-table
-        height="100%"
-        class="sa-table"
-        :data="table.data"
-        stripe
-        @selection-change="handleSelectionChange"
-      >
-        <template #empty>
-          <sa-empty />
-        </template>
-        <el-table-column v-if="modal.params.multiple" type="selection" width="55" />
-        <el-table-column prop="id" label="ID" min-width="80"></el-table-column>
-        <el-table-column label="商品信息" min-width="440">
-          <template #default="scope">
-            <goods-item
-              :goods="{
-                image: scope.row.image,
-                title: scope.row.title,
-                subtitle: scope.row.subtitle,
-              }"
-              mode="scoreShop"
-            >
-              <template #sku>
-                <div class="sku" v-if="scope.row.is_sku">
-                  <span>多规格</span>
-                </div>
-              </template>
-            </goods-item>
-          </template>
-        </el-table-column>
-        <el-table-column label="积分现金" min-width="200">
-          <template #default="scope">
-            <div class="sa-flex">
-              {{ scope.row.score_price.score }}积分
-              <div v-if="Number(scope.row.score_price.price)"
-                >+¥{{ scope.row.score_price.price }}</div
-              >
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column prop="score_sales" label="销量" min-width="100"></el-table-column>
-        <el-table-column prop="score_stock" label="库存" min-width="100"></el-table-column>
-        <el-table-column v-if="!modal.params.multiple" label="操作" width="80">
-          <template #default="scope">
-            <el-button class="is-link" type="primary" @click="onSingleSelect(scope.row)"
-              >选择</el-button
-            >
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-main>
-    <el-footer class="sa-flex sa-row-between">
-      <sa-pagination :pageData="pageData" @updateFn="getData" />
-      <el-button v-if="modal.params.multiple" type="primary" @click="modalCallBack">确定</el-button>
-    </el-footer>
-  </el-container>
-</template>
-
-<script>
-  export default {
-    name: 'ScoreShopSelect',
-  };
-</script>
-
-<script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../app.service';
-  import { usePagination } from '@/sheep/hooks';
-  import GoodsItem from '@/app/shop/components/goods-item.vue';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps(['modal']);
-
-  const loading = ref(true);
-
-  // 表格
-  const table = reactive({
-    data: [],
-    selected: [],
-  });
-
-  const { pageData } = usePagination();
-
-  // 获取数据
-  async function getData(page) {
-    loading.value = true;
-    if (page) pageData.page = page;
-    const { data } = await api.scoreShop.select({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-    });
-    table.data = data.data;
-    pageData.page = data.current_page;
-    pageData.list_rows = data.per_page;
-    pageData.total = data.total;
-    loading.value = false;
-  }
-
-  function handleSelectionChange(val) {
-    table.selected = val;
-  }
-
-  function modalCallBack() {
-    emit('modalCallBack', { event: 'confirm', data: table.selected });
-  }
-
-  function onSingleSelect(row) {
-    emit('modalCallBack', {
-      event: 'confirm',
-      data: row,
-    });
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>

+ 0 - 17
src/app/shop/admin/category/category.service.js

@@ -1,17 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'category',
-  name: 'shop.admin.category',
-  component: () => import('@/app/shop/admin/category/index.vue'),
-  meta: {
-    title: '商品分类',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/category_tag'),
-  select: (params) => SELECT('shop/admin/category_tag', params),
-};
-
-export { route, api };

+ 0 - 129
src/app/shop/admin/category/select.vue

@@ -1,129 +0,0 @@
-<template>
-  <el-container class="category-select-view">
-    <el-header class="sa-header">
-      <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">选择分类</div>
-        <div>
-          <el-button @click="handleCancel">取消</el-button>
-          <el-button type="primary" @click="handleConfirm">确定</el-button>
-        </div>
-      </div>
-    </el-header>
-    <el-main class="sa-p-0">
-      <div class="category-list">
-        <el-tree
-          ref="treeRef"
-          :data="categoryList"
-          :props="treeProps"
-          node-key="id"
-          :default-checked-keys="selectedIds"
-          show-checkbox
-          :check-strictly="checkStrictly"
-          @check="handleCheck"
-        />
-      </div>
-    </el-main>
-  </el-container>
-</template>
-
-<script>
-export default {
-  name: 'CategorySelect',
-};
-</script>
-
-<script setup>
-import { ref, reactive, onMounted } from 'vue';
-import { api } from './category.service';
-
-const emit = defineEmits(['modalCallBack']);
-const props = defineProps({
-  modal: {
-    type: Object,
-    default: () => ({}),
-  },
-  multiple: {
-    type: Boolean,
-    default: true,
-  },
-  checkStrictly: {
-    type: Boolean,
-    default: false,
-  },
-});
-
-const treeRef = ref();
-const categoryList = ref([]);
-const selectedIds = ref([]);
-
-const treeProps = {
-  children: 'children',
-  label: 'name',
-  value: 'id',
-};
-
-// 获取分类数据
-async function getData() {
-  try {
-    const { data, error } = await api.category.list();
-    if (error === 0) {
-      categoryList.value = data || [];
-    }
-  } catch (error) {
-    console.error('获取分类数据失败:', error);
-  }
-}
-
-// 处理选择
-function handleCheck(data, checked) {
-  if (props.multiple) {
-    selectedIds.value = checked.checkedKeys;
-  } else {
-    selectedIds.value = [data.id];
-    // 单选模式下,取消其他选择
-    treeRef.value.setCheckedKeys([data.id]);
-  }
-}
-
-// 确认选择
-function handleConfirm() {
-  const checkedNodes = treeRef.value.getCheckedNodes();
-  const selectedCategories = checkedNodes.map(node => ({
-    id: node.id,
-    name: node.name,
-  }));
-
-  emit('modalCallBack', {
-    type: 'confirm',
-    data: props.multiple ? selectedCategories : selectedCategories[0] || null,
-  });
-}
-
-// 取消选择
-function handleCancel() {
-  emit('modalCallBack', {
-    type: 'cancel',
-  });
-}
-
-onMounted(() => {
-  getData();
-  
-  // 设置默认选中
-  if (props.modal.selectedIds) {
-    selectedIds.value = Array.isArray(props.modal.selectedIds) 
-      ? props.modal.selectedIds 
-      : [props.modal.selectedIds];
-  }
-});
-</script>
-
-<style lang="scss" scoped>
-.category-select-view {
-  .category-list {
-    padding: 20px;
-    height: 400px;
-    overflow-y: auto;
-  }
-}
-</style>

+ 2 - 3
src/app/shop/admin/config/componenets/payConfig/edit.vue

@@ -7,8 +7,7 @@
         </el-form-item>
 
         <el-form-item label="支付方式类型">
-          <el-radio-group v-model="form.model.type" :disabled="modal.params.type=='edit'">
-            <el-radio label="wechat"> 微信支付V3版 </el-radio>
+          <el-radio-group v-model="form.model.type" :disabled="modal.params.type == 'edit'">
             <el-radio label="alipay">支付宝支付</el-radio>
           </el-radio-group>
         </el-form-item>
@@ -216,7 +215,7 @@
         sub_mch_public_cert_path: '',
         sub_mch_secret_cert: '',
       },
-      type: 'wechat',
+      type: 'alipay',
     },
     rules: {
       name: [{ required: true, message: '请输入标题', trigger: 'blur' }],

+ 0 - 17
src/app/shop/admin/content/banner/banner.service.js

@@ -1,17 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'banner',
-  name: 'shop.admin.content.banner',
-  component: () => import('@/app/shop/admin/content/banner/index.vue'),
-  meta: {
-    title: '广告位',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/banner'),
-  select: (params) => SELECT('shop/admin/banner', params),
-};
-
-export { route, api };

+ 1 - 1
src/app/shop/admin/content/banner/edit.vue

@@ -65,7 +65,7 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from './banner.service';
+  import { api } from '../content.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {

+ 3 - 3
src/app/shop/admin/content/banner/index.vue

@@ -46,9 +46,9 @@
           </el-table-column>
           <el-table-column label="广告图片" min-width="120">
             <template #default="scope">
-              <el-image 
+              <el-image
                 v-if="scope.row.image"
-                :src="scope.row.image" 
+                :src="scope.row.image"
                 style="width: 60px; height: 40px"
                 fit="cover"
               />
@@ -121,7 +121,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './banner.service';
+  import { api } from '../content.service';
   import { ElMessageBox } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';

+ 59 - 0
src/app/shop/admin/content/content.service.js

@@ -0,0 +1,59 @@
+import Content from '@/sheep/layouts/content.vue';
+import { SELECT, CRUD } from '@/sheep/request/crud';
+
+const route = {
+  path: 'content',
+  name: 'shop.admin.content',
+  component: Content,
+  meta: {
+    title: '内容',
+  },
+  children: [
+    {
+      path: 'banner',
+      name: 'shop.admin.content.banner',
+      component: () => import('./banner/index.vue'),
+      meta: {
+        title: '广告位',
+      },
+    },
+    {
+      path: 'notification',
+      name: 'shop.admin.content.notification',
+      component: () => import('./notification/index.vue'),
+      meta: {
+        title: '消息推送',
+      },
+    },
+    {
+      path: 'sms',
+      name: 'shop.admin.content.sms',
+      component: () => import('./sms/index.vue'),
+      meta: {
+        title: '短信',
+      },
+    },
+  ],
+};
+
+const api = {
+  // 广告位相关 API
+  banner: {
+    ...CRUD('shop/admin/banner'),
+    select: (params) => SELECT('shop/admin/banner', params),
+  },
+
+  // 消息推送相关 API
+  notification: {
+    ...CRUD('shop/admin/notification'),
+    select: (params) => SELECT('shop/admin/notification', params),
+  },
+
+  // 短信相关 API
+  sms: {
+    ...CRUD('shop/admin/sms_template'),
+    select: (params) => SELECT('shop/admin/sms_template', params),
+  },
+};
+
+export { route, api };

+ 4 - 4
src/app/shop/admin/content/notification/edit.vue

@@ -20,9 +20,9 @@
           </el-select>
         </el-form-item>
         <el-form-item label="消息内容" prop="content">
-          <el-input 
-            v-model="form.model.content" 
-            type="textarea" 
+          <el-input
+            v-model="form.model.content"
+            type="textarea"
             :rows="5"
             placeholder="请填写消息内容"
           ></el-input>
@@ -54,7 +54,7 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from './notification.service';
+  import { api } from '../content.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {

+ 1 - 1
src/app/shop/admin/content/notification/index.vue

@@ -108,7 +108,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './notification.service';
+  import { api } from '../content.service';
   import { ElMessageBox } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';

+ 0 - 17
src/app/shop/admin/content/notification/notification.service.js

@@ -1,17 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'notification',
-  name: 'shop.admin.content.notification',
-  component: () => import('@/app/shop/admin/content/notification/index.vue'),
-  meta: {
-    title: '消息推送',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/notification'),
-  select: (params) => SELECT('shop/admin/notification', params),
-};
-
-export { route, api };

+ 7 - 7
src/app/shop/admin/content/sms/edit.vue

@@ -16,9 +16,9 @@
           </el-select>
         </el-form-item>
         <el-form-item label="模板内容" prop="content">
-          <el-input 
-            v-model="form.model.content" 
-            type="textarea" 
+          <el-input
+            v-model="form.model.content"
+            type="textarea"
             :rows="4"
             placeholder="请填写短信模板内容,使用{变量名}表示变量"
           ></el-input>
@@ -27,9 +27,9 @@
           </div>
         </el-form-item>
         <el-form-item label="变量说明" prop="variables">
-          <el-input 
-            v-model="form.model.variables" 
-            type="textarea" 
+          <el-input
+            v-model="form.model.variables"
+            type="textarea"
             :rows="3"
             placeholder="请说明模板中使用的变量,如:{code}=验证码,{name}=用户名"
           ></el-input>
@@ -54,7 +54,7 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from './sms.service';
+  import { api } from '../content.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {

+ 1 - 1
src/app/shop/admin/content/sms/index.vue

@@ -110,7 +110,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './sms.service';
+  import { api } from '../content.service';
   import { ElMessageBox } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';

+ 0 - 17
src/app/shop/admin/content/sms/sms.service.js

@@ -1,17 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'sms',
-  name: 'shop.admin.content.sms',
-  component: () => import('@/app/shop/admin/content/sms/index.vue'),
-  meta: {
-    title: '短信',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/sms_template'),
-  select: (params) => SELECT('shop/admin/sms_template', params),
-};
-
-export { route, api };

+ 0 - 33
src/app/shop/admin/coupon/coupon.service.js

@@ -1,33 +0,0 @@
-import { request } from '@/sheep/request';
-import { CRUD, RECYCLE } from '@/sheep/request/crud';
-
-const route = {
-  path: 'coupon',
-  name: 'shop.admin.coupon',
-  component: () => import('./index.vue'),
-  meta: {
-    title: '优惠券',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/coupon'),
-  ...RECYCLE('shop/admin/coupon'),
-  select: (params, type = 'page') =>
-    request({
-      url: `shop/admin/coupon/select?type=${type}`,
-      method: 'GET',
-      params,
-    }),
-  send: (id, data) =>
-    request({
-      url: `shop/admin/coupon/send/${id}`,
-      method: 'POST',
-      data,
-      options: {
-        showSuccessMessage: true,
-      },
-    }),
-};
-
-export { route, api };

+ 0 - 489
src/app/shop/admin/coupon/edit.vue

@@ -1,489 +0,0 @@
-<template>
-  <el-container class="coupon-edit">
-    <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="110px">
-        <el-form-item label="券名称" prop="name">
-          <el-input v-model="form.model.name" placeholder="例如:国庆优惠券" />
-        </el-form-item>
-        <el-form-item label="名称备注" prop="description">
-          <el-input v-model="form.model.description" placeholder="请输入备注" />
-        </el-form-item>
-        <el-form-item label="券类型" prop="type">
-          <el-radio-group v-model="form.model.type" :disabled="props.modal.params.type == 'edit'">
-            <el-radio label="reduce">满减券</el-radio>
-            <el-radio label="discount">折扣券</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item class="el-form-item--label-right">
-          <el-form-item class="el-form-item__label-auto" label="消费满" prop="enough">
-            <div class="sa-w-120">
-              <el-input
-                type="number"
-                v-model="form.model.enough"
-                :min="0"
-                :disabled="props.modal.params.type == 'edit'"
-              >
-                <template #append>元</template>
-              </el-input>
-            </div>
-          </el-form-item>
-          <el-form-item
-            v-if="form.model.type == 'reduce'"
-            class="el-form-item__label-auto is-no-asterisk sa-m-l-16"
-            label="立减"
-            prop="amount"
-          >
-            <div class="sa-w-113">
-              <el-input
-                type="number"
-                v-model="form.model.amount"
-                :min="0"
-                :disabled="props.modal.params.type == 'edit'"
-              >
-                <template #append>元</template>
-              </el-input>
-            </div>
-          </el-form-item>
-          <el-form-item
-            v-if="form.model.type == 'discount'"
-            class="el-form-item__label-auto is-no-asterisk sa-m-l-16"
-            label="打"
-            prop="amount"
-          >
-            <div class="sa-w-113">
-              <el-input
-                type="number"
-                v-model="form.model.amount"
-                :min="0"
-                :max="10"
-                :disabled="props.modal.params.type == 'edit'"
-              >
-                <template #append>折</template>
-              </el-input>
-            </div>
-          </el-form-item>
-        </el-form-item>
-        <el-form-item v-if="form.model.type == 'discount'" class="el-form-item--label-right">
-          <el-form-item class="el-form-item__label-auto" label="最多优惠" prop="max_amount">
-            <div class="enough-input">
-              <el-input
-                type="number"
-                v-model="form.model.max_amount"
-                class="sa-w-120"
-                :min="0"
-                :disabled="props.modal.params.type == 'edit'"
-              >
-                <template #append>元</template>
-              </el-input>
-            </div>
-          </el-form-item>
-        </el-form-item>
-        <el-form-item label="发券总量" prop="stock">
-          <el-input type="number" v-model="form.model.stock" class="sa-w-120" :min="0">
-            <template #append>张</template>
-          </el-input>
-        </el-form-item>
-        <el-form-item label="每人限领次数">
-          <el-input type="number" v-model="form.model.limit_num" class="sa-w-120" :min="0">
-            <template #append>张</template>
-          </el-input>
-        </el-form-item>
-        <el-form-item label="领券时间" prop="get_time" class="get-time">
-          <el-date-picker
-            v-model="form.model.get_time"
-            type="datetimerange"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            format="YYYY-MM-DD HH:mm:ss"
-            :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
-            range-separator="至"
-            start-placeholder="开始日期"
-            end-placeholder="结束日期"
-            prefix-icon="Calendar"
-            :editable="false"
-          ></el-date-picker>
-        </el-form-item>
-        <el-form-item label="券有效期" prop="use_time_type">
-          <el-radio-group v-model="form.model.use_time_type">
-            <el-radio label="days">相对天数</el-radio>
-            <el-radio label="range">固定区间</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item class="el-form-item--label-right">
-          <template v-if="form.model.use_time_type == 'days'">
-            <el-form-item
-              class="el-form-item__label-auto is-no-asterisk"
-              label="领券"
-              prop="start_days"
-            >
-              <div class="sa-w-120">
-                <el-input type="number" v-model="form.model.start_days" :min="0">
-                  <template #append>天</template>
-                </el-input>
-              </div>
-            </el-form-item>
-            <el-form-item
-              class="el-form-item__label-auto is-no-asterisk sa-m-l-16"
-              label="后生效,有效期"
-              prop="days"
-            >
-              <div class="sa-w-120">
-                <el-input type="number" v-model="form.model.days" :min="0">
-                  <template #append>天</template>
-                </el-input>
-              </div>
-            </el-form-item>
-          </template>
-          <el-form-item
-            v-if="form.model.use_time_type == 'range'"
-            class="el-form-item__label-auto is-no-asterisk"
-            label="固定时间"
-            prop="useTime"
-          >
-            <el-date-picker
-              v-model="form.model.use_time"
-              type="datetimerange"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              format="YYYY-MM-DD HH:mm:ss"
-              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
-              range-separator="至"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              prefix-icon="Calendar"
-              :editable="false"
-            ></el-date-picker>
-          </el-form-item>
-        </el-form-item>
-        <el-form-item label="优惠叠加">
-          <div>
-            <div class="sa-flex">
-              <el-switch
-                v-model="form.model.is_double_discount"
-                :active-value="1"
-                :inactive-value="0"
-                class="sa-m-r-8"
-              />
-              <div v-if="form.model.is_double_discount == '1'" class="openswitch"> 开启 </div>
-              <div v-if="form.model.is_double_discount == '0'">关闭</div>
-            </div>
-            <div class="warning-title"> 开启优惠叠加,优惠券将可以和活动一起使用 </div>
-          </div>
-        </el-form-item>
-        <el-form-item label="券状态" prop="status">
-          <div>
-            <el-radio-group v-model="form.model.status">
-              <el-radio label="normal">公开发放</el-radio>
-              <el-radio label="hidden">后台发放</el-radio>
-              <el-radio label="disabled">禁止使用</el-radio>
-            </el-radio-group>
-            <div class="warning-title">
-              后台发放状态改为别的状态,将导致满赠活动无法赠送该优惠券
-            </div>
-          </div>
-        </el-form-item>
-        <el-form-item label="可用范围" prop="use_scope">
-          <div>
-            <el-radio-group v-model="form.model.use_scope" @change="changeUseScope">
-              <el-radio label="all_use">全场通用</el-radio>
-              <el-radio label="goods">指定商品可用</el-radio>
-              <el-radio label="disabled_goods">指定商品不可用</el-radio>
-              <el-radio label="category">指定分类可用</el-radio>
-            </el-radio-group>
-            <div v-if="form.model.use_scope == 'goods' || form.model.use_scope == 'disabled_goods'">
-              <el-button class="is-link sa-m-b-8" type="primary" @click="selectGoods"
-                >选择商品</el-button
-              >
-              <div class="sa-template-wrap" v-if="form.model.items_value.length > 0">
-                <div class="title sa-flex">
-                  <div class="key">商品信息</div>
-                  <div class="oper">操作</div>
-                </div>
-                <div>
-                  <div
-                    class="item"
-                    v-for="(element, index) in form.model.items_value"
-                    :key="element"
-                  >
-                    <el-form-item class="key">
-                      <sa-image :url="element.image" size="40"></sa-image>
-                      <div class="goods sa-flex-1 sa-table-line-1 sa-m-l-12">
-                        <div class="goods-title sa-m-b-6">
-                          {{ element.title }}
-                        </div>
-                      </div>
-                    </el-form-item>
-                    <el-form-item class="oper">
-                      <el-button class="is-link" type="danger" @click="deleteItems(index)">
-                        移除
-                      </el-button>
-                    </el-form-item>
-                  </div>
-                </div>
-              </div>
-            </div>
-            <div v-if="form.model.use_scope == 'category'">
-              <el-button class="is-link sa-m-b-8" type="primary" @click="selectCategory"
-                >选择分类</el-button
-              >
-              <div class="sa-template-wrap" v-if="form.model.items_value.length > 0">
-                <div class="title sa-flex">
-                  <div class="key">分类信息</div>
-                  <div class="oper">操作</div>
-                </div>
-                <div>
-                  <div
-                    class="item"
-                    v-for="(element, index) in form.model.items_value"
-                    :key="element"
-                  >
-                    <el-form-item class="key">
-                      <div class="goods-title sa-m-b-6">{{ element.name }}</div>
-                    </el-form-item>
-                    <el-form-item class="oper">
-                      <el-button class="is-link" type="danger" @click="deleteItems(index)">
-                        移除
-                      </el-button>
-                    </el-form-item>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </el-form-item>
-      </el-form>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button
-        v-if="modal.params.type == 'add'"
-        v-auth="'shop.admin.coupon.add'"
-        type="primary"
-        @click="confirm"
-        >保存</el-button
-      >
-      <el-button
-        v-if="modal.params.type == 'edit'"
-        v-auth="'shop.admin.coupon.edit'"
-        type="primary"
-        @click="confirm"
-        >更新</el-button
-      >
-    </el-footer>
-  </el-container>
-</template>
-<script setup>
-  import { onMounted, reactive, ref, unref } from 'vue';
-  import { isEmpty, cloneDeep } from 'lodash';
-  import { api } from './coupon.service';
-  import { useModal } from '@/sheep/hooks';
-  import GoodsSelect from '@/app/shop/admin/goods/goods/select.vue';
-  import CategorySelect from '@/app/shop/admin/category/select.vue';
-  import { ElMessage } from 'element-plus';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps(['modal']);
-
-  const formRef = ref();
-  const form = reactive({
-    model: {
-      name: '',
-      type: 'reduce', // 优惠券类型:reduce=满减券,discount=折扣券
-      use_scope: 'all_use', // 可用范围:all=全场通用,goods=指定商品可用,disabled_goods=指定商品不可用,category=指定分类可用
-      items: '',
-      items_value: [],
-      amount: '',
-      max_amount: '',
-      enough: '',
-      stock: '',
-      limit_num: '',
-      get_time: '',
-      use_time_type: 'days',
-      use_time: '',
-      start_days: '',
-      days: '',
-      is_double_discount: '',
-      description: '',
-      status: 'normal', // 状态:normal=公开,hidden=后台发放,disabled=禁用
-    },
-    rules: {
-      name: [{ required: true, message: '请填写券名称', trigger: 'blur' }],
-      type: [{ required: true, message: '请选择券类型', trigger: 'blur' }],
-      enough: [{ required: true, message: '请填写消费门槛', trigger: 'blur' }],
-      amount: [{ required: true, message: '请填写使用面额', trigger: 'blur' }],
-      max_amount: [{ required: true, message: '请填写最大优惠', trigger: 'blur' }],
-      stock: [{ required: true, message: '请填写发券总量', trigger: 'blur' }],
-      get_time: [{ required: true, message: '请选择优惠券发放时间', trigger: 'blur' }],
-      use_time: [{ required: true, message: '请选择优惠券可使用时间', trigger: 'blur' }],
-      use_time_type: [{ required: true, message: '请选择优惠券使用时间类型', trigger: 'blur' }],
-      days: [{ required: true, message: '请填写优惠券有效天数', trigger: 'blur' }],
-      use_scope: [{ required: true, message: '请选择可用范围', trigger: 'blur' }],
-    },
-  });
-
-  async function getDetail() {
-    const { error, data } = await api.detail(props.modal.params.id);
-    if (error === 0) {
-      form.model = data;
-      form.model.get_time = [data.get_start_time, data.get_end_time];
-      if (data.use_time_type == 'days') {
-        form.model.use_time = '';
-      } else if (data.use_time_type == 'range') {
-        form.model.use_time = [data.use_start_time, data.use_end_time];
-      }
-    }
-  }
-
-  function selectGoods() {
-    let ids = [];
-    form.model.items_value.forEach((i) => {
-      ids.push(i.id);
-    });
-    useModal(
-      GoodsSelect,
-      {
-        title: '选择商品',
-        multiple: true,
-        ids,
-      },
-      {
-        confirm: (res) => {
-          form.model.items_value = res.data;
-        },
-      },
-    );
-  }
-
-  function selectCategory() {
-    let ids = [];
-    form.model.items_value.forEach((i) => {
-      ids.push(i.id);
-    });
-    useModal(
-      CategorySelect,
-      {
-        title: '选择商品',
-        multiple: true,
-        from: 'coupon',
-        ids,
-      },
-      {
-        confirm: (res) => {
-          form.model.items_value = [];
-          form.model.items_value.push(...res.data.list);
-        },
-      },
-    );
-  }
-
-  function deleteItems(index) {
-    form.model.items_value.splice(index, 1);
-  }
-
-  function changeUseScope() {
-    form.model.items_value = [];
-  }
-
-  function confirm() {
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      let submitForm = cloneDeep(form.model);
-      if (Number(submitForm.enough) < Number(submitForm.amount)) {
-        ElMessage({
-          message: '请输入正确的使用门槛',
-          type: 'warning',
-        });
-        return;
-      }
-      if (
-        submitForm.use_scope == 'goods' ||
-        submitForm.use_scope == 'disabled_goods' ||
-        submitForm.use_scope == 'category'
-      ) {
-        let ids = [];
-        submitForm.items_value.forEach((i) => {
-          ids.push(i.id);
-        });
-        submitForm.items = ids.join(',');
-      } else if (submitForm.use_scope == 'all_use') {
-        submitForm.items = '';
-      }
-      delete submitForm.items_value;
-
-      if (!isEmpty(submitForm.use_time)) {
-        submitForm.use_time = submitForm.use_time.join(' - ');
-      }
-
-      if (!isEmpty(submitForm.get_time)) {
-        submitForm.get_time = submitForm.get_time.join(' - ');
-      }
-
-      const res =
-        props.modal.params.type == 'add'
-          ? await api.add(submitForm)
-          : await api.edit(props.modal.params.id, submitForm);
-      if (res.error == 0) {
-        emit('modalCallBack', {
-          event: 'confirm',
-          data: {
-            uuID: props.modal.params.uuID,
-          },
-        });
-      }
-    });
-  }
-
-  onMounted(() => {
-    if (props.modal.params.type == 'edit') {
-      getDetail();
-    }
-  });
-</script>
-<style lang="scss" scoped>
-  .el-range-editor.el-input__inner {
-    // width: 360px;
-  }
-  .sa-template-wrap {
-    // width: 360px;
-    .key {
-      flex: none;
-      width: 400px;
-    }
-    .oper {
-      flex: none;
-      width: 60px;
-    }
-    .item {
-      border-bottom: 1px solid var(--sa-space);
-      & > .el-form-item {
-        margin-bottom: 12px;
-      }
-      .goods {
-        height: 16px;
-        line-height: 16px;
-        font-size: 12px;
-        .goods-title {
-          color: var(--sa-font);
-        }
-        .goods-price {
-          color: var(--el-color-danger);
-        }
-        .goods-stock {
-          color: var(--sa-font);
-        }
-      }
-    }
-    &.sa-template-wrap-activity {
-      width: 464px;
-    }
-  }
-  //Switch开启颜色
-  .openswitch {
-    color: var(--el-color-primary);
-  }
-  .get-time {
-    max-width: 500px;
-  }
-  .warning-title {
-    color: #faad14;
-    font-size: 12px;
-    line-height: 16px;
-  }
-</style>

+ 0 - 506
src/app/shop/admin/coupon/index.vue

@@ -1,506 +0,0 @@
-<template>
-  <el-container class="coupon-view panel-block">
-    <el-header class="sa-header">
-        <el-row class="sa-m-t-20" :gutter="20">
-          <el-col v-for="(value, key) in dashboard" :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
-              <div class="card">
-                  <div class="sa-flex sa-row-between sa-m-b-4">
-                      <div class="num">{{ value.num }}</div>
-                  </div>
-                  <div class="sa-flex sa-row-between">
-                      <div class="name">{{ value.name }}</div>
-                      <el-popover popper-class="sa-popper" trigger="hover">
-                          {{ value.tip }}
-                          <template #reference>
-                              <el-icon class="tip">
-                                  <warning />
-                              </el-icon>
-                          </template>
-                      </el-popover>
-                  </div>
-              </div>
-          </el-col>
-      </el-row>
-      <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">
-          <span class="left">{{ title }}</span>
-          <search-condition
-            :conditionLabel="filterParams.conditionLabel"
-            @deleteFilter="deleteFilter"
-          ></search-condition>
-        </div>
-        <div>
-          <el-button 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.coupon.add'" type="primary" icon="Plus" @click="addRow"
-            >添加</el-button
-          >
-          <el-button
-            v-auth="'shop.admin.coupon.recyclebin'"
-            type="danger"
-            icon="Delete"
-            plain
-            @click="openRecyclebin"
-            >回收站</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" stripe>
-        <template #empty>
-          <sa-empty />
-        </template>
-        <el-table-column prop="id" label="ID" min-width="88" sortable></el-table-column>
-        <el-table-column label="优惠券名称" min-width="172">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.name }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="类型" min-width="74">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.type_text }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="可用范围" min-width="116">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.use_scope_text }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="优惠内容" min-width="214">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.amount_text }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="领取状态" min-width="80" align="center">
-          <template #default="scope">
-            <el-popover
-              placement="bottom"
-              title="优惠券有效期"
-              trigger="hover"
-              popper-class="sa-popper"
-            >
-              <template #reference>
-                <div
-                  class="sa-table-line-1 get-time-text"
-                  :class="
-                    scope.row.get_time_text == '发放中'
-                      ? 'success'
-                      : scope.row.get_time_text == '已结束'
-                      ? 'sa-delete'
-                      : 'info'
-                  "
-                >
-                  {{ scope.row.get_time_text }}
-                </div>
-              </template>
-              <div v-if="scope.row.use_time_type == 'days'">
-                领取{{ scope.row.start_days }}天后生效,有效期{{ scope.row.days }}天
-              </div>
-              <div v-if="scope.row.use_time_type == 'range'">
-                <div>开始时间:{{ scope.row.use_start_time }}</div>
-                <div>结束时间:{{ scope.row.use_end_time }}</div>
-              </div>
-            </el-popover>
-          </template>
-        </el-table-column>
-        <el-table-column label="已领取" min-width="96">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.get_num }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="已使用" min-width="96">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.use_num }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="剩余" min-width="96">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.stock }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column label="状态" min-width="130">
-          <template #default="scope">
-            <el-dropdown trigger="click" @command="handleCommand">
-              <el-button v-auth="'shop.admin.coupon.edit'" class="status-btn">
-                {{ scope.row.status_text }}
-                <el-icon class="el-icon--right">
-                  <ArrowDown />
-                </el-icon>
-              </el-button>
-              <template #dropdown>
-                <el-dropdown-menu>
-                  <el-dropdown-item
-                    :command="{
-                      id: scope.row.id,
-                      type: 'normal',
-                    }"
-                  >
-                    <!-- TODO-jj:弹框提醒 -->
-                    公开发放
-                    <!-- <template #default>
-                      <el-popconfirm width="fit-content"
-                        confirm-button-text="确认"
-                        cancel-button-text="取消"
-                        title="确认删除这条记录?"
-                        @confirm="handConfirm(scope.row.id)"
-                      >
-                        <template #reference>
-                          <el-button class="sa-button" text> 公开发放 </el-button>
-                        </template>
-                      </el-popconfirm>
-                    </template> -->
-                  </el-dropdown-item>
-                  <el-dropdown-item
-                    :command="{
-                      id: scope.row.id,
-                      type: 'hidden',
-                    }"
-                    >后台发放</el-dropdown-item
-                  >
-                  <el-dropdown-item
-                    :command="{
-                      id: scope.row.id,
-                      type: 'disabled',
-                    }"
-                    >禁止使用</el-dropdown-item
-                  >
-                </el-dropdown-menu>
-              </template>
-            </el-dropdown>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" min-width="260" fixed="right">
-          <template #default="scope">
-            <el-button
-              v-auth="'shop.admin.coupon.send'"
-              type="primary"
-              link
-              @click="onSend(scope.row.id)"
-              >手动发放</el-button
-            >
-            <el-button
-              v-auth="'shop.admin.user.coupon.couponlist'"
-              type="primary"
-              link
-              @click="onCoupon(scope.row.id)"
-              >领取记录</el-button
-            >
-            <el-button
-              v-auth="'shop.admin.coupon.detail'"
-              class="is-link"
-              type="primary"
-              @click="editRow(scope.row.id)"
-              >编辑</el-button
-            >
-            <el-popconfirm
-              width="fit-content"
-              confirm-button-text="确认"
-              cancel-button-text="取消"
-              title="确认删除这条记录?"
-              @confirm="deleteRow(scope.row.id)"
-            >
-              <template #reference>
-                <el-button v-auth="'shop.admin.coupon.delete'" class="is-link" type="danger">
-                  删除
-                </el-button>
-              </template>
-            </el-popconfirm>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-main>
-    <sa-view-bar>
-      <template #right>
-        <sa-pagination :pageData="pageData" @updateFn="getData" />
-      </template>
-    </sa-view-bar>
-  </el-container>
-</template>
-<script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from './coupon.service';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { useSearch } from '@/sheep/components/sa-table/sa-search/useSearch';
-  import { composeFilter } from '@/sheep/utils';
-  import CouponEdit from './edit.vue';
-  import ApiRecyclebin from './recyclebin.vue';
-  import { cloneDeep } from 'lodash';
-  import { useRouter, useRoute } from 'vue-router';
-  import UserSelect from '@/app/user/admin/select.vue';
-
-  const router = useRouter();
-
-  const filterParams = reactive({
-    tools: {
-      keyword: {
-        type: 'tinput',
-        field: 'keyword',
-        value: '',
-        label: '搜索内容',
-        placeholder: '请输入查询内容',
-      },
-      type: {
-        type: 'tselect',
-        label: '类型',
-        field: 'type',
-        value: '',
-        options: {
-          data: [
-            {
-              label: '满减券',
-              value: 'reduce',
-            },
-            {
-              label: '折扣券',
-              value: 'discount',
-            },
-          ],
-        },
-      },
-      use_scope: {
-        type: 'tselect',
-        label: '可用范围',
-        field: 'use_scope',
-        value: '',
-        options: {
-          data: [
-            {
-              label: '全场通用',
-              value: 'all_use',
-            },
-            {
-              label: '指定商品可用',
-              value: 'goods',
-            },
-            {
-              label: '指定商品不可用',
-              value: 'disabled_goods',
-            },
-            {
-              label: '指定分类可用',
-              value: 'category',
-            },
-          ],
-        },
-      },
-    },
-    data: {
-      keyword: '',
-      type: '',
-      use_scope: '',
-    },
-    conditionLabel: {},
-  });
-  const { openFilter, deleteFilter } = useSearch({ filterParams, getData });
-
-  const dashboard = reactive({
-    total_num: {
-      name: '总发券量/张',
-      num: '',
-      tip: '用户领取的优惠券的总张数,包含已经被后台删除的优惠券'
-    },
-    expire_num: {
-      name: '已过期/张',
-      num: '',
-      tip: '用户已领取的并且已经超过可使用日期的未使用优惠券'
-    },
-    use_num: {
-      name: '已使用/张',
-      num: '',
-      tip: '用户已领取并且已使用的优惠券',
-    },
-    use_percent: {
-      name: '使用率',
-      num: '',
-      tip: '用户已使用优惠和总发券量的比例'
-    },
-  })
-
-  // 表格
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  // 分页
-  const { pageData } = usePagination();
-  const loading = ref(true);
-  const title = ref('优惠券');
-  const statusId = ref(0);
-  function handConfirm(e) {
-    statusId.value = e;
-  }
-
-  //获取状态
-  async function handleCommand(e) {
-    await api.edit(e.id, {
-      status: e.type,
-    });
-    getData();
-  }
-  // 获取数据
-  async function getData(page) {
-    loading.value = true;
-    if (page) pageData.page = page;
-    let tempSearch = cloneDeep(filterParams.data);
-    let search = composeFilter(tempSearch);
-    const { data } = await api.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      ...search,
-    });
-    table.data = data.coupons.data;
-    pageData.page = data.coupons.current_page;
-    pageData.list_rows = data.coupons.per_page;
-    pageData.total = data.coupons.total;
-
-    for (var key in dashboard) {
-      dashboard[key].num = data[key]
-    }
-
-    loading.value = false;
-  }
-
-  function addRow() {
-    useModal(
-      CouponEdit,
-      {
-        title: `添加${title.value}`,
-        type: 'add',
-        atype: 'full_coupon',
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-  function editRow(id) {
-    useModal(
-      CouponEdit,
-      {
-        title: `编辑${title.value}`,
-        type: 'edit',
-        atype: 'full_coupon',
-        id: id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-  async function deleteRow(id) {
-    await api.delete(id);
-    getData();
-  }
-  function openRecyclebin() {
-    useModal(
-      ApiRecyclebin,
-      {
-        title: '回收站',
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-        close: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  function onCoupon(id) {
-    router.push({
-        path: '/shop/admin/user/couponList',
-        query: {
-          couponid: id,
-        },
-      });
-  }
-
-  function onSend(id) {
-    useModal(
-      UserSelect,
-      {
-        title: `选择用户`,
-        id: id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>
-<style lang="scss" scoped>
-  .coupon-view {
-
-    .card {
-        height: 80px;
-        background: var(--sa-background-assist);
-        border: 1px solid var(--sa-space);
-        box-shadow: 0px 2px 6px rgba(140, 140, 140, 0.12);
-        border-radius: 8px;
-        padding: 16px;
-        margin-bottom: 16px;
-    }
-
-    .card .num {
-        font-weight: 400;
-        font-size: 24px;
-        line-height: 26px;
-        color: var(--sa-subtitle);
-    }
-
-    .card .oper {
-        font-weight: 400;
-        font-size: 12px;
-        line-height: 14px;
-        color: #FAAD14;
-        cursor: pointer;
-    }
-
-    .card .name {
-        font-weight: 400;
-        font-size: 12px;
-        line-height: 14px;
-        color: var(--sa-font);
-    }
-
-    .card .tip {
-        font-size: 14px;
-        color: var(--sa-subfont);
-    }
-
-    .sa-title {
-        padding-top: 0;
-    }
-  }
-  .get-time-text {
-    text-align: center;
-  }
-
-  .status-btn {
-    background-color: var(--sa-space);
-    color: var(--sa-subfont);
-    border: none;
-  }
-  .success {
-    color: #52c41a;
-  }
-
-  .info {
-    color: #999999;
-  }
-</style>

+ 0 - 184
src/app/shop/admin/coupon/recyclebin.vue

@@ -1,184 +0,0 @@
-<template>
-  <el-container class="recyclebin-view">
-    <el-main v-loading="loading">
-      <el-table
-        :data="table.data"
-        @selection-change="changeSelection"
-        @sort-change="fieldFilter"
-        class="sa-table"
-        stripe
-      >
-        <template #empty>
-          <sa-empty />
-        </template>
-        <el-table-column type="selection" width="48" align="center"></el-table-column>
-        <el-table-column sortable="custom" prop="id" label="ID" min-width="100"></el-table-column>
-        <el-table-column label="名称" min-width="100">
-          <template #default="scope">
-            <div class="sa-table-line-1">{{ scope.row.name || '-' }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column
-          sortable="custom"
-          prop="delete_time"
-          label="删除时间"
-          min-width="160"
-        ></el-table-column>
-        <el-table-column fixed="right" label="操作" min-width="120">
-          <template #default="scope">
-            <el-button
-              class="is-link"
-              type="primary"
-              @click="restoreRow(scope.row.id)"
-              v-auth="'shop.admin.coupon.restore'"
-              >还原</el-button
-            >
-            <el-popconfirm
-              width="fit-content"
-              confirm-button-text="确认"
-              cancel-button-text="取消"
-              title="确认销毁这条记录?"
-              @confirm="destroyRow(scope.row.id)"
-            >
-              <template #reference>
-                <el-button v-auth="'shop.admin.coupon.destroy'" class="is-link" type="danger">
-                  销毁
-                </el-button>
-              </template>
-            </el-popconfirm>
-          </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 setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from './coupon.service';
-  import { ElMessageBox } from 'element-plus';
-  import { usePagination } from '@/sheep/hooks';
-
-  const { pageData } = usePagination();
-
-  const loading = ref(true);
-
-  // 表格状态
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-
-  // 获取数据
-  async function getData() {
-    loading.value = true;
-    const { data } = await api.recyclebin({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      order: table.order,
-      sort: table.sort,
-    });
-    table.data = data.data;
-    pageData.page = data.current_page;
-    pageData.list_rows = data.per_page;
-    pageData.total = data.total;
-    loading.value = false;
-  }
-
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
-
-  // table 批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
-  // 批量操作
-  const batchHandleTools = [
-    {
-      type: 'restore',
-      label: '还原',
-      auth: 'shop.admin.coupon.restore',
-      buttonType: 'primary',
-    },
-    {
-      type: 'destroy',
-      label: '销毁',
-      auth: 'shop.admin.coupon.destroy',
-      buttonType: 'danger',
-    },
-    {
-      type: 'destroy',
-      label: '清空回收站',
-      auth: 'shop.admin.coupon.destroy',
-      buttonType: 'danger',
-      operType: 'all',
-    },
-  ];
-  async function batchHandle(type, id) {
-    let ids = [];
-    table.selected.forEach((row) => {
-      ids.push(row.id);
-    });
-    switch (type) {
-      case 'destroy':
-        if (id == 'all') {
-          ElMessageBox.confirm('此操作将清空回收站, 是否继续?', '提示', {
-            confirmButtonText: '确定',
-            cancelButtonText: '取消',
-            type: 'warning',
-          }).then(() => {
-            destroyRow('all');
-          });
-        } else {
-          ElMessageBox.confirm('此操作将销毁, 是否继续?', '提示', {
-            confirmButtonText: '确定',
-            cancelButtonText: '取消',
-            type: 'warning',
-          }).then(() => {
-            destroyRow(ids.join(','));
-          });
-        }
-        break;
-      case 'restore':
-        ElMessageBox.confirm('此操作将还原, 是否继续?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning',
-        }).then(() => {
-          restoreRow(ids.join(','));
-        });
-        break;
-    }
-  }
-  // 销毁
-  async function destroyRow(id) {
-    await api.destroy(id);
-    getData();
-  }
-  // 还原
-  async function restoreRow(id) {
-    await api.restore(id);
-    getData();
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>

+ 0 - 197
src/app/shop/admin/coupon/select.vue

@@ -1,197 +0,0 @@
-<template>
-  <el-container class="panel-block coupon-select">
-    <el-main class="coupon-main">
-      <el-table
-        class="sa-table"
-        :data="table.data"
-        stripe
-        @selection-change="handleSelectionChange"
-      >
-        <el-table-column v-if="modal?.params?.multiple" type="selection" />
-        <el-table-column label="优惠券名称" min-width="128">
-          <template #default="scope">
-            <div class="sa-table-line-1">
-              {{ scope.row.name }}
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="类型" min-width="74">
-          <template #default="scope">
-            <div class="sa-table-line-1">
-              {{ scope.row.type_text }}
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="可用范围" min-width="88">
-          <template #default="scope">
-            <div class="sa-table-line-1">
-              {{ scope.row.use_scope_text }}
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="优惠内容" min-width="154">
-          <template #default="scope">
-            <div class="sa-table-line-1">
-              {{ scope.row.amount_text }}
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="领取状态" min-width="80">
-          <template #default="scope">
-            <el-popover
-              placement="bottom"
-              title="优惠券有效期"
-              trigger="hover"
-              popper-class="sa-popper"
-            >
-              <template #reference>
-                <div
-                  class="sa-table-line-1 get-time-text"
-                  :class="
-                    scope.row.get_time_text == '发放中'
-                      ? 'success'
-                      : scope.row.get_time_text == '已结束'
-                      ? 'sa-delete'
-                      : 'info'
-                  "
-                >
-                  {{ scope.row.get_time_text }}
-                </div>
-              </template>
-              <div v-if="scope.row.use_time_type == 'days'">
-                领取{{ scope.row.start_days }}天后生效,有效期{{ scope.row.days }}天
-              </div>
-              <div v-if="scope.row.use_time_type == 'range'">
-                <div>开始时间:{{ scope.row.use_start_time }}</div>
-                <div>结束时间:{{ scope.row.use_end_time }}</div>
-              </div>
-            </el-popover>
-          </template>
-        </el-table-column>
-        <el-table-column label="剩余" min-width="88">
-          <template #default="scope">
-            <div class="sa-table-line-1">
-              {{ scope.row.stock }}
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column v-if="!modal?.params?.multiple" label="操作" min-width="88">
-          <template #default="scope">
-            <el-button class="is-link" type="primary" @click="modalCallBack(scope.row)"
-              >选择</el-button
-            >
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-main>
-    <el-footer class="sa-footer--submit sa-flex sa-row-between sa-flex-wrap">
-      <el-pagination
-        v-if="pageData"
-        @size-change="changeSize"
-        @current-change="changeCurrent"
-        v-model:currentPage="pageData.page"
-        :page-sizes="[10, 50, 100]"
-        :page-size="pageData.list_rows"
-        layout="total, sizes, prev, pager, next, jumper"
-        :total="pageData.total"
-        :pager-count="5"
-      ></el-pagination>
-      <el-button v-if="modal?.params?.multiple" type="primary" @click="confirm">确定</el-button>
-    </el-footer>
-  </el-container>
-</template>
-
-<script>
-  export default {
-    name: 'CouponSelect',
-  };
-</script>
-
-<script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from './coupon.service';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-      default: {
-        params: {
-          multiple: false,
-        },
-      },
-    },
-  });
-
-  // 表格
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  const loading = ref(true);
-  // 获取数据
-  async function getData() {
-    loading.value = false;
-    let search = {};
-    if (props.modal?.params?.status) {
-      search = JSON.stringify({ status: [props.modal.params.status] });
-    }
-    const { data } = await api.select({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      order: table.order,
-      sort: table.sort,
-      search: search,
-    });
-    table.data = data.data;
-    pageData.page = data.current_page;
-    pageData.list_rows = data.per_page;
-    pageData.total = data.total;
-    loading.value = false;
-  }
-
-  // 分页
-  const pageData = reactive({
-    page: 1,
-    list_rows: 10,
-    total: 0,
-  });
-
-  // 选择分页数量
-  function changeSize(list_rows) {
-    pageData.list_rows = list_rows;
-    getData();
-  }
-  // 选择分页
-  function changeCurrent(page) {
-    pageData.page = page;
-    getData();
-  }
-
-  function handleSelectionChange(val) {
-    table.selected = val;
-  }
-
-  function confirm() {
-    emit('modalCallBack', { event: 'confirm', data: table.selected });
-  }
-
-  function modalCallBack(data) {
-    emit('modalCallBack', { event: 'confirm', data });
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>
-<style lang="scss" scoped>
-  .success {
-    color: #52c41a;
-  }
-
-  .info {
-    color: #999999;
-  }
-</style>

+ 0 - 13
src/app/shop/admin/data/data.service.js

@@ -30,19 +30,6 @@ const api = {
         method: 'GET',
       }),
   },
-  express: {
-    list: () =>
-      request({
-        url: '/shop/admin/data/express/list',
-        method: 'GET',
-      }),
-    track: (code, no) =>
-      request({
-        url: '/shop/admin/data/express/track',
-        method: 'GET',
-        params: { code, no },
-      }),
-  },
 };
 
 export { route, api };

+ 17 - 23
src/app/shop/admin/data/report/index.vue

@@ -57,7 +57,7 @@
           </el-col>
         </el-row>
       </div>
-      
+
       <!-- 数据表格 -->
       <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
         <el-table
@@ -76,9 +76,7 @@
           <el-table-column prop="date" label="日期" min-width="120" sortable="custom">
           </el-table-column>
           <el-table-column label="销售额" min-width="120">
-            <template #default="scope">
-              ৳{{ scope.row.sales_amount || 0 }}
-            </template>
+            <template #default="scope"> ৳{{ scope.row.sales_amount || 0 }} </template>
           </el-table-column>
           <el-table-column label="订单数" min-width="100">
             <template #default="scope">
@@ -96,14 +94,10 @@
             </template>
           </el-table-column>
           <el-table-column label="转化率" min-width="100">
-            <template #default="scope">
-              {{ scope.row.conversion_rate || 0 }}%
-            </template>
+            <template #default="scope"> {{ scope.row.conversion_rate || 0 }}% </template>
           </el-table-column>
           <el-table-column label="客单价" min-width="120">
-            <template #default="scope">
-              ৳{{ scope.row.avg_order_value || 0 }}
-            </template>
+            <template #default="scope"> ৳{{ scope.row.avg_order_value || 0 }} </template>
           </el-table-column>
         </el-table>
       </div>
@@ -124,7 +118,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './report.service';
+  import { api } from '../data.service';
   import { ElMessage } from 'element-plus';
   import { usePagination } from '@/sheep/hooks';
   const { pageData } = usePagination();
@@ -142,7 +136,7 @@
   const defaultSearchValues = reactive({
     date_range: [],
   });
-  
+
   // 统计数据
   const statsData = reactive({
     totalSales: 0,
@@ -150,7 +144,7 @@
     totalUsers: 0,
     totalGoods: 0,
   });
-  
+
   // 列表
   const table = reactive({
     data: [],
@@ -159,20 +153,20 @@
     selected: [],
   });
   const loading = ref(true);
-  
+
   // 获取统计数据
   async function getStatsData() {
-    const { error, data } = await api.stats();
+    const { error, data } = await api.report.getStats();
     if (error === 0) {
       Object.assign(statsData, data);
     }
   }
-  
+
   // 获取
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.list({
+    const { error, data } = await api.report.list({
       page: pageData.page,
       list_rows: pageData.list_rows,
       order: table.order,
@@ -188,24 +182,24 @@
     }
     loading.value = false;
   }
-  
+
   // table 字段排序
   function fieldFilter({ prop, order }) {
     table.order = order == 'ascending' ? 'asc' : 'desc';
     table.sort = prop;
     getData();
   }
-  
+
   //table批量选择
   function changeSelection(row) {
     table.selected = row;
   }
-  
+
   // 导出数据
   async function exportData() {
     ElMessage.success('导出功能开发中...');
   }
-  
+
   // 分页/批量操作
   const batchHandleTools = [
     {
@@ -215,7 +209,7 @@
       class: 'primary',
     },
   ];
-  
+
   async function batchHandle(type) {
     let ids = [];
     table.selected.forEach((row) => {
@@ -247,7 +241,7 @@
             .stats-value {
               font-size: 24px;
               font-weight: bold;
-              color: #409EFF;
+              color: #409eff;
               margin-bottom: 8px;
             }
             .stats-label {

+ 0 - 27
src/app/shop/admin/data/report/report.service.js

@@ -1,27 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'report',
-  name: 'shop.admin.data.report',
-  component: () => import('@/app/shop/admin/data/report/index.vue'),
-  meta: {
-    title: '数据报表',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/data_report'),
-  select: (params) => SELECT('shop/admin/data_report', params),
-  stats: () => ({
-    error: 0,
-    msg: '获取成功',
-    data: {
-      totalSales: 125680.50,
-      totalOrders: 1256,
-      totalUsers: 3456,
-      totalGoods: 234,
-    }
-  }),
-};
-
-export { route, api };

+ 0 - 168
src/app/shop/admin/decorate/designer/preview.vue

@@ -1,168 +0,0 @@
-<template>
-  <el-container class="page-preview">
-    <el-main>
-      <el-row :gutter="20">
-        <el-col class="sa-col-12" :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-          <div
-            v-if="
-              props.modal.params.data.type == 'diypage' ||
-              state.templateData.platform.includes('H5') ||
-              state.templateData.platform.includes('WechatOfficialAccount')
-            "
-            class="left sa-flex-col sa-col-center"
-          >
-            <div class="preview-title">此为预览效果,实际效果请扫码查看</div>
-            <div class="web-preview">
-              <div v-if="isShowIframe" class="web-preview-msg">
-                <span v-html="isShowIframe"></span>
-              </div>
-              <iframe v-else id="preview" :src="url.H5" frameborder="1" height="600px"></iframe>
-            </div>
-          </div>
-        </el-col>
-        <el-col class="sa-col-12" :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-          <div class="name">{{ state.templateData.name }}</div>
-          <template
-            v-if="
-              props.modal.params.data.type == 'diypage' || state.templateData.platform.length > 0
-            "
-          >
-            <div class="platform sa-m-b-20">
-              <sa-icon
-                class="sa-m-r-8"
-                v-for="pl in state.templateData.platform"
-                :key="pl"
-                :icon="'sa-shop-decorate-' + pl"
-                :style="{
-                  color: platformList.filter((pf) => {
-                    return pf.type == pl;
-                  })[0].color,
-                }"
-                size="20"
-              />
-            </div>
-            <div
-              v-if="
-                props.modal.params.data.type == 'diypage' ||
-                state.templateData.platform.includes('H5') ||
-                state.templateData.platform.includes('WechatOfficialAccount')
-              "
-              class="h5"
-            >
-              <qrcode-vue :value="url.H5" :size="132" level="H" />
-              <div class="tip sa-m-t-12">微信扫描二维码即可预览</div>
-            </div>
-          </template>
-          <div class="copyright">星品科技Shopro版权所有 Copyright 2020-2022</div>
-        </el-col>
-      </el-row>
-    </el-main>
-  </el-container>
-</template>
-<script setup>
-  import { computed, onMounted, reactive } from 'vue';
-  import { api as configApi } from '@/app/shop/admin/config/config.service';
-  import { api } from '../decorate.service';
-  import { platformList } from '../page/data';
-  import QrcodeVue from 'qrcode.vue';
-
-  const props = defineProps(['modal']);
-
-  const state = reactive({
-    templateData: props.modal.params.data,
-    domain: '',
-  });
-
-  async function getConfig() {
-    const { error, data } = await configApi.basic();
-    error === 0 && (state.domain = data.domain);
-  }
-
-  const url = computed(() => {
-    return {
-      H5: `${props.modal.params.data.h5Url}`,
-      WechatMiniProgram: `${props.modal.params.data.wxacode}`,
-    };
-  });
-
-  const isShowIframe = computed(() => {
-    if (window.location.protocol == 'https:' && url.value.H5.split('://')[0] == 'http') {
-      return '您的商城前端域名ssl未开启,<br/>请扫码预览';
-    } else {
-      if (!state.domain) {
-        return '请在商城配置设置您的前端域名';
-      }
-    }
-  });
-
-  onMounted(() => {
-    getConfig();
-  });
-</script>
-
-<style lang="scss">
-  .sa-dialog.preview-dialog {
-    max-height: 76vh;
-    margin: 12vh auto;
-    @media only screen and (max-width: 768px) {
-      max-height: 100vh;
-      margin: 0;
-    }
-  }
-</style>
-
-<style lang="scss" scoped>
-  .page-preview {
-    text-align: center;
-    .el-main {
-      --el-main-padding: 20px 20px 0 20px;
-    }
-    .left {
-      width: 100%;
-      margin-bottom: 20px;
-    }
-    .preview-title {
-      color: var(--sa-subtitle);
-      margin-bottom: 12px;
-    }
-    .web-preview {
-      width: 300px;
-      height: 594px;
-      background: url('/static/images/shop/decorate/preview_bg.png');
-      padding: 18px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      position: relative;
-      color: #434343;
-      #preview {
-        border: none;
-        margin: 0 auto;
-        width: 100%;
-        height: 100%;
-        border-radius: 26px;
-      }
-    }
-    .name {
-      font-size: 18px;
-      color: var(--sa-title);
-      margin-bottom: 12px;
-    }
-    .copyright {
-      font-size: 12px;
-      color: var(--sa-subfont);
-      margin-bottom: 12px;
-    }
-    .h5,
-    .wechat {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      .tip {
-        font-size: 14px;
-        color: var(--sa-font);
-        margin-bottom: 24px;
-      }
-    }
-  }
-</style>

+ 0 - 1353
src/app/shop/admin/decorate/page/data.js

@@ -1,1353 +0,0 @@
-import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
-import { api as goodsApi } from '@/app/shop/admin/goods/goods.service';
-import { api as couponApi } from '@/app/shop/admin/coupon/coupon.service';
-import { api as dataApi } from '@/app/shop/admin/data/data.service';
-import { api as activityApi } from '@/app/shop/admin/activity/activity.service';
-import { api as appApi } from '@/app/shop/admin/app/app.service';
-
-// 主题
-export const themeColor = {
-  orange: {
-    color1: '#FF6000',
-    color2: '#FE832A',
-  },
-  golden: {
-    color1: '#E9B461',
-    color2: '#EECC89',
-  },
-  yellow: {
-    color1: '#FFC300',
-    color2: '#FDDF47',
-  },
-  black: {
-    color1: '#484848',
-    color2: '#6D6D6D',
-  },
-  green: {
-    color1: '#2AAE67',
-    color2: '#3ACD72',
-  },
-  purple: {
-    color1: '#652ABF',
-    color2: '#A36FFF',
-  },
-};
-
-// 页面列表
-export const pageTypeList = [
-  {
-    type: 'basic',
-    label: '基础配置',
-  },
-  {
-    type: 'home',
-    label: '首页',
-  },
-  {
-    type: 'user',
-    label: '个人页',
-  },
-];
-
-// 系统列表
-export const systemList = [
-  {
-    type: 'android',
-    label: 'Android',
-    color: '#6F74E9',
-  },
-  {
-    type: 'ios',
-    label: 'IOS',
-    color: '#333333',
-  },
-];
-
-// 平台列表
-export const platformList = [
-  {
-    type: 'H5',
-    label: 'H5',
-    color: '#FC800E',
-  },
-  {
-    type: 'App',
-    label: 'APP',
-    color: '#806AF6',
-  },
-];
-
-// 基础列表
-export const basicList = [
-  {
-    name: '应用设置',
-    type: 'basic',
-    data: [
-      // {
-      //     name: '启动页',
-      //     type: 'splashScreen',
-      // },
-      // { name: '引导页', type: 'guidePage' },
-      {
-        name: '底部导航',
-        type: 'tabbar',
-      },
-      {
-        name: '悬浮按钮',
-        type: 'floatMenu',
-      },
-      {
-        name: '弹窗广告',
-        type: 'popupImage',
-      },
-    ],
-  },
-  {
-    name: '主题色',
-    type: 'theme',
-    data: [
-      {
-        name: '淘宝橙',
-        type: 'orange',
-      },
-      {
-        name: '香槟金',
-        type: 'golden',
-      },
-      {
-        name: '美团黄',
-        type: 'yellow',
-      },
-      {
-        name: '低奢黑',
-        type: 'black',
-      },
-      {
-        name: '微信绿',
-        type: 'green',
-      },
-      {
-        name: '尊贵紫',
-        type: 'purple',
-      },
-    ],
-  },
-];
-
-// 组件列表
-export const compList = [
-  {
-    name: '会员组件',
-    type: '0',
-    show: ['user', 'diypage'],
-    data: [
-      {
-        name: '会员卡片',
-        type: 'userCard',
-      },
-      {
-        name: '订单卡片',
-        type: 'orderCard',
-      },
-      {
-        name: '资产卡片',
-        type: 'walletCard',
-      },
-      {
-        name: '卡券卡片',
-        type: 'couponCard',
-      },
-    ],
-  },
-  {
-    name: '基础组件',
-    type: '1',
-    data: [
-      {
-        name: '搜索框',
-        type: 'searchBlock',
-      },
-      {
-        name: '公告栏',
-        type: 'noticeBlock',
-      },
-      {
-        name: '菜单导航',
-        type: 'menuButton',
-      },
-      {
-        name: '列表导航',
-        type: 'menuList',
-      },
-      {
-        name: '宫格导航',
-        type: 'menuGrid',
-      },
-    ],
-  },
-  {
-    name: '商品组件',
-    type: '2',
-    data: [
-      {
-        name: '商品卡片',
-        type: 'goodsCard',
-      },
-      {
-        name: '商品栏',
-        type: 'goodsShelves',
-      },
-    ],
-  },
-  {
-    name: '图文组件',
-    type: '3',
-    data: [
-      {
-        name: '图片展示',
-        type: 'imageBlock',
-      },
-      {
-        name: '图片轮播',
-        type: 'imageBanner',
-      },
-      {
-        name: '标题栏',
-        type: 'titleBlock',
-      },
-      {
-        name: '广告魔方',
-        type: 'imageCube',
-      },
-      {
-        name: '视频播放',
-        type: 'videoPlayer',
-      },
-      {
-        name: '辅助线',
-        type: 'lineBlock',
-      },
-      {
-        name: '富文本',
-        type: 'richtext',
-      },
-      {
-        name: '热区',
-        type: 'hotzone',
-      },
-    ],
-  },
-  {
-    name: '营销组件',
-    type: '4',
-    data: [
-      {
-        name: '拼团',
-        type: 'groupon',
-      },
-      {
-        name: '秒杀',
-        type: 'seckill',
-      },
-      {
-        name: '积分商城',
-        type: 'scoreGoods',
-      },
-      { name: '小程序直播', type: 'mplive' },
-      {
-        name: '优惠券',
-        type: 'coupon',
-      },
-      // {
-      //     name: '关注公众号',
-      //     type: 'subscribeWechatOfficialAccount',
-      // },
-    ],
-  },
-];
-
-// 页面默认数据
-export const initTemplateData = {
-  basic: {
-    // splashScreen: {
-    //     status: false, // false|true
-    //     src: '',
-    //     countdown: 5,
-    //     url: '',
-    // },
-    // guidePage: {
-    //     status: false, // false|true
-    //     list: [],
-    // },
-    tabbar: {
-      mode: 1, // 1 2
-      layout: 1, // 1=文字+图片 2=文字 3=图片
-      inactiveColor: '#EEEEEE',
-      activeColor: '#000000',
-      list: [
-        // {
-        //     inactiveIcon: '',
-        //     activeIcon: '',
-        //     url: '',
-        //     text: '',
-        // }
-      ],
-      background: {
-        type: 'color', // color=纯色 image=背景图
-        bgImage: '',
-        bgColor: '#FFFFFF',
-      },
-    },
-    floatMenu: {
-      show: 0, // 0|1
-      mode: 1, // 1|2
-      isText: 0, // 0|2
-      list: [
-        {
-          src: '',
-          url: '',
-          title: {
-            text: '',
-            color: '',
-          },
-        },
-      ],
-    },
-    popupImage: {
-      list: [
-        // {
-        //     src: '',
-        //     url: '',
-        //     show:1
-        // }
-      ],
-    },
-    theme: 'orange',
-  },
-  home: {
-    data: [],
-    style: {
-      background: {
-        color: '#F6F6F6',
-        src: '',
-      },
-      navbar: {
-        mode: 'normal', // normal inner
-        alwaysShow: 0, // 0 1
-        type: 'color',
-        color: '',
-        src: '',
-        list: {
-          mp: [],
-          app: [],
-        },
-      },
-    },
-  },
-  user: {
-    data: [
-      {
-        type: 'userCard',
-        style: {
-          marginLeft: 0,
-          marginRight: 0,
-          marginTop: 0,
-          marginBottom: 10,
-          borderRadiusTop: 0,
-          borderRadiusBottom: 0,
-          background: {
-            type: 'color',
-            bgImage: '',
-            bgColor: '#FFFFFF',
-          },
-        },
-      },
-    ],
-    style: {
-      background: {
-        color: '#F6F6F6',
-        src: '',
-      },
-      navbar: {
-        mode: 'normal', // normal inner
-        alwaysShow: 0, // 0 1
-        type: 'color',
-        color: '',
-        src: '',
-        list: {
-          mp: [],
-          app: [],
-        },
-      },
-    },
-  },
-  diypage: {
-    data: [],
-    style: {
-      background: {
-        color: '',
-        src: '',
-      },
-      navbar: {
-        mode: 'normal', // normal inner
-        alwaysShow: 0, // 0 1
-        type: 'color',
-        color: '',
-        src: '',
-        list: {
-          mp: [],
-          app: [],
-        },
-      },
-    },
-  },
-};
-
-// 组件数据
-export function cloneComponent(type, theme = 'orange') {
-  let comp = {
-    userCard: {
-      type: 'userCard',
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    orderCard: {
-      type: 'orderCard',
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    walletCard: {
-      type: 'walletCard',
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    couponCard: {
-      type: 'couponCard',
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    searchBlock: {
-      type: 'searchBlock',
-      data: {
-        placeholder: '',
-        borderRadius: 0,
-        keywords: [
-          // {
-          //     text: '',
-          //     color: '#8C8C8C',
-          // }
-        ],
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    noticeBlock: {
-      type: 'noticeBlock',
-      data: {
-        mode: 1,
-        src: checkUrl('/static/img/shop/decorate/notice-1.png'),
-        title: {
-          text: '',
-          color: '#111111',
-        },
-        url: '',
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    menuButton: {
-      type: 'menuButton',
-      data: {
-        layout: 1, // 1=图片+文字 2=图片
-        col: 3, // 列数 3|4|5
-        row: 1, // 1 2 3行数 超出滑动
-        list: [
-          // {
-          //     src: '',
-          //     title: {
-          //         text: '',
-          //         color: '#000'
-          //     },
-          //     url: '',
-          //     badge: {
-          //         show: 0, // 0|1
-          //         text: '',
-          //         color: '#FFFFFF',
-          //         bgColor: '#FF6000',
-          //     },
-          // }
-        ],
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    menuList: {
-      type: 'menuList',
-      data: {
-        list: [
-          // {
-          //     src: '',
-          //     title: {
-          //         text: '',
-          //         color: '#333'
-          //     },
-          //     tip: {
-          //         text: '',
-          //         color: '#bbb'
-          //     },
-          //     url: '',
-          // }
-        ],
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    menuGrid: {
-      type: 'menuGrid',
-      data: {
-        col: 3, // 列数 3|4
-        // border: 0, // 边框 0|1
-        list: [
-          {
-            src: '',
-            title: {
-              text: '',
-              color: '#333',
-            },
-            tip: {
-              text: '',
-              color: '#bbb',
-            },
-            url: '',
-            badge: {
-              show: 0, // 0|1
-              text: '',
-              color: '#FFFFFF',
-              bgColor: '#FF6000',
-            },
-          },
-        ],
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    goodsCard: {
-      type: 'goodsCard',
-      data: {
-        mode: 1,
-        goodsFields: {
-          title: {
-            show: 1, // 0|1
-            color: '#000',
-          },
-          subtitle: {
-            show: 1, // 0|1
-            color: '#999',
-          },
-          price: {
-            show: 1, // 0|1
-            color: '#ff3000',
-          },
-          original_price: {
-            show: 1, // 0|1
-            color: '#c4c4c4',
-          },
-          sales: {
-            show: 1, // 0|1
-            color: '#c4c4c4',
-          },
-          stock: {
-            show: 0, // 0|1
-            color: '#c4c4c4',
-          },
-        },
-        buyNowStyle: {
-          mode: 1,
-          text: '立即购买',
-          color1: themeColor[theme].color1,
-          color2: themeColor[theme].color2,
-          src: '',
-        },
-        tagStyle: {
-          show: 0,
-          src: '',
-        },
-        goodsIds: [],
-        goodsList: [],
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        space: 8,
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '',
-        },
-        marginLeft: 8,
-        marginRight: 8,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    goodsShelves: {
-      type: 'goodsShelves',
-      data: {
-        mode: 1,
-        goodsFields: {
-          title: {
-            show: 1, // 0|1
-            color: '#333',
-          },
-          price: {
-            show: 1, // 0|1
-            color: '#ff3000',
-          },
-        },
-        tagStyle: {
-          show: 0,
-          src: '',
-        },
-        goodsIds: [],
-        goodsList: [],
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        space: 0,
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    imageBlock: {
-      type: 'imageBlock',
-      data: {
-        src: '',
-        url: '',
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-        // height: 300,
-      },
-    },
-    imageBanner: {
-      type: 'imageBanner',
-      data: {
-        mode: 1, // 1 2
-        indicator: 1, // 1 2
-        autoplay: false,
-        interval: 3000,
-        list: [
-          // {
-          //     title: '',
-          //     type: 'image',
-          //     src: '',
-          //     poster: '',
-          //     url: ''
-          // }
-        ],
-        space: 0,
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-        // height: 300,
-      },
-    },
-    titleBlock: {
-      type: 'titleBlock',
-      data: {
-        src: checkUrl('/static/img/shop/decorate/title-1.png'),
-        location: 'left', // left=居左 center=居中
-        skew: 0,
-        title: {
-          text: '标题栏',
-          color: '#111111',
-          textFontSize: 14,
-          other: [], // bold=加粗 italic=倾斜
-        },
-        subtitle: {
-          text: '副标题',
-          color: '#8c8c8c',
-          textFontSize: 12,
-          other: [], // bold=加粗 italic=倾斜
-        },
-        more: {
-          show: 0, // 0=不显示 1=显示
-          url: '',
-        },
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 0,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-        height: 40,
-      },
-    },
-    imageCube: {
-      type: 'imageCube',
-      data: {
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        space: 0,
-        list: [
-          // {
-          //     width: 0,
-          //     height: 0,
-          //     top: 0,
-          //     left: 0,
-          //     src: '',
-          //     url: ''
-          // }
-        ],
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    videoPlayer: {
-      type: 'videoPlayer',
-      data: {
-        videoUrl: '', // 视频地址
-        src: '', // 视频封面
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-        height: 300,
-      },
-    },
-    lineBlock: {
-      type: 'lineBlock',
-      data: {
-        mode: 'solid', // solid dotted dashed
-        lineColor: '',
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        padding: 0,
-      },
-    },
-    richtext: {
-      type: 'richtext',
-      data: {
-        id: '',
-        title: '',
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        padding: 0,
-      },
-    },
-    hotzone: {
-      type: 'hotzone',
-      data: {
-        src: '',
-        list: [],
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        // marginLeft: 0,
-        // marginRight: 0,
-        // marginTop: 0,
-        // marginBottom: 10,
-        // padding: 0,
-      },
-    },
-    groupon: {
-      type: 'groupon',
-      data: {
-        activityId: '',
-        activityList: [],
-        goodsList: [],
-        mode: 1,
-        tagStyle: {
-          show: 0, // 0,1
-          src: '',
-        },
-        goodsFields: {
-          title: {
-            show: 1, // 0|1
-            color: '#000',
-          },
-          subtitle: {
-            show: 1, // 0|1
-            color: '#999',
-          },
-          price: {
-            show: 1, // 0|1
-            color: '#FF0000',
-          },
-          original_price: {
-            show: 1, // 0|1
-            color: '#C4C4C4',
-          },
-          sales: {
-            show: 1, // 0|1
-            color: '#c4c4c4',
-          },
-        },
-        buyNowStyle: {
-          mode: 1,
-          text: '立即拼团',
-          color1: themeColor[theme].color1,
-          color2: themeColor[theme].color2,
-          src: '',
-        },
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        space: 0,
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    seckill: {
-      type: 'seckill',
-      data: {
-        activityId: '',
-        activityList: [],
-        goodsList: [],
-        mode: 1,
-        tagStyle: {
-          show: 0, // 0,1
-          src: '',
-        },
-        goodsFields: {
-          title: {
-            show: 1, // 0|1
-            color: '#000',
-          },
-          subtitle: {
-            show: 1, // 0|1
-            color: '#999',
-          },
-          price: {
-            show: 1, // 0|1
-            color: '#FF0000',
-          },
-          original_price: {
-            show: 1, // 0|1
-            color: '#C4C4C4',
-          },
-          sales: {
-            show: 1, // 0|1
-            color: '#c4c4c4',
-          },
-        },
-        buyNowStyle: {
-          mode: 1,
-          text: '去抢购',
-          color1: themeColor[theme].color1,
-          color2: themeColor[theme].color2,
-          src: '',
-        },
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        space: 0,
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    scoreGoods: {
-      type: 'scoreGoods',
-      data: {
-        goodsIds: [],
-        goodsList: [],
-        mode: 1,
-        goodsFields: {
-          title: {
-            show: 1, // 0|1
-            color: '#333',
-          },
-          subtitle: {
-            show: 1, // 0|1
-            color: '#999',
-          },
-          score_price: {
-            show: 1, // 0|1
-            color: '#FF3000',
-          },
-          price: {
-            show: 1, // 0|1
-            color: '#C4C4C4',
-          },
-        },
-        buyNowStyle: {
-          mode: 1,
-          text: '去兑换',
-          color1: themeColor[theme].color1,
-          color2: themeColor[theme].color2,
-          src: '',
-        },
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        space: 0,
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    mplive: {
-      type: 'mplive',
-      data: {
-        mode: 1,
-        goodsFields: {
-          name: {
-            show: 1, // 0|1
-            color: '#FDFDFD',
-          },
-          anchor_name: {
-            show: 1, // 0|1
-            color: '#FDFDFD',
-          },
-        },
-        mpliveIds: [],
-        mpliveList: [],
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        space: 8,
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '',
-        },
-        marginLeft: 8,
-        marginRight: 8,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    coupon: {
-      type: 'coupon',
-      data: {
-        couponIds: [],
-        couponList: [],
-        mode: 1,
-        fill: {
-          color: '',
-          bgImage: '',
-        },
-        button: {
-          color: '',
-          bgColor: '',
-        },
-        space: 0,
-      },
-      style: {
-        background: {
-          type: 'color',
-          bgImage: '',
-          bgColor: '#FFFFFF',
-        },
-        marginLeft: 0,
-        marginRight: 0,
-        marginTop: 0,
-        marginBottom: 10,
-        borderRadiusTop: 0,
-        borderRadiusBottom: 0,
-        padding: 0,
-      },
-    },
-    // subscribeWechatOfficialAccount: {
-    //     type: 'subscribeWechatOfficialAccount',
-    //     data: {
-    //         src: '',
-    //         title: {
-    //             text: '',
-    //             textColor: ''
-    //         },
-    //         subtitle: {
-    //             text: '',
-    //             textColor: ''
-    //         }
-    //     },
-    //     style: {
-    //         background: {
-    //             type: 'color',
-    //             bgImage: '',
-    //             bgColor: '#FFFFFF',
-    //         },
-    //         marginLeft: 0,
-    //         marginRight: 0,
-    //         marginTop: 0,
-    //         marginBottom: 10,
-    //         borderRadiusTop: 0,
-    //         borderRadiusBottom: 0,
-    //         padding: 0,
-    //     },
-    // },
-  };
-  return comp[type];
-}
-
-// 组件名称
-export const compNameObj = {
-  // splashScreen: '启动页',
-  // guidePage: '引导页',
-  tabbar: '底部导航',
-  floatMenu: '悬浮按钮',
-  popupImage: '弹窗广告',
-  page: '页面设置',
-  userCard: '会员卡片',
-  orderCard: '订单卡片',
-  walletCard: '资产卡片',
-  couponCard: '卡券卡片',
-  searchBlock: '搜索框',
-  noticeBlock: '公告栏',
-  menuButton: '菜单导航',
-  menuList: '列表导航',
-  menuGrid: '宫格导航',
-  goodsCard: '商品卡片',
-  goodsShelves: '商品栏',
-  imageBlock: '图片展示',
-  imageBanner: '图片轮播',
-  titleBlock: '标题栏',
-  imageCube: '广告魔方',
-  videoPlayer: '视频播放',
-  lineBlock: '辅助线',
-  richtext: '富文本',
-  hotzone: '热区',
-  groupon: '拼团',
-  seckill: '秒杀',
-  scoreGoods: '积分商城',
-  coupon: '优惠券',
-};
-
-// 初始化暂存数据
-export function handleTempData(data) {
-  data.data.forEach(async (t) => {
-    if (['goodsCard', 'goodsShelves'].includes(t.type)) {
-      const { error, data } = await goodsApi.goods.select(
-        {
-          search: JSON.stringify({ id: [t.data.goodsIds.join(','), 'in'] }),
-        },
-        'select',
-      );
-      t.data.goodsList = error === 0 ? data : [];
-    } else if (t.type == 'coupon') {
-      const { error, data } = await couponApi.select(
-        {
-          search: JSON.stringify({ id: [t.data.couponIds.join(','), 'in'], status: ['normal'] }),
-        },
-        'select',
-      );
-      t.data.couponList = error === 0 ? data : [];
-    } else if (t.type == 'richtext') {
-      const { error, data } = await dataApi.richtext.select(
-        {
-          search: JSON.stringify({ id: [t.data.id, 'in'] }),
-        },
-        'find',
-      );
-      t.data.richtext = error === 0 ? data : [];
-    } else if (t.type == 'groupon' || t.type == 'seckill') {
-      if (t.data.activityId) {
-        const detail = await activityApi.activity.detail(t.data.activityId);
-        if (detail.error === 0) {
-          t.data.activityList = [detail.data];
-          const { error, data } = await goodsApi.goods.activitySelect({
-            activity_id: t.data.activityId,
-            need_buyers: t.type == 'groupon' ? 1 : 0,
-          });
-          t.data.goodsList = error === 0 ? data : [];
-        } else {
-          t.data.activityList = [];
-          t.data.goodsList = [];
-        }
-      } else {
-        t.data.activityList = [];
-        t.data.goodsList = [];
-      }
-    } else if (t.type == 'scoreGoods') {
-      const { error, data } = await appApi.scoreShop.select(
-        {
-          search: JSON.stringify({ id: [t.data.goodsIds.join(','), 'in'] }),
-        },
-        'select',
-      );
-      t.data.goodsList = error === 0 ? data : [];
-    } else if (t.type == 'mplive') {
-      const { error, data } = await appApi.mplive.room.select({
-        search: JSON.stringify({ roomid: [t.data.mpliveIds.join(','), 'in'] }),
-      });
-      t.data.mpliveList = error === 0 ? data : [];
-    }
-  });
-
-  return data;
-}
-
-// 提交删除多余数据
-export function handleSubmitData(data) {
-  data.data.forEach((t) => {
-    if (['goodsCard', 'goodsShelves'].includes(t.type)) {
-      t.data.goodsIds = [];
-      t.data.goodsList.forEach((g) => {
-        t.data.goodsIds.push(g.id);
-      });
-      delete t.data.goodsList;
-    } else if (t.type == 'coupon') {
-      t.data.couponIds = [];
-      t.data.couponList?.forEach((c) => {
-        t.data.couponIds.push(c.id);
-      });
-      delete t.data.couponList;
-    } else if (t.type == 'richtext') {
-      delete t.data.richtext;
-    } else if (t.type == 'groupon' || t.type == 'seckill') {
-      t.data.activityId = [];
-      t.data.activityList.forEach((c) => {
-        t.data.activityId.push(c.id);
-      });
-      t.data.activityId = t.data.activityId.join(',');
-      delete t.data.activityList;
-      delete t.data.goodsList;
-    } else if (t.type == 'scoreGoods') {
-      t.data.goodsIds = [];
-      t.data.goodsList.forEach((c) => {
-        t.data.goodsIds.push(c.id);
-      });
-      delete t.data.goodsList;
-    } else if (t.type == 'mplive') {
-      t.data.mpliveIds = [];
-      t.data.mpliveList.forEach((c) => {
-        t.data.mpliveIds.push(c.roomid);
-      });
-      delete t.data.mpliveList;
-    }
-  });
-
-  return data;
-}

+ 0 - 173
src/app/shop/admin/decorate/page/preview.vue

@@ -1,173 +0,0 @@
-<template>
-  <el-container class="page-preview">
-    <el-main>
-      <el-row :gutter="20">
-        <el-col class="sa-col-12" :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-          <div
-            v-if="
-              props.modal.params.data.type == 'diypage' ||
-              state.templateData.platform.includes('H5') ||
-              state.templateData.platform.includes('WechatOfficialAccount')
-            "
-            class="left sa-flex-col sa-col-center"
-          >
-            <div class="preview-title">此为预览效果,实际效果请扫码查看</div>
-            <div class="web-preview">
-              <div v-if="isShowIframe" class="web-preview-msg">
-                <span v-html="isShowIframe"></span>
-              </div>
-              <iframe v-else id="preview" :src="url.H5" frameborder="1" height="600px"></iframe>
-            </div>
-          </div>
-        </el-col>
-        <el-col class="sa-col-12" :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-          <div class="name">{{ state.templateData.name }}</div>
-          <template
-            v-if="
-              props.modal.params.data.type == 'diypage' || state.templateData.platform.length > 0
-            "
-          >
-            <div class="platform sa-m-b-20">
-              <sa-icon
-                class="sa-m-r-8"
-                v-for="pl in state.templateData.platform"
-                :key="pl"
-                :icon="'sa-shop-decorate-' + pl"
-                :style="{
-                  color: platformList.filter((pf) => {
-                    return pf.type == pl;
-                  })[0].color,
-                }"
-                size="20"
-              />
-            </div>
-            <div
-              v-if="
-                props.modal.params.data.type == 'diypage' ||
-                state.templateData.platform.includes('H5')
-              "
-              class="h5"
-            >
-              <qrcode-vue :value="url.H5" :size="132" level="H" />
-              <div class="tip sa-m-t-12">扫描二维码即可预览</div>
-            </div>
-          </template>
-        </el-col>
-      </el-row>
-    </el-main>
-  </el-container>
-</template>
-<script setup>
-  import { computed, onMounted, reactive } from 'vue';
-  import { api as configApi } from '@/app/shop/admin/config/config.service';
-  import { api } from '../decorate.service';
-  import { platformList } from './data';
-  import QrcodeVue from 'qrcode.vue';
-
-  const props = defineProps(['modal']);
-
-  const state = reactive({
-    templateData: props.modal.params.data,
-    domain: '',
-  });
-
-  async function getConfig() {
-    const { error, data } = await configApi.basic();
-    error === 0 && (state.domain = data.domain);
-  }
-
-  const url = computed(() => {
-    if (props.modal.params.data.type == 'diypage') {
-      return {
-        H5: `${state.domain}pages/index/page?id=${state.templateData.id}`,
-        WechatMiniProgram: api.getWxacode(`/pages/index/page?id=${state.templateData.id}`),
-      };
-    } else {
-      return {
-        H5: `${state.domain}pages/index/index?templateId=${state.templateData.id}`,
-        WechatMiniProgram: api.getWxacode(`/pages/index/index?templateId=${state.templateData.id}`),
-      };
-    }
-  });
-
-  const isShowIframe = computed(() => {
-    if (window.location.protocol == 'https:' && url.value.H5.split('://')[0] == 'http') {
-      return '您的商城前端域名ssl未开启,<br/>请扫码预览';
-    } else {
-      if (!state.domain) {
-        return '请在商城配置设置您的前端域名';
-      }
-    }
-  });
-
-  onMounted(() => {
-    getConfig();
-  });
-</script>
-
-<style lang="scss">
-  .sa-dialog.preview-dialog {
-    max-height: 76vh;
-    margin: 12vh auto;
-    @media only screen and (max-width: 768px) {
-      max-height: 100vh;
-      margin: 0;
-    }
-  }
-</style>
-
-<style lang="scss" scoped>
-  .page-preview {
-    text-align: center;
-    .el-main {
-      --el-main-padding: 20px 20px 0 20px;
-    }
-    .left {
-      width: 100%;
-      margin-bottom: 20px;
-    }
-    .preview-title {
-      color: var(--sa-subtitle);
-      margin-bottom: 12px;
-    }
-    .web-preview {
-      width: 300px;
-      height: 594px;
-      background: url('/static/images/shop/decorate/preview_bg.png');
-      padding: 18px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      position: relative;
-      color: #434343;
-      #preview {
-        border: none;
-        margin: 0 auto;
-        width: 100%;
-        height: 100%;
-        border-radius: 26px;
-      }
-    }
-    .name {
-      font-size: 18px;
-      color: var(--sa-title);
-      margin-bottom: 12px;
-    }
-    .h5,
-    .wechat {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      .tip {
-        font-size: 14px;
-        color: var(--sa-font);
-        margin-bottom: 24px;
-      }
-    }
-    .wechatminiprogram {
-      background: #fdfdfd;
-      width: 132px;
-      height: 132px;
-    }
-  }
-</style>

+ 0 - 89
src/app/shop/admin/decorate/template/edit.vue

@@ -1,89 +0,0 @@
-<template>
-  <el-container>
-    <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
-        <el-form-item label="模板名称" prop="name">
-          <el-input v-model="form.model.name" placeholder="请输入模板名称"></el-input>
-        </el-form-item>
-        <el-form-item label="备注">
-          <el-input type="textarea" v-model="form.model.memo" placeholder="请输入备注"></el-input>
-        </el-form-item>
-        <el-form-item v-if="modal?.params.templateType == 'template'" label="发布平台">
-          <el-checkbox-group v-model="form.model.platform">
-            <el-checkbox label="H5">H5</el-checkbox>
-            <el-checkbox label="App">APP</el-checkbox>
-          </el-checkbox-group>
-        </el-form-item>
-      </el-form>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button
-        v-if="modal.params.type == 'add'"
-        v-auth="'shop.admin.decorate.template.add'"
-        type="primary"
-        @click="confirm"
-        >保存</el-button
-      >
-      <el-button
-        v-if="modal.params.type == 'edit'"
-        v-auth="'shop.admin.decorate.template.edit'"
-        type="primary"
-        @click="confirm"
-        >更新</el-button
-      >
-    </el-footer>
-  </el-container>
-</template>
-<script setup>
-  import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from '../decorate.service';
-  import { cloneDeep } from 'lodash';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
-
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      type: props.modal.params.templateType,
-      name: '',
-      memo: '',
-      platform: [],
-    },
-    rules: {},
-  });
-  const loading = ref(false);
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
-    const { error, data } = await api.template.detail(id);
-    error === 0 && (form.model = data);
-    loading.value = false;
-  }
-
-  // 表单关闭时提交
-  function confirm() {
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      let submitForm = cloneDeep(form.model);
-      const { error } =
-        props.modal.params.type == 'add'
-          ? await api.template.add(submitForm)
-          : await api.template.edit(props.modal.params.id, submitForm);
-      if (error == 0) {
-        emit('modalCallBack', { event: 'confirm' });
-      }
-    });
-  }
-
-  onMounted(() => {
-    if (props.modal.params.type == 'edit') {
-      getDetail(props.modal.params.id);
-    }
-  });
-</script>

+ 0 - 27
src/app/shop/admin/finance/commission/commission.service.js

@@ -1,27 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'commission',
-  name: 'shop.admin.finance.commission',
-  component: () => import('@/app/shop/admin/finance/commission/index.vue'),
-  meta: {
-    title: '佣金',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/commission'),
-  select: (params) => SELECT('shop/admin/commission', params),
-  settle: (id) => ({
-    error: 0,
-    msg: '结算成功',
-    data: null,
-  }),
-  batchSettle: (ids) => ({
-    error: 0,
-    msg: '批量结算成功',
-    data: null,
-  }),
-};
-
-export { route, api };

+ 22 - 18
src/app/shop/admin/finance/commission/edit.vue

@@ -12,14 +12,14 @@
           </div>
         </el-form-item>
         <el-form-item label="佣金金额" prop="amount">
-          <el-input-number 
-            v-model="form.model.amount" 
-            :min="0" 
+          <el-input-number
+            v-model="form.model.amount"
+            :min="0"
             :precision="2"
             placeholder="佣金金额"
             :disabled="isView"
           />
-          <span style="margin-left: 10px;">৳</span>
+          <span style="margin-left: 10px">৳</span>
         </el-form-item>
         <el-form-item label="佣金类型" prop="type">
           <el-select v-model="form.model.type" placeholder="请选择佣金类型" :disabled="isView">
@@ -29,18 +29,22 @@
           </el-select>
         </el-form-item>
         <el-form-item label="来源订单" prop="source_order_no">
-          <el-input v-model="form.model.source_order_no" placeholder="请填写来源订单号" :disabled="isView"></el-input>
+          <el-input
+            v-model="form.model.source_order_no"
+            placeholder="请填写来源订单号"
+            :disabled="isView"
+          ></el-input>
         </el-form-item>
         <el-form-item label="佣金比例" prop="commission_rate">
-          <el-input-number 
-            v-model="form.model.commission_rate" 
-            :min="0" 
+          <el-input-number
+            v-model="form.model.commission_rate"
+            :min="0"
             :max="100"
             :precision="2"
             placeholder="佣金比例"
             :disabled="isView"
           />
-          <span style="margin-left: 10px;">%</span>
+          <span style="margin-left: 10px">%</span>
         </el-form-item>
         <el-form-item label="状态" prop="status">
           <el-select v-model="form.model.status" placeholder="请选择状态" :disabled="isView">
@@ -56,9 +60,9 @@
           <span>{{ form.model.settle_time || '-' }}</span>
         </el-form-item>
         <el-form-item label="备注" prop="remark">
-          <el-input 
-            v-model="form.model.remark" 
-            type="textarea" 
+          <el-input
+            v-model="form.model.remark"
+            type="textarea"
             :rows="3"
             placeholder="请填写备注"
             :disabled="isView"
@@ -75,16 +79,16 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref, computed } from 'vue';
-  import { api } from './commission.service';
+  import { api } from '../finance.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {
       type: Object,
     },
   });
-  
+
   const isView = computed(() => props.modal.params.type === 'view');
-  
+
   // 添加 编辑 form
   let formRef = ref(null);
   const form = reactive({
@@ -113,7 +117,7 @@
   // 获取详情
   async function getDetail(id) {
     loading.value = true;
-    const { error, data } = await api.detail(id);
+    const { error, data } = await api.commission.detail(id);
     error === 0 && (form.model = data);
     loading.value = false;
   }
@@ -124,8 +128,8 @@
       let submitForm = cloneDeep(form.model);
       const { error } =
         props.modal.params.type == 'add'
-          ? await api.add(submitForm)
-          : await api.edit(props.modal.params.id, submitForm);
+          ? await api.commission.add(submitForm)
+          : await api.commission.edit(props.modal.params.id, submitForm);
       if (error == 0) {
         emit('modalCallBack', { event: 'confirm' });
       }

+ 13 - 17
src/app/shop/admin/finance/commission/index.vue

@@ -45,9 +45,7 @@
             </template>
           </el-table-column>
           <el-table-column label="佣金金额" min-width="120">
-            <template #default="scope">
-              ৳{{ scope.row.amount || 0 }}
-            </template>
+            <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
           </el-table-column>
           <el-table-column label="佣金类型" min-width="120">
             <template #default="scope">
@@ -64,9 +62,7 @@
             </template>
           </el-table-column>
           <el-table-column label="佣金比例" min-width="100">
-            <template #default="scope">
-              {{ scope.row.commission_rate || 0 }}%
-            </template>
+            <template #default="scope"> {{ scope.row.commission_rate || 0 }}% </template>
           </el-table-column>
           <el-table-column label="状态" min-width="100">
             <template #default="scope">
@@ -88,10 +84,10 @@
           <el-table-column fixed="right" label="操作" min-width="120">
             <template #default="scope">
               <el-button class="is-link" type="primary" @click="editRow(scope.row)">查看</el-button>
-              <el-button 
+              <el-button
                 v-if="scope.row.status === 'pending'"
-                class="is-link" 
-                type="success" 
+                class="is-link"
+                type="success"
                 @click="settleCommission(scope.row)"
               >
                 结算
@@ -117,7 +113,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './commission.service';
+  import { api } from '../finance.service';
   import { ElMessageBox, ElMessage } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';
@@ -157,7 +153,7 @@
     selected: [],
   });
   const loading = ref(true);
-  
+
   // 获取状态类型
   function getStatusType(status) {
     const statusMap = {
@@ -167,12 +163,12 @@
     };
     return statusMap[status] || 'info';
   }
-  
+
   // 获取
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.list({
+    const { error, data } = await api.commission.list({
       page: pageData.page,
       list_rows: pageData.list_rows,
       order: table.order,
@@ -233,7 +229,7 @@
       },
     );
   }
-  
+
   // 结算佣金
   async function settleCommission(row) {
     ElMessageBox.confirm('确认结算该笔佣金?', '提示', {
@@ -241,14 +237,14 @@
       cancelButtonText: '取消',
       type: 'warning',
     }).then(async () => {
-      const { error } = await api.settle(row.id);
+      const { error } = await api.commission.settle(row.id);
       if (error === 0) {
         ElMessage.success('结算成功');
         getData();
       }
     });
   }
-  
+
   async function batchHandle(type) {
     let ids = [];
     table.selected.forEach((row) => {
@@ -261,7 +257,7 @@
           cancelButtonText: '取消',
           type: 'warning',
         }).then(async () => {
-          const { error } = await api.batchSettle(ids.join(','));
+          const { error } = await api.commission.batchSettle(ids.join(','));
           if (error === 0) {
             ElMessage.success('批量结算成功');
             getData();

+ 99 - 0
src/app/shop/admin/finance/finance.service.js

@@ -0,0 +1,99 @@
+import Content from '@/sheep/layouts/content.vue';
+import { SELECT, CRUD } from '@/sheep/request/crud';
+
+const route = {
+  path: 'finance',
+  name: 'shop.admin.finance',
+  component: Content,
+  meta: {
+    title: '财务',
+  },
+  children: [
+    {
+      path: 'commission',
+      name: 'shop.admin.finance.commission',
+      component: () => import('@/app/shop/admin/finance/commission/index.vue'),
+      meta: {
+        title: '佣金管理',
+      },
+    },
+    {
+      path: 'recharge',
+      name: 'shop.admin.finance.recharge',
+      component: () => import('@/app/shop/admin/finance/recharge/index.vue'),
+      meta: {
+        title: '充值管理',
+      },
+    },
+    {
+      path: 'withdraw',
+      name: 'shop.admin.finance.withdraw',
+      component: () => import('@/app/shop/admin/finance/withdraw/index.vue'),
+      meta: {
+        title: '提现管理',
+      },
+    },
+  ],
+};
+
+const api = {
+  // 佣金相关 API
+  commission: {
+    ...CRUD('shop/admin/finance/commission'),
+    select: (params) => SELECT('shop/admin/finance/commission', params),
+    settle: (id) => ({
+      error: 0,
+      msg: '结算成功',
+      data: null,
+    }),
+    batchSettle: (ids) => ({
+      error: 0,
+      msg: '批量结算成功',
+      data: null,
+    }),
+  },
+  
+  // 充值相关 API
+  recharge: {
+    ...CRUD('shop/admin/finance/recharge'),
+    select: (params) => SELECT('shop/admin/finance/recharge', params),
+    confirm: (id) => ({
+      error: 0,
+      msg: '确认成功',
+      data: null,
+    }),
+    batchConfirm: (ids) => ({
+      error: 0,
+      msg: '批量确认成功',
+      data: null,
+    }),
+  },
+  
+  // 提现相关 API
+  withdraw: {
+    ...CRUD('shop/admin/finance/withdraw'),
+    select: (params) => SELECT('shop/admin/finance/withdraw', params),
+    approve: (id) => ({
+      error: 0,
+      msg: '通过成功',
+      data: null,
+    }),
+    reject: (id, data) => ({
+      error: 0,
+      msg: '拒绝成功',
+      data: null,
+    }),
+    batchApprove: (ids) => ({
+      error: 0,
+      msg: '批量通过成功',
+      data: null,
+    }),
+    batchReject: (ids, data) => ({
+      error: 0,
+      msg: '批量拒绝成功',
+      data: null,
+    }),
+  },
+};
+
+export { route, api };

+ 28 - 16
src/app/shop/admin/finance/recharge/edit.vue

@@ -3,20 +3,28 @@
     <el-main>
       <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
         <el-form-item label="用户ID" prop="user_id">
-          <el-input v-model="form.model.user_id" placeholder="请填写用户ID" :disabled="isView"></el-input>
+          <el-input
+            v-model="form.model.user_id"
+            placeholder="请填写用户ID"
+            :disabled="isView"
+          ></el-input>
         </el-form-item>
         <el-form-item label="充值金额" prop="amount">
-          <el-input-number 
-            v-model="form.model.amount" 
-            :min="0" 
+          <el-input-number
+            v-model="form.model.amount"
+            :min="0"
             :precision="2"
             placeholder="充值金额"
             :disabled="isView"
           />
-          <span style="margin-left: 10px;">৳</span>
+          <span style="margin-left: 10px">৳</span>
         </el-form-item>
         <el-form-item label="支付方式" prop="payment_method">
-          <el-select v-model="form.model.payment_method" placeholder="请选择支付方式" :disabled="isView">
+          <el-select
+            v-model="form.model.payment_method"
+            placeholder="请选择支付方式"
+            :disabled="isView"
+          >
             <el-option label="支付宝" value="alipay"></el-option>
             <el-option label="微信支付" value="wechat"></el-option>
             <el-option label="银行卡" value="bank"></el-option>
@@ -24,7 +32,11 @@
           </el-select>
         </el-form-item>
         <el-form-item label="订单号" prop="order_no">
-          <el-input v-model="form.model.order_no" placeholder="请填写订单号" :disabled="isView"></el-input>
+          <el-input
+            v-model="form.model.order_no"
+            placeholder="请填写订单号"
+            :disabled="isView"
+          ></el-input>
         </el-form-item>
         <el-form-item label="状态" prop="status">
           <el-select v-model="form.model.status" placeholder="请选择状态" :disabled="isView">
@@ -34,9 +46,9 @@
           </el-select>
         </el-form-item>
         <el-form-item label="备注" prop="remark">
-          <el-input 
-            v-model="form.model.remark" 
-            type="textarea" 
+          <el-input
+            v-model="form.model.remark"
+            type="textarea"
             :rows="3"
             placeholder="请填写备注"
             :disabled="isView"
@@ -53,16 +65,16 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref, computed } from 'vue';
-  import { api } from './recharge.service';
+  import { api } from '../finance.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {
       type: Object,
     },
   });
-  
+
   const isView = computed(() => props.modal.params.type === 'view');
-  
+
   // 添加 编辑 form
   let formRef = ref(null);
   const form = reactive({
@@ -86,7 +98,7 @@
   // 获取详情
   async function getDetail(id) {
     loading.value = true;
-    const { error, data } = await api.detail(id);
+    const { error, data } = await api.recharge.detail(id);
     error === 0 && (form.model = data);
     loading.value = false;
   }
@@ -97,8 +109,8 @@
       let submitForm = cloneDeep(form.model);
       const { error } =
         props.modal.params.type == 'add'
-          ? await api.add(submitForm)
-          : await api.edit(props.modal.params.id, submitForm);
+          ? await api.recharge.add(submitForm)
+          : await api.recharge.edit(props.modal.params.id, submitForm);
       if (error == 0) {
         emit('modalCallBack', { event: 'confirm' });
       }

+ 12 - 14
src/app/shop/admin/finance/recharge/index.vue

@@ -45,9 +45,7 @@
             </template>
           </el-table-column>
           <el-table-column label="充值金额" min-width="120">
-            <template #default="scope">
-              ৳{{ scope.row.amount || 0 }}
-            </template>
+            <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
           </el-table-column>
           <el-table-column label="支付方式" min-width="120">
             <template #default="scope">
@@ -83,10 +81,10 @@
           <el-table-column fixed="right" label="操作" min-width="120">
             <template #default="scope">
               <el-button class="is-link" type="primary" @click="editRow(scope.row)">查看</el-button>
-              <el-button 
+              <el-button
                 v-if="scope.row.status === 'pending'"
-                class="is-link" 
-                type="success" 
+                class="is-link"
+                type="success"
                 @click="confirmRecharge(scope.row)"
               >
                 确认
@@ -112,7 +110,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './recharge.service';
+  import { api } from '../finance.service';
   import { ElMessageBox, ElMessage } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';
@@ -152,7 +150,7 @@
     selected: [],
   });
   const loading = ref(true);
-  
+
   // 获取状态类型
   function getStatusType(status) {
     const statusMap = {
@@ -162,12 +160,12 @@
     };
     return statusMap[status] || 'info';
   }
-  
+
   // 获取
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.list({
+    const { error, data } = await api.recharge.list({
       page: pageData.page,
       list_rows: pageData.list_rows,
       order: table.order,
@@ -228,7 +226,7 @@
       },
     );
   }
-  
+
   // 确认充值
   async function confirmRecharge(row) {
     ElMessageBox.confirm('确认该笔充值记录?', '提示', {
@@ -236,14 +234,14 @@
       cancelButtonText: '取消',
       type: 'warning',
     }).then(async () => {
-      const { error } = await api.confirm(row.id);
+      const { error } = await api.recharge.confirm(row.id);
       if (error === 0) {
         ElMessage.success('确认成功');
         getData();
       }
     });
   }
-  
+
   async function batchHandle(type) {
     let ids = [];
     table.selected.forEach((row) => {
@@ -256,7 +254,7 @@
           cancelButtonText: '取消',
           type: 'warning',
         }).then(async () => {
-          const { error } = await api.batchConfirm(ids.join(','));
+          const { error } = await api.recharge.batchConfirm(ids.join(','));
           if (error === 0) {
             ElMessage.success('批量确认成功');
             getData();

+ 0 - 27
src/app/shop/admin/finance/recharge/recharge.service.js

@@ -1,27 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'recharge',
-  name: 'shop.admin.finance.recharge',
-  component: () => import('@/app/shop/admin/finance/recharge/index.vue'),
-  meta: {
-    title: '充值',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/recharge'),
-  select: (params) => SELECT('shop/admin/recharge', params),
-  confirm: (id) => ({
-    error: 0,
-    msg: '确认成功',
-    data: null,
-  }),
-  batchConfirm: (ids) => ({
-    error: 0,
-    msg: '批量确认成功',
-    data: null,
-  }),
-};
-
-export { route, api };

+ 8 - 8
src/app/shop/admin/finance/withdraw/edit.vue

@@ -60,13 +60,13 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './withdraw.service';
+  import { api } from '../finance.service';
   const props = defineProps({
     modal: {
       type: Object,
     },
   });
-  
+
   // 表单数据
   const form = reactive({
     model: {
@@ -92,9 +92,9 @@
       remark: '',
     },
   });
-  
+
   const loading = ref(false);
-  
+
   // 获取状态类型
   function getStatusType(status) {
     const statusMap = {
@@ -105,21 +105,21 @@
     };
     return statusMap[status] || 'info';
   }
-  
+
   // 获取详情
   async function getDetail(id) {
     loading.value = true;
-    const { error, data } = await api.detail(id);
+    const { error, data } = await api.withdraw.detail(id);
     error === 0 && (form.model = data);
     loading.value = false;
   }
-  
+
   async function init() {
     if (props.modal.params.id) {
       await getDetail(props.modal.params.id);
     }
   }
-  
+
   onMounted(() => {
     init();
   });

+ 21 - 27
src/app/shop/admin/finance/withdraw/index.vue

@@ -44,19 +44,13 @@
             </template>
           </el-table-column>
           <el-table-column label="提款金额" min-width="120">
-            <template #default="scope">
-              ৳{{ scope.row.amount || 0 }}
-            </template>
+            <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
           </el-table-column>
           <el-table-column label="手续费" min-width="100">
-            <template #default="scope">
-              ৳{{ scope.row.fee || 0 }}
-            </template>
+            <template #default="scope"> ৳{{ scope.row.fee || 0 }} </template>
           </el-table-column>
           <el-table-column label="实际到账" min-width="120">
-            <template #default="scope">
-              ৳{{ scope.row.actual_amount || 0 }}
-            </template>
+            <template #default="scope"> ৳{{ scope.row.actual_amount || 0 }} </template>
           </el-table-column>
           <el-table-column label="提款方式" min-width="120">
             <template #default="scope">
@@ -80,18 +74,18 @@
           <el-table-column fixed="right" label="操作" min-width="150">
             <template #default="scope">
               <el-button class="is-link" type="primary" @click="editRow(scope.row)">查看</el-button>
-              <el-button 
+              <el-button
                 v-if="scope.row.status === 'pending'"
-                class="is-link" 
-                type="success" 
+                class="is-link"
+                type="success"
                 @click="approveWithdraw(scope.row)"
               >
                 通过
               </el-button>
-              <el-button 
+              <el-button
                 v-if="scope.row.status === 'pending'"
-                class="is-link" 
-                type="danger" 
+                class="is-link"
+                type="danger"
                 @click="rejectWithdraw(scope.row)"
               >
                 拒绝
@@ -117,7 +111,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './withdraw.service';
+  import { api } from '../finance.service';
   import { ElMessageBox, ElMessage } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';
@@ -158,7 +152,7 @@
     selected: [],
   });
   const loading = ref(true);
-  
+
   // 获取状态类型
   function getStatusType(status) {
     const statusMap = {
@@ -169,12 +163,12 @@
     };
     return statusMap[status] || 'info';
   }
-  
+
   // 获取
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.list({
+    const { error, data } = await api.withdraw.list({
       page: pageData.page,
       list_rows: pageData.list_rows,
       order: table.order,
@@ -215,7 +209,7 @@
       class: 'danger',
     },
   ];
-  
+
   function editRow(row) {
     useModal(
       withdrawEdit,
@@ -231,7 +225,7 @@
       },
     );
   }
-  
+
   // 通过提款
   async function approveWithdraw(row) {
     ElMessageBox.confirm('确认通过该笔提款申请?', '提示', {
@@ -239,14 +233,14 @@
       cancelButtonText: '取消',
       type: 'warning',
     }).then(async () => {
-      const { error } = await api.approve(row.id);
+      const { error } = await api.withdraw.approve(row.id);
       if (error === 0) {
         ElMessage.success('通过成功');
         getData();
       }
     });
   }
-  
+
   // 拒绝提款
   async function rejectWithdraw(row) {
     ElMessageBox.prompt('请输入拒绝原因', '拒绝提款', {
@@ -255,14 +249,14 @@
       inputPattern: /.+/,
       inputErrorMessage: '拒绝原因不能为空',
     }).then(async ({ value }) => {
-      const { error } = await api.reject(row.id, { reason: value });
+      const { error } = await api.withdraw.reject(row.id, { reason: value });
       if (error === 0) {
         ElMessage.success('拒绝成功');
         getData();
       }
     });
   }
-  
+
   async function batchHandle(type) {
     let ids = [];
     table.selected.forEach((row) => {
@@ -275,7 +269,7 @@
           cancelButtonText: '取消',
           type: 'warning',
         }).then(async () => {
-          const { error } = await api.batchApprove(ids.join(','));
+          const { error } = await api.withdraw.batchApprove(ids.join(','));
           if (error === 0) {
             ElMessage.success('批量通过成功');
             getData();
@@ -289,7 +283,7 @@
           inputPattern: /.+/,
           inputErrorMessage: '拒绝原因不能为空',
         }).then(async ({ value }) => {
-          const { error } = await api.batchReject(ids.join(','), { reason: value });
+          const { error } = await api.withdraw.batchReject(ids.join(','), { reason: value });
           if (error === 0) {
             ElMessage.success('批量拒绝成功');
             getData();

+ 0 - 37
src/app/shop/admin/finance/withdraw/withdraw.service.js

@@ -1,37 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'withdraw',
-  name: 'shop.admin.finance.withdraw',
-  component: () => import('@/app/shop/admin/finance/withdraw/index.vue'),
-  meta: {
-    title: '提款',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/withdraw'),
-  select: (params) => SELECT('shop/admin/withdraw', params),
-  approve: (id) => ({
-    error: 0,
-    msg: '通过成功',
-    data: null,
-  }),
-  reject: (id, data) => ({
-    error: 0,
-    msg: '拒绝成功',
-    data: null,
-  }),
-  batchApprove: (ids) => ({
-    error: 0,
-    msg: '批量通过成功',
-    data: null,
-  }),
-  batchReject: (ids, data) => ({
-    error: 0,
-    msg: '批量拒绝成功',
-    data: null,
-  }),
-};
-
-export { route, api };

+ 4 - 4
src/app/shop/admin/category/edit.vue → src/app/shop/admin/goods/category/edit.vue

@@ -19,7 +19,7 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from './category.service';
+  import { api } from '../goods.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {
@@ -42,7 +42,7 @@
   // 获取详情
   async function getDetail(id) {
     loading.value = true;
-    const { error, data } = await api.detail(id);
+    const { error, data } = await api.category.detail(id);
     error === 0 && (form.model = data);
     loading.value = false;
   }
@@ -53,8 +53,8 @@
       let submitForm = cloneDeep(form.model);
       const { error } =
         props.modal.params.type == 'add'
-          ? await api.add(submitForm)
-          : await api.edit(props.modal.params.id, submitForm);
+          ? await api.category.add(submitForm)
+          : await api.category.edit(props.modal.params.id, submitForm);
       if (error == 0) {
         emit('modalCallBack', { event: 'confirm' });
       }

+ 3 - 3
src/app/shop/admin/category/index.vue → src/app/shop/admin/goods/category/index.vue

@@ -89,7 +89,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './category.service';
+  import { api } from '../goods.service';
   import { ElMessageBox } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';
@@ -121,7 +121,7 @@
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.list({
+    const { error, data } = await api.category.list({
       page: pageData.page,
       list_rows: pageData.list_rows,
       order: table.order,
@@ -184,7 +184,7 @@
   }
   // 删除api 单独批量可以直接调用
   async function deleteApi(id) {
-    await api.delete(id);
+    await api.category.delete(id);
     getData();
   }
   async function batchHandle(type) {

+ 129 - 0
src/app/shop/admin/goods/category/select.vue

@@ -0,0 +1,129 @@
+<template>
+  <el-container class="category-select-view">
+    <el-header class="sa-header">
+      <div class="sa-title sa-flex sa-row-between">
+        <div class="label sa-flex">选择分类</div>
+        <div>
+          <el-button @click="handleCancel">取消</el-button>
+          <el-button type="primary" @click="handleConfirm">确定</el-button>
+        </div>
+      </div>
+    </el-header>
+    <el-main class="sa-p-0">
+      <div class="category-list">
+        <el-tree
+          ref="treeRef"
+          :data="categoryList"
+          :props="treeProps"
+          node-key="id"
+          :default-checked-keys="selectedIds"
+          show-checkbox
+          :check-strictly="checkStrictly"
+          @check="handleCheck"
+        />
+      </div>
+    </el-main>
+  </el-container>
+</template>
+
+<script>
+  export default {
+    name: 'CategorySelect',
+  };
+</script>
+
+<script setup>
+  import { ref, reactive, onMounted } from 'vue';
+  import { api } from '../goods.service';
+
+  const emit = defineEmits(['modalCallBack']);
+  const props = defineProps({
+    modal: {
+      type: Object,
+      default: () => ({}),
+    },
+    multiple: {
+      type: Boolean,
+      default: true,
+    },
+    checkStrictly: {
+      type: Boolean,
+      default: false,
+    },
+  });
+
+  const treeRef = ref();
+  const categoryList = ref([]);
+  const selectedIds = ref([]);
+
+  const treeProps = {
+    children: 'children',
+    label: 'name',
+    value: 'id',
+  };
+
+  // 获取分类数据
+  async function getData() {
+    try {
+      const { data, error } = await api.category.list();
+      if (error === 0) {
+        categoryList.value = data || [];
+      }
+    } catch (error) {
+      console.error('获取分类数据失败:', error);
+    }
+  }
+
+  // 处理选择
+  function handleCheck(data, checked) {
+    if (props.multiple) {
+      selectedIds.value = checked.checkedKeys;
+    } else {
+      selectedIds.value = [data.id];
+      // 单选模式下,取消其他选择
+      treeRef.value.setCheckedKeys([data.id]);
+    }
+  }
+
+  // 确认选择
+  function handleConfirm() {
+    const checkedNodes = treeRef.value.getCheckedNodes();
+    const selectedCategories = checkedNodes.map((node) => ({
+      id: node.id,
+      name: node.name,
+    }));
+
+    emit('modalCallBack', {
+      type: 'confirm',
+      data: props.multiple ? selectedCategories : selectedCategories[0] || null,
+    });
+  }
+
+  // 取消选择
+  function handleCancel() {
+    emit('modalCallBack', {
+      type: 'cancel',
+    });
+  }
+
+  onMounted(() => {
+    getData();
+
+    // 设置默认选中
+    if (props.modal.selectedIds) {
+      selectedIds.value = Array.isArray(props.modal.selectedIds)
+        ? props.modal.selectedIds
+        : [props.modal.selectedIds];
+    }
+  });
+</script>
+
+<style lang="scss" scoped>
+  .category-select-view {
+    .category-list {
+      padding: 20px;
+      height: 400px;
+      overflow-y: auto;
+    }
+  }
+</style>

+ 12 - 0
src/app/shop/admin/goods/goods.service.js

@@ -18,6 +18,14 @@ const route = {
         title: '商品库',
       },
     },
+    {
+      path: 'category',
+      name: 'shop.admin.goods.category',
+      component: () => import('./category/index.vue'),
+      meta: {
+        title: '商品分类',
+      },
+    },
   ],
 };
 
@@ -41,6 +49,10 @@ const api = {
         method: 'GET',
       }),
   },
+  category: {
+    ...CRUD('shop/admin/category_tag'),
+    select: (params) => SELECT('shop/admin/category_tag', params),
+  },
 };
 
 export { route, api };

+ 2 - 5
src/app/shop/admin/goods/goods/edit.vue

@@ -900,13 +900,10 @@
   // 获取分类数据
   const getCategoryData = async () => {
     try {
-      const response = await api.goods.getCategory();
+      const response = await api.goods.getType();
       if (response.error === 0) {
         // 只使用一级分类
-        categoryOptions.value = response.data.map((item) => ({
-          id: item.id,
-          name: item.name,
-        }));
+        categoryOptions.value = response.data.categories;
       }
     } catch (error) {
       console.error('获取分类失败:', error);

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

@@ -136,7 +136,7 @@
    */
   import { nextTick, onMounted, reactive, ref, watch } from 'vue';
   import { api } from '../goods.service';
-  import { api as categoryApi } from '@/app/shop/admin/category/category.service';
+  import { api as categoryApi } from '@/app/shop/admin/goods/category/category.service';
   import { ElMessage } from 'element-plus';
   import { composeFilter } from '@/sheep/utils';
   import { useDebounceFn } from '@vueuse/core';

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

@@ -9,22 +9,22 @@
           <el-input v-model="form.model.goods_id" placeholder="请填写商品ID"></el-input>
         </el-form-item>
         <el-form-item label="拼团人数" prop="people_num">
-          <el-input-number 
-            v-model="form.model.people_num" 
-            :min="2" 
+          <el-input-number
+            v-model="form.model.people_num"
+            :min="2"
             :max="100"
             placeholder="拼团人数"
           />
-          <span style="margin-left: 10px;">人</span>
+          <span style="margin-left: 10px">人</span>
         </el-form-item>
         <el-form-item label="拼团价格" prop="group_price">
-          <el-input-number 
-            v-model="form.model.group_price" 
-            :min="0" 
+          <el-input-number
+            v-model="form.model.group_price"
+            :min="0"
             :precision="2"
             placeholder="拼团价格"
           />
-          <span style="margin-left: 10px;">৳</span>
+          <span style="margin-left: 10px">৳</span>
         </el-form-item>
         <el-form-item label="开始时间" prop="start_time">
           <el-date-picker
@@ -51,9 +51,9 @@
           </el-select>
         </el-form-item>
         <el-form-item label="活动描述" prop="description">
-          <el-input 
-            v-model="form.model.description" 
-            type="textarea" 
+          <el-input
+            v-model="form.model.description"
+            type="textarea"
             :rows="3"
             placeholder="请填写活动描述"
           ></el-input>
@@ -69,7 +69,7 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from './group.service';
+  import { api } from '../marketing.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {
@@ -103,7 +103,7 @@
   // 获取详情
   async function getDetail(id) {
     loading.value = true;
-    const { error, data } = await api.detail(id);
+    const { error, data } = await api.group.detail(id);
     error === 0 && (form.model = data);
     loading.value = false;
   }
@@ -114,8 +114,8 @@
       let submitForm = cloneDeep(form.model);
       const { error } =
         props.modal.params.type == 'add'
-          ? await api.add(submitForm)
-          : await api.edit(props.modal.params.id, submitForm);
+          ? await api.group.add(submitForm)
+          : await api.group.edit(props.modal.params.id, submitForm);
       if (error == 0) {
         emit('modalCallBack', { event: 'confirm' });
       }

+ 0 - 17
src/app/shop/admin/marketing/group/group.service.js

@@ -1,17 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'group',
-  name: 'shop.admin.marketing.group',
-  component: () => import('@/app/shop/admin/marketing/group/index.vue'),
-  meta: {
-    title: '拼团',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/group_buy'),
-  select: (params) => SELECT('shop/admin/group_buy', params),
-};
-
-export { route, api };

+ 6 - 10
src/app/shop/admin/marketing/group/index.vue

@@ -52,14 +52,10 @@
             </template>
           </el-table-column>
           <el-table-column label="拼团人数" min-width="100">
-            <template #default="scope">
-              {{ scope.row.people_num || '-' }}人
-            </template>
+            <template #default="scope"> {{ scope.row.people_num || '-' }}人 </template>
           </el-table-column>
           <el-table-column label="拼团价格" min-width="120">
-            <template #default="scope">
-              ৳{{ scope.row.group_price || '-' }}
-            </template>
+            <template #default="scope"> ৳{{ scope.row.group_price || '-' }} </template>
           </el-table-column>
           <el-table-column label="状态" min-width="100">
             <template #default="scope">
@@ -113,7 +109,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './group.service';
+  import { api } from '../marketing.service';
   import { ElMessageBox } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';
@@ -145,7 +141,7 @@
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.list({
+    const { error, data } = await api.group.list({
       page: pageData.page,
       list_rows: pageData.list_rows,
       order: table.order,
@@ -208,7 +204,7 @@
   }
   // 删除api 单独批量可以直接调用
   async function deleteApi(id) {
-    await api.delete(id);
+    await api.group.delete(id);
     getData();
   }
   async function batchHandle(type) {
@@ -227,7 +223,7 @@
         });
         break;
       default:
-        await api.edit(ids.join(','), {
+        await api.group.edit(ids.join(','), {
           status: type,
         });
         getData();

+ 31 - 0
src/app/shop/admin/marketing/marketing.service.js

@@ -0,0 +1,31 @@
+import Content from '@/sheep/layouts/content.vue';
+import { SELECT, CRUD } from '@/sheep/request/crud';
+
+const route = {
+  path: 'marketing',
+  name: 'shop.admin.marketing',
+  component: Content,
+  meta: {
+    title: '营销',
+  },
+  children: [
+    {
+      path: 'group',
+      name: 'shop.admin.marketing.group',
+      component: () => import('./group/index.vue'),
+      meta: {
+        title: '拼团',
+      },
+    },
+  ],
+};
+
+const api = {
+  // 拼团相关 API
+  group: {
+    ...CRUD('shop/admin/group_buy'),
+    select: (params) => SELECT('shop/admin/group_buy', params),
+  },
+};
+
+export { route, api };

+ 0 - 1
src/app/shop/admin/order/aftersale/index.vue

@@ -103,7 +103,6 @@
               <div class="order-sn sa-flex">
                 订单号:{{ scope.row.order_sn }}
                 <el-button
-                  v-auth="'shop.admin.order.order.detail'"
                   class="is-link sa-m-l-4"
                   type="primary"
                   size="small"

+ 15 - 3
src/app/shop/admin/order/order.service.js

@@ -1,6 +1,6 @@
 import Content from '@/sheep/layouts/content.vue';
 import { request } from '@/sheep/request';
-import { CRUD } from '@/sheep/request/crud';
+import { SELECT, CRUD } from '@/sheep/request/crud';
 import { EXPORT } from '@/sheep/request/export';
 
 const route = {
@@ -12,8 +12,8 @@ const route = {
   },
   children: [
     {
-      path: 'order',
-      name: 'shop.admin.order.order',
+      path: 'list',
+      name: 'shop.admin.order.list',
       component: () => import('@/app/shop/admin/order/order/index.vue'),
       meta: {
         title: '订单管理',
@@ -35,6 +35,14 @@ const route = {
         title: '订单发票',
       },
     },
+    {
+      path: 'setting',
+      name: 'shop.admin.order.setting',
+      component: () => import('@/app/shop/admin/order/setting/index.vue'),
+      meta: {
+        title: '订单设置',
+      },
+    },
   ],
 };
 
@@ -186,6 +194,10 @@ const api = {
         data,
       }),
   },
+  setting: {
+    ...CRUD('shop/admin/order/setting'),
+    select: (params) => SELECT('shop/admin/order/setting', params),
+  },
 };
 
 export { route, api };

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

@@ -342,6 +342,7 @@
 
 <style lang="scss" scoped>
   .order-detail {
+    padding: 20px;
     .order-track {
       h3 {
         font-size: 16px;

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

@@ -0,0 +1,1251 @@
+<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>

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

@@ -0,0 +1,1207 @@
+<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>

+ 23 - 16
src/app/shop/admin/order/setting/edit.vue

@@ -6,10 +6,17 @@
           <el-input v-model="form.model.name" placeholder="请填写设置名称"></el-input>
         </el-form-item>
         <el-form-item label="设置键" prop="key">
-          <el-input v-model="form.model.key" placeholder="请填写设置键,如:order_timeout"></el-input>
+          <el-input
+            v-model="form.model.key"
+            placeholder="请填写设置键,如:order_timeout"
+          ></el-input>
         </el-form-item>
         <el-form-item label="数据类型" prop="type">
-          <el-select v-model="form.model.type" placeholder="请选择数据类型" @change="handleTypeChange">
+          <el-select
+            v-model="form.model.type"
+            placeholder="请选择数据类型"
+            @change="handleTypeChange"
+          >
             <el-option label="字符串" value="string"></el-option>
             <el-option label="数字" value="number"></el-option>
             <el-option label="布尔值" value="boolean"></el-option>
@@ -17,37 +24,37 @@
           </el-select>
         </el-form-item>
         <el-form-item label="设置值" prop="value">
-          <el-input 
+          <el-input
             v-if="form.model.type === 'string'"
-            v-model="form.model.value" 
+            v-model="form.model.value"
             placeholder="请填写设置值"
           ></el-input>
-          <el-input-number 
+          <el-input-number
             v-else-if="form.model.type === 'number'"
-            v-model="form.model.value" 
+            v-model="form.model.value"
             placeholder="请填写数字"
             style="width: 100%"
           />
-          <el-select 
+          <el-select
             v-else-if="form.model.type === 'boolean'"
-            v-model="form.model.value" 
+            v-model="form.model.value"
             placeholder="请选择布尔值"
           >
             <el-option label="是" value="true"></el-option>
             <el-option label="否" value="false"></el-option>
           </el-select>
-          <el-input 
+          <el-input
             v-else-if="form.model.type === 'json'"
-            v-model="form.model.value" 
+            v-model="form.model.value"
             type="textarea"
             :rows="4"
             placeholder="请填写JSON格式数据"
           ></el-input>
         </el-form-item>
         <el-form-item label="描述" prop="description">
-          <el-input 
-            v-model="form.model.description" 
-            type="textarea" 
+          <el-input
+            v-model="form.model.description"
+            type="textarea"
             :rows="3"
             placeholder="请填写设置描述"
           ></el-input>
@@ -72,7 +79,7 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from './setting.service';
+  import { api } from '../order.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {
@@ -101,7 +108,7 @@
     },
   });
   const loading = ref(false);
-  
+
   // 处理类型变化
   function handleTypeChange(type) {
     // 重置值
@@ -113,7 +120,7 @@
       form.model.value = '';
     }
   }
-  
+
   // 获取详情
   async function getDetail(id) {
     loading.value = true;

+ 1 - 1
src/app/shop/admin/order/setting/index.vue

@@ -109,7 +109,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './setting.service';
+  import { api } from '../order.service';
   import { ElMessageBox } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';

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

@@ -1,17 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'setting',
-  name: 'shop.admin.order.setting',
-  component: () => import('@/app/shop/admin/order/setting/index.vue'),
-  meta: {
-    title: '订单设置',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/order_setting'),
-  select: (params) => SELECT('shop/admin/order_setting', params),
-};
-
-export { route, api };

+ 0 - 74
src/app/shop/admin/user/coupon.vue

@@ -1,74 +0,0 @@
-<template>
-  <el-table class="sa-table" :data="log.data" height="520" v-loading="log.loading">
-    <el-table-column label="优惠券名称" min-width="160" align="center">
-      <template #default="scope">
-        <div class="sa-line-1">{{ scope.row.coupon?.name }}</div>
-      </template>
-    </el-table-column>
-    <el-table-column label="优惠券面额" min-width="120" align="center">
-      <template #default="scope">
-        <div class="sa-line-1">{{ scope.row.coupon?.amount }}</div>
-      </template>
-    </el-table-column>
-    <el-table-column prop="status_text" label="优惠券状态" min-width="120" align="center" />
-    <el-table-column prop="create_time" label="领取时间" min-width="172" align="center" />
-    <el-table-column prop="use_time" label="使用时间" min-width="172" align="center" />
-  </el-table>
-  <sa-view-bar>
-    <template #right>
-      <sa-pagination :pageData="pageData" @updateFn="getData" />
-    </template>
-  </sa-view-bar>
-</template>
-
-<script setup>
-  import { onMounted, reactive, watch } from 'vue';
-  import { api } from './user.service.js';
-  import { usePagination } from '@/sheep/hooks';
-
-  const emit = defineEmits(['updateRefresh']);
-  const props = defineProps(['id', 'refresh']);
-
-  const log = reactive({
-    data: [],
-    loading: false,
-  });
-
-  const { pageData } = usePagination();
-
-  async function getData() {
-    log.loading = true;
-    const { error, data } = await api.coupon(props.id, {
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-    });
-    if (error === 0) {
-      log.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
-    }
-    log.loading = false;
-    emit('updateRefresh', false);
-  }
-
-  function onChangeLogTabName() {
-    pageData.page = 1;
-    pageData.list_rows = 10;
-    pageData.total = 0;
-    getData();
-  }
-
-  watch(
-    () => props.refresh,
-    () => {
-      if (props.refresh) {
-        onChangeLogTabName();
-      }
-    },
-  );
-
-  onMounted(() => {
-    onChangeLogTabName();
-  });
-</script>

+ 0 - 190
src/app/shop/admin/user/couponList.vue

@@ -1,190 +0,0 @@
-<template>
-  <el-container class="coupon-view panel-block">
-    <el-header class="sa-header">
-      <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 class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
-          <el-button class="sa-button-refresh" icon="Search" @click="openFilter"></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" stripe>
-        <template #empty>
-          <sa-empty />
-        </template>
-        <el-table-column prop="id" label="ID" min-width="90" sortable="custom"> </el-table-column>
-        <el-table-column label="用户信息" min-width="150">
-          <template #default="scope">
-            <sa-user-profile :user="scope.row.user" :id="scope.row.user_id"></sa-user-profile>
-          </template>
-        </el-table-column>
-        <el-table-column prop="status_text" label="使用状态" min-width="120">
-          <template #default="scope">
-            <div :class="`sa-color--${statusClass[scope.row.status]}`">
-              {{ scope.row.status_text || '-' }}
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="有效期" min-width="320">
-          <template #default="scope">
-            {{ scope.row.use_start_time }}~{{ scope.row.use_end_time }}
-          </template>
-        </el-table-column>
-        <el-table-column prop="use_time" label="使用时间" min-width="172">
-          <template #default="scope">
-            {{ scope.row.use_time || '-' }}
-          </template>
-        </el-table-column>
-        <el-table-column prop="order_sn" label="订单号" min-width="280">
-          <template #default="scope">
-            {{ scope.row.order?.order_sn || '-' }}
-          </template>
-        </el-table-column>
-        <el-table-column prop="create_time" label="领取时间" min-width="172"></el-table-column>
-      </el-table>
-    </el-main>
-    <sa-view-bar>
-      <template #right>
-        <sa-pagination :pageData="pageData" @updateFn="getData" />
-      </template>
-    </sa-view-bar>
-  </el-container>
-</template>
-<script setup>
-import { onMounted, reactive, ref } from 'vue';
-import { api } from './user.service.js';
-import { usePagination } from '@/sheep/hooks';
-import { useSearch } from '@/sheep/components/sa-table/sa-search/useSearch';
-import { composeFilter } from '@/sheep/utils';
-
-import { cloneDeep } from 'lodash';
-import { useRoute } from 'vue-router';
-
-const route = useRoute();
-
-const filterParams = reactive({
-  tools: {
-    user: {
-      type: 'tinputprepend',
-      field: 'user',
-      label: '用户信息',
-      placeholder: '请输入查询内容',
-      value: {
-        field: 'user_id',
-        value: '',
-      },
-      options: [{
-        label: '用户ID',
-        value: 'user_id',
-      },
-      {
-        label: '用户昵称',
-        value: 'user.nickname',
-      },
-      {
-        label: '用户手机号',
-        value: 'user.mobile',
-      }]
-    },
-    'order.order_sn': {
-      type: 'tinput',
-      field: 'order.order_sn',
-      label: '订单号',
-      placeholder: '请输入订单号',
-      value: '',
-    },
-    status: {
-      type: 'tselect',
-      field: 'status',
-      label: '状态',
-      value: '',
-      options: {
-        data: [{
-          label: '未使用',
-          value: 'geted',
-        },
-        {
-          label: '已使用',
-          value: 'used',
-        },
-        {
-          label: '已过期',
-          value: 'expired',
-        }],
-      },
-    },
-    create_time: {
-      type: 'tdatetimerange',
-      field: 'create_time',
-      label: '领取时间',
-      value: [],
-    },
-    use_time: {
-      type: 'tdatetimerange',
-      field: 'use_time',
-      label: '使用时间',
-      value: [],
-    },
-  },
-  data: {
-    user: { field: 'user_id', value: '' },
-    'order.order_sn': '',
-    status: '',
-    create_time: [],
-    use_time: [],
-  },
-  conditionLabel: {},
-});
-const { openFilter, deleteFilter } = useSearch({ filterParams, getData });
-
-
-const statusClass = {
-  geted: 'info',
-  used: 'success',
-  expired: 'error',
-}
-
-const loading = ref(true);
-
-const table = reactive({
-  data: [],
-  order: '',
-  sort: '',
-  selected: [],
-});
-
-const { pageData } = usePagination();
-
-// 获取数据
-async function getData(page) {
-  loading.value = true;
-  if (page) pageData.page = page;
-  let tempSearch = cloneDeep(filterParams.data);
-  let search = composeFilter(tempSearch, {
-    'user.nickname': 'like',
-    'user.mobile': 'like',
-    create_time: 'range',
-    use_time: 'range',
-  });
-  const { data } = await api.couponList(route.query.couponid, {
-    page: pageData.page,
-    list_rows: pageData.list_rows,
-    ...search,
-  });
-  table.data = data.data;
-  pageData.page = data.current_page;
-  pageData.list_rows = data.per_page;
-  pageData.total = data.total;
-
-  loading.value = false;
-}
-
-onMounted(() => {
-  getData();
-});
-</script>

+ 13 - 18
src/app/shop/admin/user/level/edit.vue

@@ -9,27 +9,22 @@
           <el-input v-model="form.model.icon" placeholder="请填写图标地址"></el-input>
         </el-form-item>
         <el-form-item label="升级条件" prop="upgrade_amount">
-          <el-input-number 
-            v-model="form.model.upgrade_amount" 
-            :min="0" 
+          <el-input-number
+            v-model="form.model.upgrade_amount"
+            :min="0"
             :precision="2"
             placeholder="消费金额"
           />
-          <span style="margin-left: 10px;">元</span>
+          <span style="margin-left: 10px">元</span>
         </el-form-item>
         <el-form-item label="折扣率" prop="discount">
-          <el-input-number 
-            v-model="form.model.discount" 
-            :min="1" 
-            :max="100"
-            placeholder="折扣率"
-          />
-          <span style="margin-left: 10px;">%</span>
+          <el-input-number v-model="form.model.discount" :min="1" :max="100" placeholder="折扣率" />
+          <span style="margin-left: 10px">%</span>
         </el-form-item>
         <el-form-item label="描述" prop="description">
-          <el-input 
-            v-model="form.model.description" 
-            type="textarea" 
+          <el-input
+            v-model="form.model.description"
+            type="textarea"
             :rows="3"
             placeholder="请填写等级描述"
           ></el-input>
@@ -48,7 +43,7 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from './level.service';
+  import { api } from '../user.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {
@@ -77,7 +72,7 @@
   // 获取详情
   async function getDetail(id) {
     loading.value = true;
-    const { error, data } = await api.detail(id);
+    const { error, data } = await api.level.detail(id);
     error === 0 && (form.model = data);
     loading.value = false;
   }
@@ -88,8 +83,8 @@
       let submitForm = cloneDeep(form.model);
       const { error } =
         props.modal.params.type == 'add'
-          ? await api.add(submitForm)
-          : await api.edit(props.modal.params.id, submitForm);
+          ? await api.level.add(submitForm)
+          : await api.level.edit(props.modal.params.id, submitForm);
       if (error == 0) {
         emit('modalCallBack', { event: 'confirm' });
       }

+ 8 - 12
src/app/shop/admin/user/level/index.vue

@@ -46,9 +46,9 @@
           </el-table-column>
           <el-table-column label="等级图标" min-width="100">
             <template #default="scope">
-              <el-image 
+              <el-image
                 v-if="scope.row.icon"
-                :src="scope.row.icon" 
+                :src="scope.row.icon"
                 style="width: 40px; height: 40px"
                 fit="cover"
               />
@@ -57,15 +57,11 @@
           </el-table-column>
           <el-table-column label="升级条件" min-width="150">
             <template #default="scope">
-              <span class="sa-table-line-1">
-                消费满 {{ scope.row.upgrade_amount || 0 }} 元
-              </span>
+              <span class="sa-table-line-1"> 消费满 {{ scope.row.upgrade_amount || 0 }} 元 </span>
             </template>
           </el-table-column>
           <el-table-column label="折扣率" min-width="100">
-            <template #default="scope">
-              {{ scope.row.discount || 100 }}%
-            </template>
+            <template #default="scope"> {{ scope.row.discount || 100 }}% </template>
           </el-table-column>
           <el-table-column label="排序" min-width="100">
             <template #default="scope">
@@ -112,7 +108,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './level.service';
+  import { api } from '../user.service';
   import { ElMessageBox } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';
@@ -144,7 +140,7 @@
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.list({
+    const { error, data } = await api.level.list({
       page: pageData.page,
       list_rows: pageData.list_rows,
       order: table.order,
@@ -207,7 +203,7 @@
   }
   // 删除api 单独批量可以直接调用
   async function deleteApi(id) {
-    await api.delete(id);
+    await api.level.delete(id);
     getData();
   }
   async function batchHandle(type) {
@@ -226,7 +222,7 @@
         });
         break;
       default:
-        await api.edit(ids.join(','), {
+        await api.level.edit(ids.join(','), {
           status: type,
         });
         getData();

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

@@ -1,17 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'level',
-  name: 'shop.admin.user.level',
-  component: () => import('@/app/shop/admin/user/level/index.vue'),
-  meta: {
-    title: '会员等级',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/user_level'),
-  select: (params) => SELECT('shop/admin/user_level', params),
-};
-
-export { route, api };

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

@@ -31,7 +31,7 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from './list.service';
+  import { api } from '../user.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {
@@ -60,7 +60,7 @@
   // 获取详情
   async function getDetail(id) {
     loading.value = true;
-    const { error, data } = await api.detail(id);
+    const { error, data } = await api.list.detail(id);
     error === 0 && (form.model = data);
     loading.value = false;
   }
@@ -71,8 +71,8 @@
       let submitForm = cloneDeep(form.model);
       const { error } =
         props.modal.params.type == 'add'
-          ? await api.add(submitForm)
-          : await api.edit(props.modal.params.id, submitForm);
+          ? await api.list.add(submitForm)
+          : await api.list.edit(props.modal.params.id, submitForm);
       if (error == 0) {
         emit('modalCallBack', { event: 'confirm' });
       }

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

@@ -108,7 +108,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './list.service';
+  import { api } from '../user.service';
   import { ElMessageBox } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';
@@ -147,7 +147,7 @@
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.list({
+    const { error, data } = await api.list.list({
       page: pageData.page,
       list_rows: pageData.list_rows,
       order: table.order,
@@ -210,7 +210,7 @@
   }
   // 删除api 单独批量可以直接调用
   async function deleteApi(id) {
-    await api.delete(id);
+    await api.list.delete(id);
     getData();
   }
   async function batchHandle(type) {
@@ -229,7 +229,7 @@
         });
         break;
       default:
-        await api.edit(ids.join(','), {
+        await api.list.edit(ids.join(','), {
           status: type,
         });
         getData();

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

@@ -1,17 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'list',
-  name: 'shop.admin.user.list',
-  component: () => import('@/app/shop/admin/user/list/index.vue'),
-  meta: {
-    title: '用户列表',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/user'),
-  select: (params) => SELECT('shop/admin/user', params),
-};
-
-export { route, api };

+ 0 - 70
src/app/shop/admin/user/order.vue

@@ -1,70 +0,0 @@
-<template>
-  <el-table class="sa-table" :data="log.data" height="520" v-loading="log.loading">
-    <el-table-column prop="create_time" label="下单时间" min-width="172" align="center" />
-    <el-table-column prop="order_sn" label="订单号" min-width="280" align="center" />
-    <el-table-column prop="platform_text" label="订单来源" min-width="120" align="center" />
-    <el-table-column prop="type_text" label="订单类型" min-width="100" align="center" />
-    <el-table-column prop="order_amount" label="订单总金额" min-width="140" align="center" />
-    <el-table-column prop="total_discount_fee" label="优惠减免" min-width="140" align="center" />
-    <el-table-column prop="pay_fee" label="实付金额" min-width="140" align="center" />
-    <el-table-column prop="status_text" label="订单状态" min-width="100" align="center" />
-  </el-table>
-  <sa-view-bar>
-    <template #right>
-      <sa-pagination :pageData="pageData" @updateFn="getData" />
-    </template>
-  </sa-view-bar>
-</template>
-
-<script setup>
-  import { onMounted, reactive, watch } from 'vue';
-  import { api } from '@/app/shop/admin/order/order.service.js';
-  import { usePagination } from '@/sheep/hooks';
-
-  const emit = defineEmits(['updateRefresh']);
-  const props = defineProps(['id', 'refresh']);
-
-  const log = reactive({
-    data: [],
-    loading: false,
-  });
-
-  const { pageData } = usePagination();
-
-  async function getData() {
-    log.loading = true;
-    const { error, data } = await api.order.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      search: JSON.stringify({ user_id: [props.id] }),
-    });
-    if (error === 0) {
-      log.data = data.orders.data;
-      pageData.page = data.orders.current_page;
-      pageData.list_rows = data.orders.per_page;
-      pageData.total = data.orders.total;
-    }
-    log.loading = false;
-    emit('updateRefresh', false);
-  }
-
-  function onChangeLogTabName() {
-    pageData.page = 1;
-    pageData.list_rows = 10;
-    pageData.total = 0;
-    getData();
-  }
-
-  watch(
-    () => props.refresh,
-    () => {
-      if (props.refresh) {
-        onChangeLogTabName();
-      }
-    },
-  );
-
-  onMounted(() => {
-    onChangeLogTabName();
-  });
-</script>

+ 0 - 78
src/app/shop/admin/user/share.vue

@@ -1,78 +0,0 @@
-<template>
-  <el-table class="sa-table" :data="log.data" height="520" v-loading="log.loading">
-    <el-table-column prop="create_time" label="分享时间" min-width="172" align="center" />
-    <el-table-column label="被分享用户" min-width="180">
-      <template #default="scope">
-        <sa-user-profile :user="scope.row.user" :id="scope.user_id" />
-      </template>
-    </el-table-column>
-    <!-- TODO: 分享类型 -->
-    <el-table-column prop="from_text" label="分享类型" min-width="120" align="center" />
-    <el-table-column label="分享信息" min-width="180" show-overflow-tooltip>
-      <template #default="scope">
-        <div class="sa-flex">
-          <sa-image :url="scope.row.ext?.image" size="32" />
-          <div class="sa-line-1 sa-m-l-8">{{ scope.row.ext?.memo }}</div>
-        </div>
-      </template>
-    </el-table-column>
-    <el-table-column prop="platform_text" label="平台" min-width="120" align="center" />
-  </el-table>
-  <sa-view-bar>
-    <template #right>
-      <sa-pagination :pageData="pageData" @updateFn="getData" />
-    </template>
-  </sa-view-bar>
-</template>
-
-<script setup>
-  import { onMounted, reactive, watch } from 'vue';
-  import { api } from './user.service.js';
-  import { usePagination } from '@/sheep/hooks';
-
-  const emit = defineEmits(['updateRefresh']);
-  const props = defineProps(['id', 'refresh']);
-
-  const log = reactive({
-    data: [],
-    loading: false,
-  });
-
-  const { pageData } = usePagination();
-
-  async function getData() {
-    log.loading = true;
-    const { error, data } = await api.share(props.id, {
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-    });
-    if (error === 0) {
-      log.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
-    }
-    log.loading = false;
-    emit('updateRefresh', false);
-  }
-
-  function onChangeLogTabName() {
-    pageData.page = 1;
-    pageData.list_rows = 10;
-    pageData.total = 0;
-    getData();
-  }
-
-  watch(
-    () => props.refresh,
-    () => {
-      if (props.refresh) {
-        onChangeLogTabName();
-      }
-    },
-  );
-
-  onMounted(() => {
-    onChangeLogTabName();
-  });
-</script>

+ 11 - 8
src/app/shop/admin/user/tag/edit.vue

@@ -6,12 +6,15 @@
           <el-input v-model="form.model.name" placeholder="请填写标签名称"></el-input>
         </el-form-item>
         <el-form-item label="标签颜色" prop="color">
-          <el-color-picker v-model="form.model.color" placeholder="请选择标签颜色"></el-color-picker>
+          <el-color-picker
+            v-model="form.model.color"
+            placeholder="请选择标签颜色"
+          ></el-color-picker>
         </el-form-item>
         <el-form-item label="描述" prop="description">
-          <el-input 
-            v-model="form.model.description" 
-            type="textarea" 
+          <el-input
+            v-model="form.model.description"
+            type="textarea"
             :rows="3"
             placeholder="请填写标签描述"
           ></el-input>
@@ -30,7 +33,7 @@
 <script setup>
   import { cloneDeep } from 'lodash';
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from './tag.service';
+  import { api } from '../user.service';
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
     modal: {
@@ -56,7 +59,7 @@
   // 获取详情
   async function getDetail(id) {
     loading.value = true;
-    const { error, data } = await api.detail(id);
+    const { error, data } = await api.tag.detail(id);
     error === 0 && (form.model = data);
     loading.value = false;
   }
@@ -67,8 +70,8 @@
       let submitForm = cloneDeep(form.model);
       const { error } =
         props.modal.params.type == 'add'
-          ? await api.add(submitForm)
-          : await api.edit(props.modal.params.id, submitForm);
+          ? await api.tag.add(submitForm)
+          : await api.tag.edit(props.modal.params.id, submitForm);
       if (error == 0) {
         emit('modalCallBack', { event: 'confirm' });
       }

+ 4 - 4
src/app/shop/admin/user/tag/index.vue

@@ -103,7 +103,7 @@
 </template>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
-  import { api } from './tag.service';
+  import { api } from '../user.service';
   import { ElMessageBox } from 'element-plus';
   import { useModal } from '@/sheep/hooks';
   import { usePagination } from '@/sheep/hooks';
@@ -135,7 +135,7 @@
   async function getData(page, searchParams = {}) {
     if (page) pageData.page = page;
     loading.value = true;
-    const { error, data } = await api.list({
+    const { error, data } = await api.tag.list({
       page: pageData.page,
       list_rows: pageData.list_rows,
       order: table.order,
@@ -198,7 +198,7 @@
   }
   // 删除api 单独批量可以直接调用
   async function deleteApi(id) {
-    await api.delete(id);
+    await api.tag.delete(id);
     getData();
   }
   async function batchHandle(type) {
@@ -217,7 +217,7 @@
         });
         break;
       default:
-        await api.edit(ids.join(','), {
+        await api.tag.edit(ids.join(','), {
           status: type,
         });
         getData();

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

@@ -1,17 +0,0 @@
-import { SELECT, CRUD } from '@/sheep/request/crud';
-
-const route = {
-  path: 'tag',
-  name: 'shop.admin.user.tag',
-  component: () => import('@/app/shop/admin/user/tag/index.vue'),
-  meta: {
-    title: '标签管理',
-  },
-};
-
-const api = {
-  ...CRUD('shop/admin/user_tag'),
-  select: (params) => SELECT('shop/admin/user_tag', params),
-};
-
-export { route, api };

+ 50 - 6
src/app/shop/admin/user/user.service.js

@@ -1,26 +1,70 @@
 import Content from '@/sheep/layouts/content.vue';
 import { request } from '@/sheep/request';
+import { CRUD } from '@/sheep/request/crud';
 
 const route = {
   path: 'user',
   name: 'shop.admin.user',
   component: Content,
   meta: {
-    title: '',
+    title: '用户',
   },
   children: [
     {
-      path: 'couponList',
-      name: 'shop.admin.user.couponList',
-      component: () => import('@/app/shop/admin/user/couponList.vue'),
+      path: 'list',
+      name: 'shop.admin.user.list',
+      component: () => import('./list/index.vue'),
       meta: {
-        title: '优惠券领取记录',
+        title: '用户列表',
+      },
+    },
+    {
+      path: 'level',
+      name: 'shop.admin.user.level',
+      component: () => import('./level/index.vue'),
+      meta: {
+        title: '会员等级',
+      },
+    },
+    {
+      path: 'tag',
+      name: 'shop.admin.user.tag',
+      component: () => import('./tag/index.vue'),
+      meta: {
+        title: '会员标签',
       },
     },
   ],
 };
 
 const api = {
+  // 用户列表相关 API
+  list: {
+    ...CRUD('shop/admin/user/list'),
+    export: (params) =>
+      request({
+        url: 'shop/admin/user/list/export',
+        method: 'GET',
+        params,
+      }),
+    statistics: () =>
+      request({
+        url: 'shop/admin/user/list/statistics',
+        method: 'GET',
+      }),
+  },
+
+  // 会员等级相关 API
+  level: {
+    ...CRUD('shop/admin/user/level'),
+  },
+
+  // 会员标签相关 API
+  tag: {
+    ...CRUD('shop/admin/user/tag'),
+  },
+
+  // 其他用户相关 API
   share: (id, params) =>
     request({
       url: `shop/admin/share/${id}`,
@@ -37,7 +81,7 @@ const api = {
     request({
       url: `shop/admin/user/coupon/couponList/${id}`,
       method: 'GET',
-      params
+      params,
     }),
 };
 

+ 0 - 75
src/app/user/admin/components/commission.vue

@@ -1,75 +0,0 @@
-<template>
-  <el-table class="sa-table" :data="log.data" height="520" v-loading="log.loading">
-    <el-table-column prop="create_time" label="交易时间" min-width="172" align="center" />
-    <el-table-column prop="amount" label="变动余额" min-width="120" align="center" />
-    <el-table-column prop="before" label="变更前" min-width="120" align="center" />
-    <el-table-column prop="after" label="剩余余额" min-width="120" align="center" />
-    <el-table-column label="操作人" min-width="160">
-      <template #default="scope">
-        <sa-user-profile type="oper" :user="scope.row.oper" :id="scope.row.oper_id" />
-      </template>
-    </el-table-column>
-    <el-table-column label="备注" min-width="160" align="center">
-      <template #default="scope">
-        {{ scope.row.event_text }}{{ scope.row.memo ? ':' + scope.row.memo : '' }}
-      </template>
-    </el-table-column>
-  </el-table>
-  <sa-view-bar>
-    <template #right>
-      <sa-pagination :pageData="pageData" @updateFn="getData" />
-    </template>
-  </sa-view-bar>
-</template>
-
-<script setup>
-  import { onMounted, reactive, watch } from 'vue';
-  import userApi from '@/app/user/api';
-  import { usePagination } from '@/sheep/hooks';
-
-  const emit = defineEmits(['updateRefresh']);
-  const props = defineProps(['id', 'refresh']);
-
-  const log = reactive({
-    data: [],
-    loading: false,
-  });
-
-  const { pageData } = usePagination();
-
-  async function getData() {
-    log.loading = true;
-    const { error, data } = await userApi.walletLog.commission(props.id, {
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-    });
-    if (error === 0) {
-      log.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
-    }
-    log.loading = false;
-    emit('updateRefresh', false);
-  }
-
-  function onChangeLogTabName() {
-    pageData.page = 1;
-    pageData.list_rows = 10;
-    pageData.total = 0;
-    getData();
-  }
-
-  watch(
-    () => props.refresh,
-    () => {
-      if (props.refresh) {
-        onChangeLogTabName();
-      }
-    },
-  );
-
-  onMounted(() => {
-    onChangeLogTabName();
-  });
-</script>

+ 0 - 75
src/app/user/admin/components/money.vue

@@ -1,75 +0,0 @@
-<template>
-  <el-table class="sa-table" :data="log.data" height="520" v-loading="log.loading">
-    <el-table-column prop="create_time" label="交易时间" min-width="172" align="center" />
-    <el-table-column prop="amount" label="变动余额" min-width="120" align="center" />
-    <el-table-column prop="before" label="变更前" min-width="120" align="center" />
-    <el-table-column prop="after" label="剩余余额" min-width="120" align="center" />
-    <el-table-column label="操作人" min-width="160">
-      <template #default="scope">
-        <sa-user-profile type="oper" :user="scope.row.oper" :id="scope.row.oper_id" />
-      </template>
-    </el-table-column>
-    <el-table-column label="备注" min-width="160" align="center">
-      <template #default="scope">
-        {{ scope.row.event_text }}{{ scope.row.memo ? ':' + scope.row.memo : '' }}
-      </template>
-    </el-table-column>
-  </el-table>
-  <sa-view-bar>
-    <template #right>
-      <sa-pagination :pageData="pageData" @updateFn="getData" />
-    </template>
-  </sa-view-bar>
-</template>
-
-<script setup>
-  import { onMounted, reactive, watch } from 'vue';
-  import userApi from '@/app/user/api';
-  import { usePagination } from '@/sheep/hooks';
-
-  const emit = defineEmits(['updateRefresh']);
-  const props = defineProps(['id', 'refresh']);
-
-  const log = reactive({
-    data: [],
-    loading: false,
-  });
-
-  const { pageData } = usePagination();
-
-  async function getData() {
-    log.loading = true;
-    const { error, data } = await userApi.walletLog.money(props.id, {
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-    });
-    if (error === 0) {
-      log.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
-    }
-    log.loading = false;
-    emit('updateRefresh', false);
-  }
-
-  function onChangeLogTabName() {
-    pageData.page = 1;
-    pageData.list_rows = 10;
-    pageData.total = 0;
-    getData();
-  }
-
-  watch(
-    () => props.refresh,
-    () => {
-      if (props.refresh) {
-        onChangeLogTabName();
-      }
-    },
-  );
-
-  onMounted(() => {
-    onChangeLogTabName();
-  });
-</script>

+ 0 - 75
src/app/user/admin/components/score.vue

@@ -1,75 +0,0 @@
-<template>
-  <el-table class="sa-table" :data="log.data" height="520" v-loading="log.loading">
-    <el-table-column prop="create_time" label="交易时间" min-width="172" align="center" />
-    <el-table-column prop="amount" label="变动积分" min-width="120" align="center" />
-    <el-table-column prop="before" label="变更前" min-width="120" align="center" />
-    <el-table-column prop="after" label="剩余积分" min-width="120" align="center" />
-    <el-table-column label="操作人" min-width="160">
-      <template #default="scope">
-        <sa-user-profile type="oper" :user="scope.row.oper" :id="scope.row.oper_id" />
-      </template>
-    </el-table-column>
-    <el-table-column label="备注" min-width="160" align="center">
-      <template #default="scope">
-        {{ scope.row.event_text }}{{ scope.row.memo ? ':' + scope.row.memo : '' }}
-      </template>
-    </el-table-column>
-  </el-table>
-  <sa-view-bar>
-    <template #right>
-      <sa-pagination :pageData="pageData" @updateFn="getData" />
-    </template>
-  </sa-view-bar>
-</template>
-
-<script setup>
-  import { onMounted, reactive, watch } from 'vue';
-  import userApi from '@/app/user/api';
-  import { usePagination } from '@/sheep/hooks';
-
-  const emit = defineEmits(['updateRefresh']);
-  const props = defineProps(['id', 'refresh']);
-
-  const log = reactive({
-    data: [],
-    loading: false,
-  });
-
-  const { pageData } = usePagination();
-
-  async function getData() {
-    log.loading = true;
-    const { error, data } = await userApi.walletLog.score(props.id, {
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-    });
-    if (error === 0) {
-      log.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
-    }
-    log.loading = false;
-    emit('updateRefresh', false);
-  }
-
-  function onChangeLogTabName() {
-    pageData.page = 1;
-    pageData.list_rows = 10;
-    pageData.total = 0;
-    getData();
-  }
-
-  watch(
-    () => props.refresh,
-    () => {
-      if (props.refresh) {
-        onChangeLogTabName();
-      }
-    },
-  );
-
-  onMounted(() => {
-    onChangeLogTabName();
-  });
-</script>

+ 0 - 443
src/app/user/admin/detail.vue

@@ -1,443 +0,0 @@
-<template>
-  <el-container class="admin-detail">
-    <el-main class="sa-p-b-0">
-      <el-row :gutter="24">
-        <el-col class="sa-col-24" :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
-          <div class="title">基本信息</div>
-          <div class="bottom">
-            <div class="sa-flex sa-row-between sa-m-b-16">
-              <div class="sa-flex sa-flex-1">
-                <sa-image :url="detail.data.avatar" size="64" radius="32" />
-                <div class="sa-m-l-20 sa-flex-1">
-                  <div class="nickname sa-m-b-8 sa-table-line-1">
-                    {{ detail.data.nickname }}
-                  </div>
-                  <div class="id">#{{ detail.data.id }}</div>
-                </div>
-              </div>
-              <el-button v-auth="'user.admin.user.edit'" type="primary" @click="onUpload"
-                >更换头像</el-button
-              >
-            </div>
-            <div class="info">
-              <el-row :gutter="24">
-                <el-col class="item sa-col-12" :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-                  <div class="label">昵称</div>
-                  <div class="content">
-                    <el-input v-model="detail.data.nickname" />
-                  </div>
-                </el-col>
-                <el-col class="item sa-col-12" :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-                  <div class="label">性别</div>
-                  <div class="content">
-                    <el-radio-group v-model="detail.data.gender">
-                      <el-radio :label="0">未知</el-radio>
-                      <el-radio :label="1">男</el-radio>
-                      <el-radio :label="2">女</el-radio>
-                    </el-radio-group>
-                  </div>
-                </el-col>
-                <el-col class="item sa-col-12" :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-                  <div class="label">用户名</div>
-                  <div class="content">
-                    <el-input v-model="detail.data.username" placeholder="首位需为字母且不少五位">
-                      <template #suffix>
-                        <span v-html="settingStatus('username')"></span>
-                      </template>
-                    </el-input>
-                  </div>
-                </el-col>
-                <el-col class="item sa-col-12" :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-                  <div class="label">密码</div>
-                  <div class="content">
-                    <el-input v-model="detail.data.password" placeholder="不修改则留空">
-                      <template #suffix>
-                        <span v-html="settingStatus('password')"></span>
-                      </template>
-                    </el-input>
-                  </div>
-                </el-col>
-                <el-col class="item sa-col-12" :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-                  <div class="label">手机号</div>
-                  <div class="content">
-                    <el-input v-model="detail.data.mobile" type="number">
-                      <template #suffix>
-                        <span v-html="settingStatus('mobile')"></span>
-                      </template>
-                    </el-input>
-                  </div>
-                </el-col>
-                <el-col class="item sa-col-12" :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-                  <div class="label">电子邮箱</div>
-                  <div class="content">
-                    <el-input v-model="detail.data.email">
-                      <template #suffix>
-                        <span v-html="settingStatus('email')"></span>
-                      </template>
-                    </el-input>
-                  </div>
-                </el-col>
-                <el-col class="item sa-col-12" :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-                  <div class="label">状态</div>
-                  <div class="content">
-                    <el-radio-group v-model="detail.data.status">
-                      <el-radio label="normal">正常</el-radio>
-                      <el-radio label="disabled">禁用</el-radio>
-                    </el-radio-group>
-                  </div>
-                </el-col>
-                <el-col class="item sa-col-12" :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-                  <div class="label"></div>
-                  <div class="content sa-flex sa-row-right">
-                    <el-button v-auth="'user.admin.user.detail'" @click="getDetail">重置</el-button>
-                    <el-button
-                      v-throttle
-                      v-auth="'user.admin.user.edit'"
-                      type="primary"
-                      @click="onSave"
-                      >保存</el-button
-                    >
-                  </div>
-                </el-col>
-              </el-row>
-            </div>
-          </div>
-        </el-col>
-        <el-col class="sa-col-24" :xs="24" :sm="24" :md="18" :lg="18" :xl="18">
-          <el-row :gutter="24">
-            <el-col class="sa-col-24" :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-              <div class="title">账户信息</div>
-              <div class="bottom">
-                <div class="info">
-                  <el-row>
-                    <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-                      <div class="item">
-                        <div class="label">余额</div>
-                        <div class="content sa-flex">
-                          {{ detail.data.money }}
-                          <el-button
-                            v-auth="'user.admin.user.recharge'"
-                            class="is-link sa-m-l-12"
-                            type="primary"
-                            @click="onRecharge('money')"
-                            >充值</el-button
-                          >
-                        </div>
-                      </div>
-                      <div class="item">
-                        <div class="label">积分</div>
-                        <div class="content sa-flex">
-                          {{ detail.data.score }}
-                          <el-button
-                            v-auth="'user.admin.user.recharge'"
-                            class="is-link sa-m-l-12"
-                            type="primary"
-                            @click="onRecharge('score')"
-                            >充值</el-button
-                          >
-                        </div>
-                      </div>
-                      <div class="item">
-                        <div class="label">总计消费</div>
-                        <div class="content">
-                          {{ detail.data.total_consume }}
-                        </div>
-                      </div>
-                    </el-col>
-                    <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-                      <div class="item">
-                        <div class="label">上次登陆时间</div>
-                        <div class="content">{{ detail.data.login_time }}</div>
-                      </div>
-                      <div class="item">
-                        <div class="label">登录IP</div>
-                        <div class="content">{{ detail.data.login_ip }}</div>
-                      </div>
-                      <div class="item">
-                        <div class="label">注册时间</div>
-                        <div class="content">{{ detail.data.create_time }}</div>
-                      </div>
-                    </el-col>
-                  </el-row>
-                </div>
-              </div>
-            </el-col>
-            <el-col class="third-oauth sa-col-24" :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
-              <div class="title">第三方账号</div>
-              <div class="bottom">
-                <el-row class="info">
-                  <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-                    <div class="item" v-for="item in detail.data.third_oauth" :key="item.id">
-                      <div class="content sa-flex sa-row-between">
-                        <div class="sa-flex">
-                          <img
-                            :src="`./static/images/platform/${item.provider}/${item.platform}.png`"
-                          />
-                          <span class="sa-m-l-16">
-                            {{ platform[item.provider][item.platform] }}
-                          </span>
-                        </div>
-                        <el-popover popper-class="sa-popper" placement="top-start" trigger="hover">
-                          <div>
-                            <div class="item sa-flex">
-                              <div class="label">登录次数:</div>
-                              <div class="content">{{ item.login_num }}</div>
-                            </div>
-                            <div class="item sa-flex">
-                              <div class="label">Openid:</div>
-                              <div class="content">{{ item.openid }}</div>
-                            </div>
-                            <div v-if="item.unionid" class="item sa-flex">
-                              <div class="label">Unionid:</div>
-                              <div class="content">{{ item.unionid }}</div>
-                            </div>
-                            <div class="item sa-flex">
-                              <div class="label">更新时间:</div>
-                              <div class="content">{{ item.update_time }}</div>
-                            </div>
-                          </div>
-                          <template #reference>
-                            <div class="sa-flex">
-                              <sa-image :url="item.avatar" size="32" />
-                              <span class="name sa-m-l-12">
-                                {{ item.nickname }}
-                              </span>
-                            </div>
-                          </template>
-                        </el-popover>
-                      </div>
-                    </div>
-                  </el-col>
-                </el-row>
-              </div>
-            </el-col>
-            <el-col class="log sa-col-24" :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-              <div class="title sa-flex"> 用户动态 </div>
-              <div class="bottom">
-                <el-tabs v-model="log.tabName">
-                  <el-tab-pane label="余额记录" name="money"></el-tab-pane>
-                  <el-tab-pane label="积分记录" name="score"></el-tab-pane>
-
-                  <el-tab-pane label="订单记录" name="order"></el-tab-pane>
-                  <el-tab-pane label="分享记录" name="share"></el-tab-pane>
-                  <el-tab-pane label="优惠券明细" name="coupon"></el-tab-pane>
-                </el-tabs>
-                <component
-                  :is="`${log.tabName}-log`"
-                  :id="modal?.params?.id"
-                  :refresh="log.refresh"
-                  @updateRefresh="(val) => (log.refresh = val)"
-                />
-              </div>
-            </el-col>
-          </el-row>
-        </el-col>
-      </el-row>
-    </el-main>
-  </el-container>
-</template>
-
-<script>
-  import MoneyLog from './components/money.vue';
-  import ScoreLog from './components/score.vue';
-  import OrderLog from '@/app/shop/admin/user/order.vue';
-  import ShareLog from '@/app/shop/admin/user/share.vue';
-  import CouponLog from '@/app/shop/admin/user/coupon.vue';
-  export default {
-    components: {
-      MoneyLog,
-      ScoreLog,
-      OrderLog,
-      ShareLog,
-      CouponLog,
-    },
-  };
-</script>
-
-<script setup>
-  import { onMounted, reactive } from 'vue';
-  import userApi from '@/app/user/api';
-  import { useFile, useModal } from '@/sheep/hooks';
-  import AdminRecharge from './recharge.vue';
-
-  const props = defineProps(['modal']);
-
-  const detail = reactive({
-    loading: false,
-    data: {},
-  });
-
-  async function getDetail() {
-    detail.loading = true;
-    const { error, data } = await userApi.detail(props.modal.params.id);
-    error === 0 && (detail.data = data);
-    detail.loading = false;
-  }
-
-  function settingStatus(key) {
-    let flag = detail.data.verification?.[key];
-    return `<span style="color:${flag ? 'var(--el-color-success)' : 'var(--el-color-warning)'}">
-  ${flag ? '已' : '未'}${key == 'username' || key == 'password' ? '设置' : '认证'}
-  </span>`;
-  }
-
-  const platform = {
-    wechat: {
-      openPlatform: '微信开放平台',
-      miniProgram: '微信小程序',
-      officialAccount: '微信公众平台',
-    },
-  };
-
-  const log = reactive({
-    tabName: 'money',
-    refresh: false,
-  });
-
-  function onUpload() {
-    useFile(
-      {
-        fileType: 'image',
-      },
-      {
-        confirm: (data) => {
-          detail.data.avatar = data.url;
-        },
-      },
-    );
-  }
-
-  async function onSave() {
-    const { error } = await userApi.edit(props.modal.params.id, detail.data);
-    error == 0 && getDetail();
-  }
-
-  function onRecharge(type) {
-    useModal(
-      AdminRecharge,
-      {
-        title: '充值',
-        id: props.modal.params.id,
-        type,
-      },
-      {
-        confirm: () => {
-          getDetail();
-          log.refresh = true;
-        },
-      },
-    );
-  }
-
-  onMounted(() => {
-    getDetail();
-  });
-</script>
-
-<style lang="scss" scoped>
-  .admin-detail {
-    .title {
-      height: 32px;
-      line-height: 32px;
-      font-size: 16px;
-      color: var(--sa-subtitle);
-      margin-bottom: 8px;
-    }
-    .bottom {
-      padding: 16px;
-      background: var(--sa-table-header-bg);
-      border-radius: 8px;
-      margin-bottom: 20px;
-      .nickname {
-        line-height: 20px;
-        font-size: 20px;
-        font-weight: 500;
-        color: var(--sa-subtitle);
-      }
-      .id {
-        line-height: 16px;
-        font-size: 16px;
-        font-weight: 500;
-        color: var(--sa-subfont);
-      }
-      .info {
-        padding: 16px 16px 0;
-        background: var(--sa-background-assist);
-        border-radius: 4px;
-        .item {
-          margin-bottom: 16px;
-          .label {
-            height: 12px;
-            line-height: 12px;
-            font-size: 12px;
-            font-weight: 500;
-            color: var(--sa-subfont);
-            margin-bottom: 8px;
-          }
-          .content {
-            line-height: 32px;
-            font-size: 14px;
-            font-weight: 400;
-            color: var(--sa-subtitle);
-          }
-        }
-      }
-    }
-    .third-oauth {
-      .info {
-        padding: 20px 16px 0;
-        .item {
-          margin-bottom: 24px;
-          img {
-            width: 32px;
-            height: 32px;
-          }
-          .name {
-            color: var(--sa-font);
-          }
-          .none {
-            color: #999;
-          }
-        }
-      }
-    }
-    .log {
-      .bottom {
-        padding: 0;
-        background: var(--sa-background-assist);
-        :deep() {
-          .el-tabs__nav-wrap::after {
-            height: 0;
-          }
-        }
-        .sa-table {
-          height: 300px;
-        }
-        :deep() {
-          .oper-type {
-            font-size: 12px;
-            color: var(--sa-subtitle);
-          }
-          .system {
-            font-size: 14px;
-            color: var(--sa-subtitle);
-            img {
-              width: 32px;
-              height: 32px;
-              margin-right: 8px;
-            }
-          }
-        }
-      }
-    }
-
-    @media only screen and (max-width: 1200px) {
-      @for $i from 0 through 24 {
-        .sa-col-#{$i} {
-          max-width: calc(calc($i * 100%) / 24) !important;
-          flex: 0 0 calc(calc($i * 100%) / 24) !important;
-        }
-      }
-    }
-  }
-</style>

+ 0 - 240
src/app/user/admin/index.vue

@@ -1,240 +0,0 @@
-<template>
-  <el-container>
-    <el-header class="sa-header">
-      <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="'user.admin.user.list'"
-            class="sa-button-refresh"
-            icon="RefreshRight"
-            @click="getData()"
-          ></el-button>
-          <el-button class="sa-button-refresh" icon="Search" @click="openFilter"></el-button>
-        </div>
-      </div>
-    </el-header>
-    <el-main class="sa-p-0" v-loading="table.loading">
-      <el-table class="sa-table" height="100%" :data="table.data" stripe @sort-change="fieldFilter">
-        <template #empty>
-          <sa-empty />
-        </template>
-        <el-table-column sortable="custom" prop="id" label="ID" min-width="80" />
-        <el-table-column label="用户信息" min-width="150">
-          <template #default="scope">
-            <sa-user-profile :user="scope.row" :id="scope.row.id" />
-          </template>
-        </el-table-column>
-        <el-table-column prop="username" label="用户名" min-width="120" align="center" />
-        <el-table-column prop="mobile" label="手机号" min-width="120" align="center" />
-        <el-table-column
-          sortable="custom"
-          prop="commission"
-          label="佣金"
-          min-width="110"
-          align="center"
-        />
-        <el-table-column
-          sortable="custom"
-          prop="total_consume"
-          label="总消费"
-          min-width="110"
-          align="center"
-        />
-        <el-table-column
-          sortable="custom"
-          prop="score"
-          label="积分"
-          min-width="110"
-          align="center"
-        />
-        <el-table-column
-          sortable="custom"
-          prop="money"
-          label="余额"
-          min-width="110"
-          align="center"
-        />
-        <el-table-column
-          sortable="custom"
-          prop="create_time"
-          label="注册时间"
-          min-width="172"
-          align="center"
-        />
-        <el-table-column
-          sortable="custom"
-          prop="login_time"
-          label="上次登录"
-          min-width="172"
-          align="center"
-        />
-        <el-table-column label="操作" min-width="120" fixed="right">
-          <template #default="scope">
-            <el-button
-              v-auth="'user.admin.user.detail'"
-              class="is-link"
-              type="primary"
-              @click="onDetail(scope.row.id)"
-              >查看</el-button
-            >
-            <el-popconfirm
-              width="fit-content"
-              confirm-button-text="确认"
-              cancel-button-text="取消"
-              title="确认删除这条记录?"
-              @confirm="onDelete(scope.row.id)"
-            >
-              <template #reference>
-                <el-button v-auth="'user.admin.user.delete'" class="is-link" type="danger">
-                  删除
-                </el-button>
-              </template>
-            </el-popconfirm>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-main>
-    <sa-view-bar>
-      <template #right>
-        <sa-pagination :pageData="pageData" @updateFn="getData" />
-      </template>
-    </sa-view-bar>
-  </el-container>
-</template>
-
-<script>
-  export default {
-    name: 'user.admin',
-  };
-</script>
-
-<script setup>
-  import { onMounted, reactive } from 'vue';
-  import userApi from '@/app/user/api';
-  import { usePagination, useModal } from '@/sheep/hooks';
-  import { useSearch } from '@/sheep/components/sa-table/sa-search/useSearch';
-  import { composeFilter } from '@/sheep/utils';
-  import UserDetail from './detail.vue';
-  import { cloneDeep } from 'lodash';
-
-  const filterParams = reactive({
-    tools: {
-      user: {
-        type: 'tinputprepend',
-        placeholder: '请输入查询内容',
-        field: 'user',
-        user: {
-          field: 'id',
-          value: '',
-        },
-        options: [
-          {
-            label: '用户ID',
-            value: 'id',
-          },
-          {
-            label: '用户名',
-            value: 'username',
-          },
-          {
-            label: '昵称',
-            value: 'nickname',
-          },
-          {
-            label: '手机号',
-            value: 'mobile',
-          },
-          {
-            label: '邮箱',
-            value: 'email',
-          },
-        ],
-      },
-      create_time: {
-        type: 'tdatetimerange',
-        label: '注册时间',
-        field: 'create_time',
-        value: [],
-      },
-      login_time: {
-        type: 'tdatetimerange',
-        label: '上次登录',
-        field: 'login_time',
-        value: [],
-      },
-    },
-    data: {
-      user: { field: 'id', value: '' },
-      create_time: [],
-      login_time: [],
-    },
-    conditionLabel: {},
-  });
-  const { openFilter, deleteFilter } = useSearch({ filterParams, getData });
-
-  const table = reactive({
-    loading: false,
-    data: [],
-    order: '',
-    sort: '',
-  });
-
-  const { pageData } = usePagination();
-
-  async function getData(page) {
-    table.loading = true;
-    if (page) pageData.page = page;
-    let tempSearch = cloneDeep(filterParams.data);
-    let search = composeFilter(tempSearch, {
-      username: 'like',
-      nickname: 'like',
-      mobile: 'like',
-      email: 'like',
-      create_time: 'range',
-      login_time: 'range',
-    });
-    const { error, data } = await userApi.list({
-      page: pageData.page,
-      list_rows: pageData.list_rows,
-      ...search,
-      order: table.order,
-      sort: table.sort,
-    });
-    if (error === 0) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.list_rows = data.per_page;
-      pageData.total = data.total;
-    }
-    table.loading = false;
-  }
-
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
-
-  function onDetail(id) {
-    useModal(UserDetail, {
-      title: '会员详情',
-      id,
-    });
-  }
-
-  async function onDelete(id) {
-    const { error } = await userApi.delete(id);
-    error == 0 && getData();
-  }
-
-  onMounted(() => {
-    getData();
-  });
-</script>

Some files were not shown because too many files changed in this diff