xgj 1 month ago
parent
commit
982cb957bb

+ 1 - 2
.env

@@ -1,6 +1,5 @@
 # title
-VITE_GLOB_APP_TITLE = Geeker Admin
-
+VITE_GLOB_APP_TITLE = 合作机构
 # 本地运行端口号
 VITE_PORT = 8848
 

+ 1 - 1
.env.development

@@ -36,7 +36,7 @@ VITE_ROUTER_HISTORY = "hash"
 ENV = 'development'
 
 # 开发环境请求接口
-VITE_APP_H5_DOMAIN = 'https://q.dev.mdfitnesscao.com'
+VITE_APP_H5_DOMAIN = 'https://cps.mdfitnesscao.com'
 VITE_APP_DOMAIN = 'https://mcapi.mdfitnesscao.com'
 VITE_APP_DOMAIN_API = 'https://api.mdfitnesscao.com'
 VITE_APP_CHANNEL_API = 'https://mcapi.mdfitnesscao.com'

+ 1 - 0
package.json

@@ -56,6 +56,7 @@
     "nprogress": "^0.2.0",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",
+    "qrcode": "^1.5.4",
     "qs": "^6.12.1",
     "screenfull": "^6.0.2",
     "sortablejs": "^1.15.2",

+ 91 - 0
pnpm-lock.yaml

@@ -53,6 +53,9 @@ importers:
       pinia-plugin-persistedstate:
         specifier: ^3.2.1
         version: 3.2.3(pinia@2.2.2(typescript@5.6.2)(vue@3.5.5(typescript@5.6.2)))
+      qrcode:
+        specifier: ^1.5.4
+        version: 1.5.4
       qs:
         specifier: ^6.12.1
         version: 6.13.0
@@ -1944,6 +1947,9 @@ packages:
     resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
     engines: {node: '>=18'}
 
+  cliui@6.0.0:
+    resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
+
   cliui@7.0.4:
     resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
 
@@ -2342,6 +2348,9 @@ packages:
   didyoumean@1.2.2:
     resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
 
+  dijkstrajs@1.0.3:
+    resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
+
   dir-glob@3.0.1:
     resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
     engines: {node: '>=8'}
@@ -3947,6 +3956,10 @@ packages:
     resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
     engines: {node: '>= 6'}
 
+  pngjs@5.0.0:
+    resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
+    engines: {node: '>=10.13.0'}
+
   portfinder@1.0.32:
     resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==}
     engines: {node: '>= 0.12.0'}
@@ -4109,6 +4122,11 @@ packages:
 
       (For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
 
+  qrcode@1.5.4:
+    resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
+    engines: {node: '>=10.13.0'}
+    hasBin: true
+
   qs@6.13.0:
     resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
@@ -4210,6 +4228,9 @@ packages:
     resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
     engines: {node: '>=0.10.0'}
 
+  require-main-filename@2.0.0:
+    resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
+
   resolve-from@4.0.0:
     resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
     engines: {node: '>=4'}
@@ -4327,6 +4348,9 @@ packages:
   serialize-javascript@6.0.2:
     resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
 
+  set-blocking@2.0.0:
+    resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+
   set-function-length@1.2.2:
     resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
     engines: {node: '>= 0.4'}
@@ -5097,6 +5121,9 @@ packages:
   which-boxed-primitive@1.0.2:
     resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
 
+  which-module@2.0.1:
+    resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
+
   which-typed-array@1.1.15:
     resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==}
     engines: {node: '>= 0.4'}
@@ -5169,6 +5196,10 @@ packages:
   workbox-window@7.1.0:
     resolution: {integrity: sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==}
 
+  wrap-ansi@6.2.0:
+    resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
+    engines: {node: '>=8'}
+
   wrap-ansi@7.0.0:
     resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
     engines: {node: '>=10'}
@@ -5196,6 +5227,9 @@ packages:
     resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
     engines: {node: '>=0.4'}
 
+  y18n@4.0.3:
+    resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
+
   y18n@5.0.8:
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     engines: {node: '>=10'}
@@ -5211,6 +5245,10 @@ packages:
     engines: {node: '>= 14'}
     hasBin: true
 
+  yargs-parser@18.1.3:
+    resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
+    engines: {node: '>=6'}
+
   yargs-parser@20.2.9:
     resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
     engines: {node: '>=10'}
@@ -5219,6 +5257,10 @@ packages:
     resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
     engines: {node: '>=12'}
 
+  yargs@15.4.1:
+    resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
+    engines: {node: '>=8'}
+
   yargs@16.2.0:
     resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
     engines: {node: '>=10'}
@@ -7260,6 +7302,12 @@ snapshots:
       slice-ansi: 5.0.0
       string-width: 7.2.0
 
+  cliui@6.0.0:
+    dependencies:
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wrap-ansi: 6.2.0
+
   cliui@7.0.4:
     dependencies:
       string-width: 4.2.3
@@ -7668,6 +7716,8 @@ snapshots:
 
   didyoumean@1.2.2: {}
 
+  dijkstrajs@1.0.3: {}
+
   dir-glob@3.0.1:
     dependencies:
       path-type: 4.0.0
@@ -9348,6 +9398,8 @@ snapshots:
 
   pirates@4.0.6: {}
 
+  pngjs@5.0.0: {}
+
   portfinder@1.0.32:
     dependencies:
       async: 2.6.4
@@ -9488,6 +9540,12 @@ snapshots:
 
   q@1.5.1: {}
 
+  qrcode@1.5.4:
+    dependencies:
+      dijkstrajs: 1.0.3
+      pngjs: 5.0.0
+      yargs: 15.4.1
+
   qs@6.13.0:
     dependencies:
       side-channel: 1.0.6
@@ -9605,6 +9663,8 @@ snapshots:
 
   require-from-string@2.0.2: {}
 
+  require-main-filename@2.0.0: {}
+
   resolve-from@4.0.0: {}
 
   resolve-from@5.0.0: {}
@@ -9724,6 +9784,8 @@ snapshots:
     dependencies:
       randombytes: 2.1.0
 
+  set-blocking@2.0.0: {}
+
   set-function-length@1.2.2:
     dependencies:
       define-data-property: 1.1.4
@@ -10661,6 +10723,8 @@ snapshots:
       is-string: 1.0.7
       is-symbol: 1.0.4
 
+  which-module@2.0.1: {}
+
   which-typed-array@1.1.15:
     dependencies:
       available-typed-arrays: 1.0.7
@@ -10796,6 +10860,12 @@ snapshots:
       '@types/trusted-types': 2.0.7
       workbox-core: 7.1.0
 
+  wrap-ansi@6.2.0:
+    dependencies:
+      ansi-styles: 4.3.0
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+
   wrap-ansi@7.0.0:
     dependencies:
       ansi-styles: 4.3.0
@@ -10825,6 +10895,8 @@ snapshots:
 
   xtend@4.0.2: {}
 
+  y18n@4.0.3: {}
+
   y18n@5.0.8: {}
 
   yallist@3.1.1: {}
@@ -10833,10 +10905,29 @@ snapshots:
 
   yaml@2.5.1: {}
 
+  yargs-parser@18.1.3:
+    dependencies:
+      camelcase: 5.3.1
+      decamelize: 1.2.0
+
   yargs-parser@20.2.9: {}
 
   yargs-parser@21.1.1: {}
 
+  yargs@15.4.1:
+    dependencies:
+      cliui: 6.0.0
+      decamelize: 1.2.0
+      find-up: 4.1.0
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      require-main-filename: 2.0.0
+      set-blocking: 2.0.0
+      string-width: 4.2.3
+      which-module: 2.0.1
+      y18n: 4.0.3
+      yargs-parser: 18.1.3
+
   yargs@16.2.0:
     dependencies:
       cliui: 7.0.4

+ 15 - 2
src/assets/json/authMenuList.json

@@ -11,8 +11,7 @@
         "isLink": "",
         "isHide": false,
         "isFull": false,
-        "isAffix": false,
-        "isKeepAlive": true
+        "isAffix": false
       },
       "children": [
         {
@@ -29,6 +28,20 @@
             "isKeepAlive": true
           }
         },
+        {
+          "path": "/promotion/invoice",
+          "name": "PromotionInvoice",
+          "component": "/promotion/invoice",
+          "meta": {
+            "icon": "Menu",
+            "title": "推广账单",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": true,
+            "isKeepAlive": true
+          }
+        },
 
         {
           "path": "/promotion/channel",

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

@@ -58,5 +58,5 @@ const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu
 </script>
 
 <style scoped lang="scss">
-@import "./index.scss";
+@import "./index";
 </style>

+ 1 - 1
src/layouts/index.vue

@@ -1,6 +1,6 @@
 <!-- 💥 这里是一次性加载 LayoutComponents -->
 <template>
-  <el-watermark id="watermark" :font="font" :content="watermark ? ['Geeker Admin', 'Happy Working'] : ''">
+  <el-watermark id="watermark" :font="font" :content="watermark ? ['第三方管理系统', 'Happy Working'] : ''">
     <component :is="LayoutComponents[layout]" />
     <ThemeDrawer />
   </el-watermark>

+ 1 - 1
src/layouts/indexAsync.vue

@@ -1,6 +1,6 @@
 <!-- 💥 这里是异步加载 LayoutComponents -->
 <template>
-  <el-watermark id="watermark" :font="font" :content="watermark ? ['Geeker Admin', 'Happy Working'] : ''">
+  <el-watermark id="watermark" :font="font" :content="watermark ? ['第三方管理系统', 'Happy Working'] : ''">
     <suspense>
       <template #default>
         <component :is="LayoutComponents[layout]" />

+ 33 - 0
src/utils/index.ts

@@ -309,3 +309,36 @@ export function findItemNested(enumData: any, callValue: any, value: string, chi
     if (current[children]) return findItemNested(current[children], callValue, value, children);
   }, null);
 }
+
+/**
+ * 下载文件的公共函数
+ * @param {string} url - 请求的 URL
+ * @param {string} fileName - 下载后文件的名称
+ * @param {object} params - 请求参数
+ */
+export const handleDownloadFile = async (response, fileName, params = {}) => {
+  try {
+    // 创建一个 URL 对象
+    const blob = new Blob([response], {
+      type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+    });
+
+    const downloadUrl = window.URL.createObjectURL(blob);
+
+    // 创建一个链接元素
+    const link = document.createElement("a");
+    link.href = downloadUrl;
+    link.setAttribute("download", fileName); // 设置下载文件名
+
+    // 触发下载
+    document.body.appendChild(link);
+    link.click();
+
+    // 清理
+    link.parentNode.removeChild(link);
+    window.URL.revokeObjectURL(downloadUrl);
+  } catch (error) {
+    console.error("下载文件失败:", error);
+    throw error; // 可以根据需要抛出错误
+  }
+};

+ 1 - 1
src/views/directives/watermarkDirect/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-waterMarker="{ text: 'Geeker Admin', textColor: 'rgba(180, 180, 180, 0.6)' }" class="card content-box">
+  <div v-waterMarker="{ text: '第三方管理系统', textColor: 'rgba(180, 180, 180, 0.6)' }" class="card content-box">
     <span class="text">水印指令 🍇🍇🍇🍓🍓🍓</span>
   </div>
 </template>

+ 0 - 1
src/views/login/index.scss

@@ -45,7 +45,6 @@
           height: 52px;
         }
         .logo-text {
-          padding: 0 0 0 25px;
           margin: 0;
           font-size: 32px;
           font-weight: bold;

+ 1 - 1
src/views/login/index.vue

@@ -7,7 +7,7 @@
       </div>
       <div class="login-form">
         <div class="login-logo">
-          <img class="login-icon" src="@/assets/images/logo.svg" alt="" />
+          <!-- <img class="login-icon" src="@/assets/images/logo.svg" alt="" /> -->
           <h4 class="logo-text">合作机构后台管理系统</h4>
         </div>
         <LoginForm />

+ 30 - 4
src/views/promotion/components/DialogPromotionCode.vue

@@ -10,8 +10,32 @@
           <el-table-column prop="price" :formatter="v => (v.price / 100).toFixed(2)" label="推广售价"> </el-table-column>
           <el-table-column prop="prop" label="操作">
             <template #default="{ row }">
-              <el-button type="primary" :icon="CopyDocument" v-copy="row.id" link>复制推广链接{{ row.id }}</el-button>
-              <el-button type="primary" :icon="Download" link>下载推广二维码</el-button>
+              <el-space>
+                <el-button
+                  type="primary"
+                  :icon="CopyDocument"
+                  v-copy="`${H5_DOMAIN}/promotion/goods?promotionSiteId=${rowData.sn}&&promotionGoodsId=${row.id}`"
+                  link
+                  >复制推广链接</el-button
+                >
+                <QRCodeShare ref="QRCodeShareRef">
+                  <el-button
+                    type="primary"
+                    :icon="Download"
+                    link
+                    @click="
+                      QRCodeShareRef.generateAndDownloadQRCode(
+                        `${H5_DOMAIN}/promotion/goods?promotionSiteId=${rowData.sn}&&promotionGoodsId=${row.sn}`,
+                        `推广者二维码--${rowData.name}--会员卡名称--${row.memberCardName}`
+                      )
+                    "
+                  >
+                    下载推广二维码</el-button
+                  >
+                </QRCodeShare>
+              </el-space>
+              <!-- <el-button type="primary" :icon="Download" link>
+                下载推广二维码</el-button> -->
             </template>
           </el-table-column>
         </el-table>
@@ -26,11 +50,11 @@
   </div>
 </template>
 <script lang="ts" setup>
-import { computed, defineProps, defineEmits, reactive, watch } from "vue";
+import { computed, defineProps, defineEmits, reactive, watch, ref } from "vue";
 import { ElMessage, type FormInstance } from "element-plus";
 import { promotionSiteUpdateOrCreate, getPromotionGoodsList } from "@/api/modules/promotion";
 import { Download, CopyDocument } from "@element-plus/icons-vue";
-
+import QRCodeShare from "./QRCodeShare.vue";
 const props = defineProps({
   show: {
     type: Boolean,
@@ -44,6 +68,8 @@ const props = defineProps({
   }
 });
 const emits = defineEmits(["update:show", "success"]);
+const QRCodeShareRef = ref();
+const H5_DOMAIN = import.meta.env.VITE_APP_H5_DOMAIN as string;
 const state = reactive({
   list: []
 });

+ 81 - 0
src/views/promotion/components/DialogRecordTable.vue

@@ -0,0 +1,81 @@
+<template>
+  <div>
+    <el-dialog title="获取账单生成历史记录(含错误信息)" v-model="dialogVisible">
+      <div>
+        <el-table :data="list" style="width: 100%">
+          <el-table-column prop="startTime" label="账单周期">
+            <template #default="{ row }">
+              <div class="flex">
+                <div>{{ row.startTime?.split(" ")[0] }}</div>
+                <div class="mx-2">至</div>
+                <div>{{ row.endTime?.split(" ")[0] }}</div>
+              </div>
+            </template>
+          </el-table-column>
+
+          <el-table-column prop="errMsgList" label="错误信息">
+            <template #default="{ row }">
+              <div v-if="row.errMsgList.length === 0">-</div>
+              <div v-else v-for="(err, index) in row.errMsgList" :key="index">
+                <div class="text-red-500">{{ err }}</div>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import request from "@/api";
+import { ref, computed, defineProps, defineEmits, watch, reactive } from "vue";
+const props = defineProps({
+  show: {
+    type: Boolean,
+    default: false
+  },
+  rowData: {
+    type: Object,
+    default: () => {}
+  }
+});
+const emits = defineEmits(["update:show", "success"]);
+const dialogVisible = computed({
+  get() {
+    return props.show;
+  },
+  set(n) {
+    emits("update:show", n);
+  }
+});
+const query = {
+  page: 1,
+  pageSize: 10
+};
+const total = ref(0);
+const list = ref([]);
+watch(
+  () => dialogVisible.value,
+  n => {
+    if (n) {
+      query.page = 1;
+      getList();
+    }
+  }
+);
+const getList = () => {
+  request
+    .get("/archivesService/agency/orderBill/generateLog/paginate", {
+      params: query.value
+    })
+    .then(resp => {
+      list.value = resp.data.list.map(v => {
+        return {
+          ...v,
+          errMsgList: JSON.parse(v.errMsg)
+        };
+      });
+      total.value = resp.data.total;
+    });
+};
+</script>

+ 155 - 0
src/views/promotion/components/PerviewDetail.vue

@@ -0,0 +1,155 @@
+<template>
+  <div>
+    <div class="flex items-center">
+      <div class="w-[100px] h-[100px] text-purple-600">
+        <Tickets />
+      </div>
+      <div>
+        <div>会员卡订单</div>
+        <div class="mt-6">订单编号:{{ detail?.sn }}</div>
+      </div>
+    </div>
+    <div class="mt-4">
+      <el-tabs type="border-card" v-model="activeName">
+        <el-tab-pane name="1">
+          <template #label>
+            订单信息
+            <!-- <el-icon v-if="isvisibleIcon" class="ml-1" @click.stop="handleProcessCallback"><Warning /></el-icon> -->
+          </template>
+          <el-descriptions title="" :column="2">
+            <el-descriptions-item label="订单编号:">{{ detail?.sn }}</el-descriptions-item>
+            <el-descriptions-item label="订单类型:">会员卡订单</el-descriptions-item>
+            <el-descriptions-item label="创建时间:">{{ detail?.createdAt }}</el-descriptions-item>
+            <el-descriptions-item label="支付时间:">{{ detail?.payTime || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="订单状态:">
+              <span :class="statusList[detail?.status - 1]?.color">
+                {{ statusList[detail?.status - 1]?.label }}
+              </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="支付方式:"> 微信支付 </el-descriptions-item>
+            <el-descriptions-item label="订单金额:"> ¥{{ detail?.promotionGoods?.originPrice / 100 }} </el-descriptions-item>
+            <el-descriptions-item label="实付金额:"> ¥{{ detail?.promotionGoods?.price / 100 }} </el-descriptions-item>
+            <el-descriptions-item label="支付款流向:">
+              {{ detail?.paymentType == 1 ? "平台" : detail?.agency?.nickname }}
+            </el-descriptions-item>
+            <el-descriptions-item label="收款ID:">
+              {{ detail?.mchId }}
+            </el-descriptions-item>
+            <el-descriptions-item label="支付单号:">
+              {{ detail?.paymentOrderSn || "-" }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+        <el-tab-pane label="订单来源信息" name="2">
+          <el-descriptions title="" :column="2">
+            <el-descriptions-item label="订单来源:">
+              {{ detail?.agency?.nickname || "-" }}
+            </el-descriptions-item>
+            <el-descriptions-item label="来源ID:">
+              {{ detail?.agency?.account }}
+            </el-descriptions-item>
+            <el-descriptions-item label="渠道名称:">
+              {{ detail?.promotionSite?.name || "-" }}
+            </el-descriptions-item>
+            <el-descriptions-item label="渠道ID:">
+              {{ detail?.promotionSite?.sn || "-" }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+        <el-tab-pane label="商品信息" name="3">
+          <el-descriptions title="" :column="1">
+            <el-descriptions-item label="商品名称:">
+              {{ detail?.promotionGoods?.promotionGoods || "-" }}
+            </el-descriptions-item>
+            <el-descriptions-item label="商品ID:">
+              {{ detail?.promotionGoods?.memberCardId || "-" }}
+            </el-descriptions-item>
+            <el-descriptions-item label="购买数量:">1 </el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+        <el-tab-pane label="用户信息" name="4">
+          <el-descriptions title="" :column="1">
+            <el-descriptions-item label="用户ID:">
+              {{ detail?.archives?.id || "-" }}
+            </el-descriptions-item>
+            <el-descriptions-item label="手机号:">
+              <span v-if="!detail?.archives?.accounts?.length">-</span>
+              <span v-else v-for="(item, index) in detail?.archives?.accounts" :key="index" class="space-x-4">
+                <span>{{ item?.account || "-" }}12</span>
+              </span>
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+<script setup>
+// import { request } from "@/utils";
+import http from "@/api";
+import { Tickets, Warning } from "@element-plus/icons-vue";
+import { ref, defineProps, computed } from "vue";
+import { ElMessage } from "element-plus";
+
+import { statusList } from "../constant.ts";
+
+const activeName = ref("1");
+const props = defineProps({
+  orderSn: {
+    type: String,
+    default: ""
+  }
+});
+const emits = defineEmits(["success"]);
+const detail = ref({});
+
+const isvisibleIcon = computed(() => {
+  const payTimeDiffFiveMin =
+    detail.value?.payTime && new Date().getTime() - new Date(detail.value?.payTime).getTime() > 5 * 60 * 1000;
+  return (
+    (detail.value.status == 2 && [1, 3].includes(detail.value?.callbackStatus)) ||
+    (detail.value?.callbackStatus == 4 && payTimeDiffFiveMin)
+  );
+});
+const handleProcessCallback = () => {
+  /**
+   * 
+   * 订单状态为支付成功的时候,微信支付回调可能会支付失败。如果为待处理和失败 则重新发起
+   * 如果为4,则支付时间超过了5分钟 也要重新发起
+   * 
+   * OrderTypeCallbackStatusWait    = 1 // 待处理
+    OrderTypeCallbackStatusDone    = 2 // 已处理
+    OrderTypeCallbackStatusError   = 3 // 失败
+    OrderTypeCallbackStatusProcess = 4 // 处理中
+   */
+  http
+    .post("archivesService/mechanism/order/processCallback", {
+      orderSn: props.orderSn
+    })
+    .then(resp => {
+      ElMessage.success(resp.message);
+      emits("success");
+      getDetail();
+    });
+};
+
+const getDetail = () => {
+  if (!props.orderSn) return;
+  http
+    .get("/archivesService/agency/order/detail", {
+      orderSn: props.orderSn
+    })
+    .then(resp => {
+      detail.value = resp.data.detail;
+    });
+};
+const initData = () => {
+  activeName.value = "1";
+  getDetail();
+};
+
+defineExpose({
+  initData
+});
+</script>
+<style lang="scss"></style>

+ 233 - 0
src/views/promotion/components/PreviewPromotionalInvoiceDetailDialog.vue

@@ -0,0 +1,233 @@
+<template>
+  <div>
+    <el-dialog title="账单详情" v-model="dialogVisible" width="70%">
+      <div>
+        <el-table :data="[detail]" style="width: 100%" border>
+          <el-table-column prop="startTime" label="账单周期">
+            <template #default="{ row }">
+              <div class="flex">
+                <div>{{ row.startTime?.split(" ")[0] }}</div>
+                <div class="mx-2">至</div>
+                <div>{{ row.endTime?.split(" ")[0] }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column prop="archiveId" label="账单所属第三方名称">
+            <template #default="{ row }">
+              <div class="flex">
+                <div>{{ row.agency?.nickname }}【{{ row.agency?.id }}】</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column
+            prop="agencyReceiveBalance"
+            label="累计支付到第三方总金额"
+            :formatter="v => (v.agencyReceiveBalance / 100).toFixed(2)"
+          />
+          <el-table-column
+            prop="toMechanismBalance"
+            label="结算后平台应收收款金额"
+            :formatter="v => (v.toMechanismBalance / 100).toFixed(2)"
+          />
+          <el-table-column
+            prop="mechanismReceiveBalance"
+            label="累计支付到平台总金额"
+            :formatter="v => (v.mechanismReceiveBalance / 100).toFixed(2)"
+          />
+          <el-table-column
+            prop="toAgencyBalance"
+            label="结算后第三方应收款金额"
+            :formatter="v => (v.toAgencyBalance / 100).toFixed(2)"
+          />
+          <el-table-column prop="serviceName" label="结算状态">
+            <template #default="{ row }">
+              <div :class="statusList[row.status - 1]?.color">
+                {{ statusList[row.status - 1]?.label }}
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <el-button type="primary" class="my-3" v-if="detail.status == 2" @click="handleSettle">设为已结算</el-button>
+        <el-divider />
+        <div>
+          <div class="flex justify-between">
+            <div class="flex">
+              <div>{{ detail.startTime?.split(" ")[0] }}</div>
+              <div class="mx-2">至</div>
+              <div class="mr-3">{{ detail.endTime?.split(" ")[0] }}</div>
+              订单详情(系统只显示这期间内支付已完成订单)
+            </div>
+            <el-button v-if="state.recordList.length" :icon="Document" type="primary" link @click="handleExport"
+              >导出详情</el-button
+            >
+          </div>
+
+          <el-table :data="state.recordList" style="width: 100%" border class="my-4">
+            <el-table-column prop="sn" label="订单信息">
+              <template #default="{ row }">
+                <div>
+                  <span>{{ row.order.sn }}</span>
+                  <el-tag>【会员卡订单】</el-tag>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="promotionSite" label="订单来源信息">
+              <template #default="{ row }">
+                <div>
+                  <div>{{ row?.order?.promotionSite?.name }}</div>
+                  <div style="color: #a8abb2">[ {{ row.order.promotionSite.sn }} ]</div>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="promotionGoods" :formatter="v => v.order.promotionGoods.memberCardName" label="订单商品名称" />
+            <el-table-column prop="promotionGoods" label="实付金额">
+              <template #default="{ row }">
+                <div>
+                  {{ row.order.promotionGoods.price / 100 }}
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="status" :formatter="v => v.order.promotionGoods.memberCardName" label="订单状态">
+              <template #default="{ row }">
+                <div :class="orderStatusList[row.order.status - 1]?.color">
+                  {{ orderStatusList[row.order.status - 1]?.label }}
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="createdAt" label="支付时间" />
+            <el-table-column prop="payType" label="支付方式" :formatter="() => '微信支付'" />
+            <el-table-column prop="paymentType" label="支付款流向">
+              <template #default="{ row }">
+                <div>
+                  {{ row.order.paymentType === 1 ? "平台" : row?.order.agency?.nickname }}
+                </div>
+                <div style="color: #a8abb2">
+                  [ 收款ID:{{ row.order.mchId }}]
+                  <br />
+                  [账号ID: {{ row.order.agencyId }}]
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column
+              prop="paymentType"
+              label="结算比例"
+              :formatter="
+                v =>
+                  v.order.paymentType == 1
+                    ? `${(v.order.promotionGoods?.toPlatformSettleRate).toFixed(2)}%`
+                    : `${v.order.promotionGoods?.toAgencySettleRate.toFixed(2)}%`
+              "
+            />
+            <el-table-column
+              prop="toAgencyBalance"
+              label="结算后第三方应收款金额"
+              :formatter="v => (v.type == 2 ? (v.balance / 100).toFixed(2) : '0.00')"
+            />
+            <el-table-column
+              prop="toMechanismBalance"
+              label="结算后平台应收款金额"
+              :formatter="v => (v.type == 1 ? (v.balance / 100).toFixed(2) : '0.00')"
+            />
+          </el-table>
+          <el-pagination
+            class="mt-4 justify-end"
+            background
+            layout="prev, pager, next"
+            :total="state.total"
+            :page-size="state.query.pageSize"
+            @current-change="changePage"
+          />
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script setup>
+import { handleDownloadFile } from "@/utils";
+import { ref, computed, defineProps, defineEmits, reactive } from "vue";
+import { statusList } from "../constant.ts";
+import { statusList as orderStatusList } from "../constant.ts";
+import { ElMessage } from "element-plus";
+import { Document } from "@element-plus/icons-vue";
+import request from "@/api";
+
+const props = defineProps({
+  show: {
+    type: Boolean,
+    default: false
+  },
+  rowData: {
+    type: Object,
+    default: () => {}
+  }
+});
+const emits = defineEmits(["update:show", "success"]);
+const detail = ref({});
+const state = reactive({
+  query: {
+    page: 1,
+    pageSize: 10
+  },
+
+  total: 0,
+  recordList: []
+});
+const dialogVisible = computed({
+  get() {
+    return props.show;
+  },
+  set(n) {
+    emits("update:show", n);
+  }
+});
+const handleSettle = () => {
+  request.post(`archivesService/agency/orderBill/settle?id=${detail.value.id}`).then(resp => {
+    ElMessage.success(resp.message);
+    emits("success");
+    initData(detail.value.id);
+  });
+};
+const initData = id => {
+  request.get(`archivesService/agency/orderBill/detail?id=${id}`).then(resp => {
+    detail.value = resp.data.detail;
+  });
+  getOrderBillRecord(id);
+};
+const getOrderBillRecord = id => {
+  request
+    .get("archivesService/agency/orderBill/record/paginate", {
+      ...state.query,
+      billId: id
+    })
+    .then(resp => {
+      state.recordList = resp.data.list;
+      state.total = resp.data.total;
+    });
+};
+const handleExport = () => {
+  request
+    .post(
+      `archivesService/agency/orderBill/export`,
+      {
+        id: detail.value.id
+      },
+      {
+        responseType: "blob"
+      }
+    )
+    .then(resp => {
+      const fileName = `${detail.value.startTime?.split(" ")[0]} 至 ${
+        detail.value.endTime?.split(" ")[0]
+      } 订单详情(系统只显示这期间内支付已完成订单)`;
+
+      handleDownloadFile(resp, fileName);
+    });
+};
+const changePage = page => {
+  state.query.page = page;
+  getOrderBillRecord(detail.value.id);
+};
+defineExpose({
+  initData
+});
+</script>

+ 52 - 0
src/views/promotion/components/QRCodeShare.vue

@@ -0,0 +1,52 @@
+<template>
+  <div class="">
+    <!-- <button @click="generateAndDownloadQRCode" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
+      下载分享二维码
+    </button> -->
+    <slot></slot>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+import QRCode from "qrcode";
+
+// const SHARE_URL = "https://www.baidu.com";
+
+const generateAndDownloadQRCode = async (SHARE_URL, filename, params = {}) => {
+  try {
+    // 生成二维码的 DataURL
+    const qrCodeDataUrl = await QRCode.toDataURL(SHARE_URL, {
+      errorCorrectionLevel: "H",
+      width: 300,
+      margin: 2,
+      ...params
+    });
+
+    // 创建一个临时链接元素
+    const downloadLink = document.createElement("a");
+    downloadLink.href = qrCodeDataUrl;
+    downloadLink.download = `${filename}.png`;
+
+    // 触发下载
+    document.body.appendChild(downloadLink);
+    downloadLink.click();
+    document.body.removeChild(downloadLink);
+  } catch (error) {
+    console.error("生成二维码失败:", error);
+    alert("生成二维码失败,请重试");
+  }
+};
+defineExpose({
+  generateAndDownloadQRCode
+});
+</script>
+
+<style scoped>
+.qr-code-share {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+}
+</style>

+ 22 - 0
src/views/promotion/constant.ts

@@ -0,0 +1,22 @@
+export const statusList = [
+  {
+    label: "待支付",
+    value: 1,
+    color: "text-red-500"
+  },
+  {
+    label: "已完成",
+    value: 2,
+    color: "text-green-500"
+  },
+  {
+    label: "已关闭",
+    value: 3,
+    color: "text-purple-500"
+  }
+];
+export const accountTypes = {
+  1: "手机号",
+  2: "身份证号",
+  3: "特殊编号"
+};

+ 237 - 0
src/views/promotion/invoice.vue

@@ -0,0 +1,237 @@
+<template>
+  <div>
+    <div>
+      <el-card class="mb-4" shadow="never">
+        <div>
+          <el-row>
+            <el-col :span="18">
+              <el-space wrap>
+                <el-date-picker
+                  v-model="state.times"
+                  type="monthrange"
+                  clearable
+                  range-separator="至"
+                  value-format="YYYY/MM/DD"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                />
+                <el-select
+                  v-model="state.query.status"
+                  placeholder="所有结算状态"
+                  filterable
+                  multiple
+                  collapse-tags
+                  clearable
+                  class="w-[180px]"
+                >
+                  <el-option v-for="item in statusList" :label="item.label" :value="item.value" :key="item.value" />
+                </el-select>
+                <el-button class="ml-2" type="primary" @click="onSearch">筛选</el-button>
+                <el-button type="primary" plain @click="onReset">重置</el-button>
+              </el-space>
+            </el-col>
+            <el-col :span="6">
+              <div class="flex justify-end text-right">
+                <el-space wrap>
+                  <!-- <el-button type="primary" plain @click="state.visibleRecordOfError = true" link class="mr-2"
+                    >获取账单生成历史记录</el-button
+                  > -->
+                  <el-input v-model="state.query.key" placeholder="关键词,可以搜索备注" style="width: 225px" />
+                  <el-button :icon="Search" @click="onSearch">查询</el-button>
+                </el-space>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+      </el-card>
+      <el-table :data="state.list" style="width: 100%" border>
+        <el-table-column prop="startTime" label="账单周期">
+          <template #default="{ row }">
+            <div class="flex">
+              <div>{{ row.startTime?.split(" ")[0] }}</div>
+              <div class="mx-2">至</div>
+              <div>{{ row.endTime?.split(" ")[0] }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="archiveId" label="账单所属第三方名称">
+          <template #default="{ row }">
+            <div class="flex">
+              <div>{{ row.agency?.nickname }}【{{ row.agency?.id }}】</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="agencyReceiveBalance"
+          label="累计支付到第三方总金额"
+          :formatter="v => (v.agencyReceiveBalance / 100).toFixed(2)"
+        />
+        <el-table-column
+          prop="toMechanismBalance"
+          label="结算后平台应收款金额"
+          :formatter="v => (v.toMechanismBalance / 100).toFixed(2)"
+        />
+        <el-table-column
+          prop="mechanismReceiveBalance"
+          label="累计支付到平台总金额"
+          :formatter="v => (v.mechanismReceiveBalance / 100).toFixed(2)"
+        />
+        <el-table-column
+          prop="toAgencyBalance"
+          label="结算后第三方应收款金额"
+          :formatter="v => (v.toAgencyBalance / 100).toFixed(2)"
+        />
+        <el-table-column prop="serviceName" label="结算状态">
+          <template #default="{ row }">
+            <div :class="statusList[row.status - 1].color">
+              {{ statusList[row.status - 1]?.label }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="mechanismRemark" label="备注">
+          <template #default="{ row }">
+            <div v-html="row.mechanismRemark" />
+          </template>
+        </el-table-column>
+        <el-table-column prop="serviceName" label="操作">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="handlePreviwDetail(row.id)">账单详情</el-button>
+            <el-button
+              type="primary"
+              link
+              @click="
+                state.rowData = { ...row };
+                state.visibleEeditRemark = true;
+              "
+              >备注信息</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-pagination
+        class="mt-4 justify-end"
+        background
+        layout="prev, pager, next"
+        :total="state.total"
+        :page-size="state.pageSize"
+        @current-change="changePage"
+      />
+      <PreviewPromotionalInvoiceDetailDialog
+        v-model:show="visiblePreview"
+        ref="previewPromotionalInvoiceDetailDialogRef"
+        @success="getList"
+      />
+    </div>
+    <el-dialog title="账单备注" v-model="state.visibleEeditRemark">
+      <div>
+        <Editor placeholder="请输入账单备注" v-model:value="state.rowData.remark" />
+      </div>
+      <template #footer>
+        <el-button @click="state.visibleEeditRemark = false">取 消</el-button>
+        <el-button type="primary" @click="handleSaveRemark">确 定</el-button>
+      </template>
+    </el-dialog>
+    <DialogRecordTable v-model:show="state.visibleRecordOfError" />
+  </div>
+</template>
+<script setup>
+import { ref, reactive } from "vue";
+
+import { statusList } from "./constant.ts";
+import request from "@/api";
+
+import PreviewPromotionalInvoiceDetailDialog from "./components/PreviewPromotionalInvoiceDetailDialog.vue";
+import DialogRecordTable from "./components/DialogRecordTable.vue";
+import Editor from "@/components/WangEditor/index.vue";
+
+import { ElMessage } from "element-plus";
+import { Search } from "@element-plus/icons-vue";
+
+const state = reactive({
+  visible: false,
+  visibleRecordOfError: false,
+  rowData: {},
+  query: {
+    page: 1,
+    pageSize: 10, //
+    agencyIds: [],
+    status: [],
+    key: ""
+  },
+  times: [],
+  list: [],
+  total: 0,
+  agencyList: [],
+  visibleEeditRemark: false
+});
+const visiblePreview = ref(false);
+const previewPromotionalInvoiceDetailDialogRef = ref(null);
+const getList = () => {
+  let start = undefined;
+  let end = undefined;
+  if (state.times?.length > 0) {
+    start = state.times[0] + " 00:00:00";
+    end = state.times[1] + " 23:59:59";
+  }
+  request
+    .get("archivesService/agency/orderBill/paginate", {
+      ...state.query,
+      agencyIds: state.query.agencyIds?.join(","),
+      status: state.query?.status?.join(","),
+      start,
+      end
+    })
+    .then(resp => {
+      state.list = resp.data.list;
+      state.total = resp.data.total;
+    });
+};
+const onSearch = () => {
+  state.query.page = 1;
+  getList();
+};
+const onReset = () => {
+  state.times = [];
+  state.query.agencyIds = [];
+  state.query.times = [];
+  state.query.serviceIds = [];
+  state.query.type = 0;
+  state.query.value = "";
+  state.query.key = "";
+  onSearch();
+};
+const handleSaveRemark = () => {
+  request
+    .post("/archivesService/agency/orderBill/updateRemark", {
+      id: state.rowData.id,
+      remark: state.rowData.remark
+    })
+    .then(resp => {
+      ElMessage.success(resp.message);
+      state.visibleEeditRemark = false;
+      getList();
+    });
+};
+
+const handlePreviwDetail = id => {
+  visiblePreview.value = true;
+  previewPromotionalInvoiceDetailDialogRef.value.initData(id);
+};
+
+const changePage = page => {
+  state.query.page = page;
+  getList();
+};
+
+const initData = () => {
+  getList();
+};
+
+initData();
+</script>
+<style lang="scss">
+.el-select-dropdown__item {
+  height: auto;
+  overflow: auto;
+}
+</style>

+ 296 - 1
src/views/promotion/order.vue

@@ -1,3 +1,298 @@
 <template>
-  <div>订单中心</div>
+  <div>
+    <div>
+      <el-card class="mb-4" shadow="never">
+        <div>
+          <el-row>
+            <el-col :span="18">
+              <el-space wrap>
+                <el-select
+                  v-model="state.query.type"
+                  placeholder="全部订单类型"
+                  clearable
+                  collapse-tags
+                  multiple
+                  filterable
+                  class="w-[180px]"
+                >
+                  <el-option label="会员卡订单" :value="1" />
+                </el-select>
+                <el-select
+                  v-model="state.query.agencyIds"
+                  placeholder="全部渠道"
+                  multiple
+                  collapse-tags
+                  class="w-[180px]"
+                  filterable
+                >
+                  <el-option v-for="item in channelList" :key="item.id" :label="item.name" :value="item.id"> </el-option>
+                </el-select>
+
+                <el-select
+                  v-model="state.query.status"
+                  placeholder="全部订单状态"
+                  clearable
+                  collapse-tags
+                  multiple
+                  filterable
+                  class="w-[180px]"
+                >
+                  <el-option label="微信支付" :value="1" />
+                </el-select>
+                <el-select
+                  v-model="state.query.payType"
+                  placeholder="全部支付方式"
+                  clearable
+                  collapse-tags
+                  multiple
+                  filterable
+                  class="w-[180px]"
+                >
+                  <el-option v-for="item in statusList" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+                <el-select
+                  v-model="state.query.paymentType"
+                  placeholder="全部支付款流向"
+                  clearable
+                  collapse-tags
+                  multiple
+                  filterable
+                  class="w-[180px]"
+                >
+                  <el-option label="平台" :value="1" />
+                  <el-option label="第三方" :value="2" />
+                </el-select>
+                <el-date-picker
+                  v-model="state.times"
+                  type="daterange"
+                  clearable
+                  range-separator="至"
+                  value-format="YYYY-MM-DD"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                />
+                <el-button class="ml-2" type="primary" @click="onSearch">筛选</el-button>
+                <el-button type="primary" plain @click="onReset">重置</el-button>
+              </el-space>
+            </el-col>
+            <el-col :span="6">
+              <div class="flex justify-end text-right">
+                <el-select v-model="state.key" placeholder="请选择" style="width: 150px">
+                  <el-option label="订单号" value="key" />
+                  <el-option label="用户ID" value="user" />
+                  <el-option label="用户手机尾号" value="mobile" />
+                  <el-option label="商品名称" value="goodsName" />
+                </el-select>
+                <el-input
+                  v-model="state.query.value"
+                  placeholder="请输入关联业务单号"
+                  v-if="state.key == 'orderNo'"
+                  style="width: 195px"
+                />
+                <el-input v-else v-model="state.value" placeholder="请输入" style="width: 195px" />
+                <el-button :icon="Search" @click="onSearch">查询</el-button>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+      </el-card>
+      <el-table :data="state.list" style="width: 100%" border>
+        <el-table-column prop="sn" label="订单信息">
+          <template #default="{ row }">
+            <div>
+              <span>{{ row.sn }}</span>
+              <el-tag>【会员卡订单】</el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="promotionSite" label="订单来源信息">
+          <template #default="{ row }">
+            <div>
+              <div>{{ row.agency.nickname }}</div>
+              <div style="color: #a8abb2">[ {{ row.promotionSite.name }} ]</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="promotionGoods" :formatter="v => v.promotionGoods.memberCardName" label="订单商品名称" />
+        <el-table-column prop="promotionGoods" :formatter="v => v.promotionGoods.memberCardName" label="实付金额">
+          <template #default="{ row }">
+            <div>
+              {{ row.promotionGoods.price / 100 }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" :formatter="v => v.promotionGoods.memberCardName" label="订单状态">
+          <template #default="{ row }">
+            <div :class="statusList[row.status - 1].color">
+              {{ statusList[row.status - 1]?.label }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createdAt" label="支付时间" />
+        <el-table-column prop="payType" label="支付方式" :formatter="() => '微信支付'" />
+        <el-table-column prop="paymentType" label="支付款流向">
+          <template #default="{ row }">
+            <div>
+              {{ row.paymentType === 1 ? "平台" : row?.agency?.nickname }}
+            </div>
+            <div style="color: #a8abb2">
+              [ 收款ID:{{ row.mchId }} ]
+              <br />
+              [ 账号ID: {{ row.agencyId }} ]
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="archives" label="用户信息">
+          <template #default="{ row }">
+            <div>
+              {{ row.archives.name }}
+            </div>
+            <div>
+              <div v-for="(item, index) in row.archives.accounts" :key="index" class="space-x-4">
+                <span class="inline-block whitespace-nowrap w-[4em]">{{ accountTypes[item.type] }}</span>
+                <span>【{{ item.account }}】</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作">
+          <template #default="{ row }">
+            <el-button
+              type="primary"
+              link
+              @click="
+                visibleViewDetail = true;
+                state.rowData = row;
+              "
+              >详情</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-pagination
+        class="mt-4 justify-end"
+        background
+        layout="prev, pager, next"
+        :total="state.total"
+        :page-size="state.pageSize"
+        @current-change="changePage"
+      />
+    </div>
+    <el-drawer v-model="visibleViewDetail" direction="rtl" title="订单详情" @open="handleOpenViewDetail">
+      <PerviewDetail :orderSn="state.rowData.sn" ref="perviewDetailRef" @success="getList()" />
+    </el-drawer>
+  </div>
 </template>
+<script setup>
+// import { request } from "@/utils";
+import { ref, reactive } from "vue";
+import { Search } from "@element-plus/icons-vue";
+import PerviewDetail from "./components/PerviewDetail.vue";
+import { statusList, accountTypes } from "./constant.ts";
+import { promotionSitePaginate } from "@/api/modules/promotion";
+import http from "@/api";
+
+const perviewDetailRef = ref();
+const visibleViewDetail = ref(false);
+const state = reactive({
+  visible: false,
+  visibleSystemInterest: false,
+  rowData: {},
+  key: "user",
+  query: {
+    page: 1,
+    pageSize: 10, //
+    type: 0,
+    status: [],
+    payType: [],
+    paymentType: [],
+    agencyIds: [],
+    name: "", // 搜索项目:user: 用户;outTradeNo:第三方订单号
+    key: ""
+  },
+  times: [],
+  list: [],
+  total: 0
+});
+
+const memberCardList = ref([]);
+const channelList = ref([]);
+const getList = () => {
+  let start = undefined;
+  let end = undefined;
+  if (state.times?.length > 0) {
+    start = state.times[0];
+    end = state.times[1];
+  }
+  const agencyIds =
+    state.query.agencyIds.length &&
+    state.query.agencyIds.map(v => {
+      return v[1];
+    });
+  http
+    .get("archivesService/agency/order/paginate", {
+      ...state.query,
+      agencyIds: agencyIds ? agencyIds?.join(",") : undefined,
+      status: state.query.status?.join(","),
+      payType: state.query.payType?.join(","),
+      paymentType: state.query.paymentType?.join(","),
+      start,
+      end
+    })
+    .then(resp => {
+      state.list = resp.data.list;
+      state.total = resp.data.total;
+    });
+};
+
+const getMemberCardList = () => {
+  http
+    .get("archivesService/agency/promotionGoods/list", {
+      params: { status: 1 }
+    })
+    .then(resp => {
+      memberCardList.value = resp.data;
+    });
+};
+const handleOpenViewDetail = () => {
+  perviewDetailRef.value.initData();
+};
+const onSearch = () => {
+  state.query.page = 1;
+  getList();
+};
+const onReset = () => {
+  state.times = [];
+  state.query.cardIds = [];
+  state.query.times = [];
+  state.query.agencyIds = [];
+  state.query.type = 0;
+  state.query.value = "";
+  state.query.key = "";
+  onSearch();
+};
+const changePage = page => {
+  state.query.page = page;
+  getList();
+};
+const getChannleList = () => {
+  promotionSitePaginate({
+    page: 1,
+    pageSize: 1000
+  }).then(res => {
+    channelList.value = res.data.list;
+  });
+};
+const initData = () => {
+  getMemberCardList();
+  getList();
+  getChannleList();
+};
+initData();
+</script>
+<style lang="scss">
+.el-select-dropdown__item {
+  height: auto;
+  overflow: auto;
+}
+</style>