Ver código fonte

Merge branch 'sp' into test

zqwang 1 ano atrás
pai
commit
b3c6d4c966
58 arquivos alterados com 3885 adições e 369 exclusões
  1. 1 0
      package.json
  2. 4 11
      src/App.vue
  3. 15 1
      src/api/applet/index.ts
  4. 26 0
      src/api/backConfig/juliangPlus/index.ts
  5. 82 0
      src/api/officialAccount/keywordReply/index.ts
  6. 7 0
      src/api/officialAccount/officialList/index.ts
  7. 3 4
      src/api/promotion/ranse.ts
  8. 59 0
      src/components/Draggable/index.vue
  9. 17 15
      src/components/admin/table/search.vue
  10. 0 2
      src/layout/components/content.vue
  11. 26 13
      src/router/modules/charge.ts
  12. 153 0
      src/styles/devices.css
  13. 2 0
      src/styles/index.scss
  14. 1 1
      src/styles/var.scss
  15. 32 12
      src/views/appletManage/miniProgramList/index.vue
  16. 102 0
      src/views/appletManage/miniProgramList/ranse/index.vue
  17. 1 1
      src/views/customer/audience/userDetail/tables/consume.vue
  18. 1 1
      src/views/customer/audience/userDetail/tables/readLog.vue
  19. 83 76
      src/views/dashboard/dataStatistics/notices.vue
  20. 2 1
      src/views/dataStatistics/shortStatistical/excelTitle.ts
  21. 18 0
      src/views/dataStatistics/shortStatistical/index.vue
  22. 0 1
      src/views/notice/mynotice/dataStatistics/notices.vue
  23. 1 1
      src/views/notice/mynotice/dataStatistics/todayData.vue
  24. 2 2
      src/views/notice/mynotice/index.vue
  25. 174 0
      src/views/officialAccount/attentionReply/form/create.vue
  26. 102 0
      src/views/officialAccount/attentionReply/form/generateLink.vue
  27. 159 0
      src/views/officialAccount/attentionReply/index.vue
  28. 122 0
      src/views/officialAccount/components/configPublic.vue
  29. 216 0
      src/views/officialAccount/keywordReply/form/create.vue
  30. 108 0
      src/views/officialAccount/keywordReply/form/generateLink.vue
  31. 240 0
      src/views/officialAccount/keywordReply/index.vue
  32. 249 0
      src/views/officialAccount/newsService/form/create.vue
  33. 102 0
      src/views/officialAccount/newsService/form/generateLink.vue
  34. 199 0
      src/views/officialAccount/newsService/index.vue
  35. 41 0
      src/views/officialAccount/officialList/index.vue
  36. 459 0
      src/views/officialAccount/publicCustomMenu/index.vue
  37. 1 1
      src/views/ordersManage/tabs/rechargeList/userDetail/tables/consume.vue
  38. 1 1
      src/views/ordersManage/tabs/rechargeList/userDetail/tables/readLog.vue
  39. 1 1
      src/views/payBack/juliangAccount/tabs/advertiserList/form/create.vue
  40. 0 2
      src/views/payBack/juliangAccount/tabs/advertiserList/form/paybackConfig.vue
  41. 9 8
      src/views/payBack/juliangAccount/tabs/advertiserList/index.vue
  42. 1 2
      src/views/payBack/juliangAccount/tabs/logList/excelTitle.ts
  43. 1 1
      src/views/payBack/juliangAccount/tabs/logList/index.vue
  44. 32 0
      src/views/payBack/juliangPlus/index.vue
  45. 231 0
      src/views/payBack/juliangPlus/tabs/advertiserList/form/create.vue
  46. 235 0
      src/views/payBack/juliangPlus/tabs/advertiserList/form/paybackConfig.vue
  47. 159 0
      src/views/payBack/juliangPlus/tabs/advertiserList/index.vue
  48. 16 0
      src/views/payBack/juliangPlus/tabs/logList/excelTitle.ts
  49. 284 0
      src/views/payBack/juliangPlus/tabs/logList/index.vue
  50. 57 15
      src/views/promotion/promotionList/form/backConfig.vue
  51. 3 3
      src/views/promotion/promotionList/form/create.vue
  52. 17 6
      src/views/promotion/promotionList/index.vue
  53. 0 122
      src/views/promotion/ranse/index.vue
  54. 0 56
      src/views/settleManage/financialControl/tabs/rechargeSettle/excelTitle.ts
  55. 2 2
      src/views/videoManage/videoLibraryList/detail.vue
  56. 1 1
      src/views/videoManage/videoLibraryList/form/detailForm.vue
  57. 21 4
      src/views/videoManage/videoLibraryList/form/uploadVideo.vue
  58. 4 2
      vite.config.js

+ 1 - 0
package.json

@@ -31,6 +31,7 @@
     "terser": "^5.16.5",
     "vue": "^3.2.47",
     "vue-clipboard3": "^2.0.0",
+    "vue-draggable-next": "^2.2.1",
     "vue-i18n": "9",
     "vue-router": "4.1.6",
     "vuedraggable": "^4.1.0",

+ 4 - 11
src/App.vue

@@ -54,25 +54,18 @@ function getBreadcrumbs(newRoute: RouteLocationNormalizedLoaded) {
 
 <style lang="scss">
 ::-webkit-scrollbar {
-  width: 6px;
-  height: 8px;
-  background-color: #ebeef5;
+  width: 5px;
+  height: 5px;
 }
 
 ::-webkit-scrollbar-thumb {
   border-radius: 30px;
-  box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
-  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
-  background-color: #ccc;
-}
-
-::-webkit-scrollbar-thumb:hover {
-  background-color: rgba($color: #000000, $alpha: .5);
+  box-shadow: inset 0 0 6px rgba(0, 0, 0, .2);
+  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .2);
 }
 
 ::-webkit-scrollbar-track {
   box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
   border-radius: 3px;
-  background: rgba(255, 255, 255, 1);
 }
 </style>

+ 15 - 1
src/api/applet/index.ts

@@ -63,8 +63,22 @@ export function manageMiniprogramAllocationDetail(
 }
 
 /**
- *
+ *获取微信授权
  */
 export function channelOpenPlatformPreauth(params?: object) {
   return http.get(`/channel/openPlatform/preauth`, params);
 }
+
+/**
+ * 获取染色时间
+ */
+export function getRanseDuration(params: object) {
+  return http.get('/manage/miniprogram/ranseConfig/getRanseDuration', params);
+}
+
+/**
+ * 设置染色时间
+ */
+export function setRanseDuration(params: object) {
+  return http.post('/manage/miniprogram/ranseConfig/setRanseDuration', params);
+}

+ 26 - 0
src/api/backConfig/juliangPlus/index.ts

@@ -0,0 +1,26 @@
+import http from '@/api/http';
+/**
+ * 巨量2.0事件-列表
+ */
+export function callbackJlEventList(params: object) {
+  return http.get('/callback/jlEvent/list', params);
+}
+/**
+ * 巨量2.0事件-金额项卡比例选项
+ */
+export function optionsJLEventCustomRate(params?: object) {
+  return http.get('/options/JLEventCustomRate', params);
+}
+
+/**
+ * 巨量2.0事件-添加
+ */
+export function callbackJlEventAdd(params: object) {
+  return http.post('/callback/jlEvent/add', params);
+}
+/**
+ * 巨量2.0事件-更新
+ */
+export function callbackJlEventUpdate(params: object) {
+  return http.post(`/callback/jlEvent/update`, params);
+}

+ 82 - 0
src/api/officialAccount/keywordReply/index.ts

@@ -0,0 +1,82 @@
+import http from '@/api/http';
+/**
+ * 关键词-列表
+ */
+export function wechatPlatformKeywordList(params: object) {
+  return http.get(`/wechatPlatform/keyword/list`, params);
+}
+/**
+ * 关键词-详情
+ */
+export function wechatPlatformKeywordDetail(
+  id: number | string,
+  params?: object
+) {
+  return http.get(`/wechatPlatform/keyword/detail/${id}`, params);
+}
+/**
+ * 关键词-配置公众号列表
+ */
+export function wechatPlatformKeywordAuthList(
+  id: number | string,
+  params?: object
+) {
+  return http.get(`/wechatPlatform/keyword/auth_list/${id}`, params);
+}
+
+/**
+ * 获取小程序播放页面链接
+ */
+export function wechatPlatformCommonGetPlayUrl(params: object) {
+  return http.post('/wechatPlatform/common/get_play_url', params);
+}
+/**
+ * 关键词-添加
+ */
+export function wechatPlatformKeywordAdd(params: object) {
+  return http.post('/wechatPlatform/keyword/add', params);
+}
+/**
+ * 关键词-编辑保存
+ */
+export function wechatPlatformKeywordEdit(id: number | string, params: object) {
+  return http.post(`/wechatPlatform/keyword/edit/${id}`, params);
+}
+/**
+ * 关键词-删除
+ */
+export function wechatPlatformKeywordDel(params: object) {
+  return http.post(`/wechatPlatform/keyword/del`, params);
+}
+/**
+ * 关键词-分配公众号
+ */
+export function wechatPlatformKeywordAllocation(
+  id: number | string,
+  params: object
+) {
+  return http.post(`/wechatPlatform/keyword/allocation/${id}`, params);
+}
+
+/**
+ * 关键字-全局设置获取
+ */
+export function wechatPlatformKeywordGetConfig(
+  miniprogramId: number | string,
+  params?: object
+) {
+  return http.get(`/wechatPlatform/keyword/getConfig/${miniprogramId}`, params);
+}
+
+/**
+ * 关键字-全局设置更新
+ */
+export function wechatPlatformKeywordGetConfigSet(
+  miniprogramId: number | string,
+  params: object
+) {
+  return http.post(
+    `/wechatPlatform/keyword/getConfig/${miniprogramId}`,
+    params
+  );
+}

+ 7 - 0
src/api/officialAccount/officialList/index.ts

@@ -0,0 +1,7 @@
+import http from '@/api/http';
+/**
+ * 公众号列表
+ */
+export function wechatPlatformOfficialAccountList(params: object) {
+  return http.get(`/wechatPlatform/officialAccount/list`, params);
+}

+ 3 - 4
src/api/promotion/ranse.ts

@@ -3,14 +3,13 @@ import http from '@/api/http';
 /**
  * 获取染色时间
  */
-export function getRanseDuration() {
-  return http.get('/tuiguang/ranseConfig/getRanseDuration');
+export function getRanseDuration(params: object) {
+  return http.get('/tuiguang/ranseConfig/getRanseDuration', params);
 }
 
 /**
  * 设置染色时间
  */
 export function setRanseDuration(params: object) {
-  return http.post('/tuiguang/ranseConfig/setRanseDuration',params);
+  return http.post('/tuiguang/ranseConfig/setRanseDuration', params);
 }
-

+ 59 - 0
src/components/Draggable/index.vue

@@ -0,0 +1,59 @@
+<template>
+  <draggable :list="list" v-bind="$attrs" :class="classString" :disabled="disabled" @end="onEnd">
+    <slot v-for="(item, index) in list" :key="item.id" :item="item" :index="index" />
+  </draggable>
+</template>
+
+<script lang="ts">
+import { VueDraggableNext } from 'vue-draggable-next'
+import { defineComponent } from 'vue'
+export default defineComponent({
+  components: {
+    draggable: VueDraggableNext
+  },
+  props: {
+    headerText: {
+      type: String,
+      default: 'header'
+    },
+    list: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    },
+    classString: {
+      type: String,
+      default: 'board-column-content'
+    },
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    options: {
+      type: Object,
+      default: () => {
+        return null
+      }
+    }
+
+  },
+  setup(props, { emit }) {
+    console.log(props)
+    const onEnd = (e) => {
+      console.log(e, 'onEndonEndonEnd');
+      emit('end', e)
+    }
+    return {
+      onEnd
+    }
+  }
+})
+
+</script>
+
+<style lang="scss" scoped>
+.board-column-content {
+  width: 100%;
+}
+</style>

+ 17 - 15
src/components/admin/table/search.vue

@@ -1,19 +1,21 @@
 <template>
-  <div class="w-full min-h-0 pt-5 pl-5 pr-5 bg-white rounded-lg dark:bg-regal-dark">
-    <el-form :inline="true" @submit.native.prevent>
-      <slot name="body" />
-      <el-form-item>
-        <el-button type="primary" @click="search()">
-          <Icon name="magnifying-glass" className="w-4 h-4 mr-1 -ml-1" />
-          搜索
-        </el-button>
-        <el-button @click="reset()">
-          <Icon name="arrow-path" className="w-4 h-4 mr-1 -ml-1" />
-          重置
-        </el-button>
-        <slot name="extra_button" />
-      </el-form-item>
-    </el-form>
+  <div class="w-full min-h-0 pl-5 pr-5 bg-white rounded-lg dark:bg-regal-dark">
+    <div class="w-full min-h-0 pt-5 pl-5 pr-5 bg-white rounded-lg dark:bg-regal-dark">
+      <el-form :inline="true" @submit.native.prevent>
+        <slot name="body" />
+        <el-form-item>
+          <el-button type="primary" @click="search()">
+            <Icon name="magnifying-glass" className="w-4 h-4 mr-1 -ml-1" />
+            搜索
+          </el-button>
+          <el-button @click="reset()">
+            <Icon name="arrow-path" className="w-4 h-4 mr-1 -ml-1" />
+            重置
+          </el-button>
+          <slot name="extra_button" />
+        </el-form-item>
+      </el-form>
+    </div>
     <slot name="content" />
   </div>
 </template>

+ 0 - 2
src/layout/components/content.vue

@@ -7,8 +7,6 @@
     <!-- Container -->
     <div class="h-screen max-w-full p-1 overflow-auto sm:p-2 sm:overflow-x-hidden" id="content">
       <router-view />
-
-      <!--<div class="w-full h-10 mt-2 leading-10 text-center text-gray-400">内容中台管理系统 @copyright 2018 ~ {{ year }}</div>-->
     </div>
   </div>
 </template>

+ 26 - 13
src/router/modules/charge.ts

@@ -5,34 +5,47 @@
 //   {
 //     path: '/charge',
 //     component: () => import('@/layout/index.vue'),
-//     meta: { title: '数据统计', icon: 'user' },
+//     meta: { title: '回传管理', icon: 'user' },
 //     children: [
 //       {
 //         path: 'index',
 //         name: 'user-account11',
-//         meta: { title: '短剧统计', icon: 'home' },
+//         meta: { title: '巨量2.0事件-微信小程序', icon: 'home' },
+//         component: () => import('@/views/payBack/juliangPlus/index.vue')
+//       },
+//       {
+//         path: 'index1',
+//         name: 'user-account',
+//         meta: { title: '被关注回复', icon: 'home' },
 //         component: () =>
-//           import('@/views/dataStatistics/shortStatistical/index.vue')
+//           import('@/views/officialAccount/attentionReply/index.vue')
 //       },
 //       {
 //         path: 'index3',
-//         name: 'user-account112',
-//         meta: { title: '投入产出', icon: 'home' },
+//         name: 'user-account33',
+//         meta: { title: '关键字回复', icon: 'home' },
 //         component: () =>
-//           import('@/views/dataStatistics/roiStatistical/index.vue')
+//           import('@/views/officialAccount/keywordReply/index.vue')
 //       },
 //       {
-//         path: 'index1',
-//         name: 'user-account',
-//         meta: { title: '充值统计', icon: 'home' },
+//         path: 'index4',
+//         name: 'user-account44',
+//         meta: { title: '公众号列表', icon: 'home' },
 //         component: () =>
-//           import('@/views/dataStatistics/rechargeStatistics/index.vue')
+//           import('@/views/officialAccount/officialList/index.vue')
+//       },
+//       {
+//         path: 'index5',
+//         name: 'user-account55',
+//         meta: { title: '客服消息', icon: 'home' },
+//         component: () => import('@/views/officialAccount/newsService/index.vue')
 //       },
 //       {
 //         path: 'index2',
-//         name: 'user-account99',
-//         meta: { title: '微信提审', icon: 'home', hidden: false },
-//         component: () => import('@/views/videoManage/wechatAudit/index.vue')
+//         name: 'user-account123',
+//         meta: { title: '菜单', icon: 'home', hidden: true },
+//         component: () =>
+//           import('@/views/officialAccount/publicCustomMenu/index.vue')
 //       }
 //     ]
 //   }

+ 153 - 0
src/styles/devices.css

@@ -0,0 +1,153 @@
+/*! Devices.css v0.1.16 | MIT License | github.com/picturepan2/devices.css */
+.device,
+.device *,
+.device ::after,
+.device ::before,
+.device::after,
+.device::before {
+  box-sizing: border-box;
+  display: block;
+}
+.device {
+  position: relative;
+  transform: scale(1);
+  z-index: 1;
+}
+.device .device-frame {
+  z-index: 1;
+}
+.device .device-content {
+  background-color: #fff;
+  background-position: center center;
+  background-size: cover;
+  object-fit: cover;
+  position: relative;
+}
+.device-iphone-x {
+  height: 668px;
+  width: 100%;
+}
+.device-iphone-x .device-frame {
+  background: #222;
+  border-radius: 40px;
+  box-shadow: inset 0 0 2px 2px #c8cacb, inset 0 0 0 -6px #e2e3e4;
+  height: 668px;
+  padding: 5px;
+  width: 100%;
+}
+.device-iphone-x .device-content {
+  border-radius: 40px;
+  height: 100%;
+  width: 100%;
+  padding-top: 30px;
+}
+.device-iphone-x .device-stripe::after,
+.device-iphone-x .device-stripe::before {
+  border: solid rgba(51, 51, 51, 0.25);
+  border-width: 0 7px;
+  content: "";
+  height: 7px;
+  left: 0;
+  position: absolute;
+  width: 100%;
+  z-index: 9;
+}
+.device-iphone-x .device-stripe::after {
+  top: 85px;
+}
+.device-iphone-x .device-stripe::before {
+  bottom: 85px;
+}
+.device-iphone-x .device-header {
+  background: #222;
+  border-bottom-left-radius: 20px;
+  border-bottom-right-radius: 20px;
+  height: 30px;
+  left: 50%;
+  margin-left: -102px;
+  position: absolute;
+  top: 5px;
+  width: 204px;
+}
+.device-iphone-x .device-header::after,
+.device-iphone-x .device-header::before {
+  content: "";
+  height: 10px;
+  position: absolute;
+  top: 0;
+  width: 10px;
+}
+.device-iphone-x .device-header::after {
+  background: radial-gradient(
+    circle at bottom left,
+    transparent 0,
+    transparent 75%,
+    #222 75%,
+    #222 100%
+  );
+  left: -10px;
+}
+.device-iphone-x .device-header::before {
+  background: radial-gradient(
+    circle at bottom right,
+    transparent 0,
+    transparent 75%,
+    #222 75%,
+    #222 100%
+  );
+  right: -10px;
+}
+.device-iphone-x .device-sensors::after,
+.device-iphone-x .device-sensors::before {
+  content: "";
+  position: absolute;
+}
+.device-iphone-x .device-sensors::after {
+  background: #444;
+  border-radius: 3px;
+  height: 6px;
+  left: 50%;
+  margin-left: -25px;
+  top: 18px;
+  width: 50px;
+}
+.device-iphone-x .device-sensors::before {
+  background: #444;
+  border-radius: 50%;
+  height: 14px;
+  left: 50%;
+  margin-left: 40px;
+  top: 13px;
+  width: 14px;
+}
+.device-iphone-x .device-btns {
+  background: #c8cacb;
+  height: 32px;
+  left: -3px;
+  position: absolute;
+  top: 115px;
+  width: 3px;
+}
+.device-iphone-x .device-btns::after,
+.device-iphone-x .device-btns::before {
+  background: #c8cacb;
+  content: "";
+  height: 62px;
+  left: 0;
+  position: absolute;
+  width: 3px;
+}
+.device-iphone-x .device-btns::after {
+  top: 60px;
+}
+.device-iphone-x .device-btns::before {
+  top: 140px;
+}
+.device-iphone-x .device-power {
+  background: #c8cacb;
+  height: 100px;
+  position: absolute;
+  right: -3px;
+  top: 200px;
+  width: 3px;
+}

+ 2 - 0
src/styles/index.scss

@@ -13,3 +13,5 @@
 
 
 @import 'iphone.css';
+
+@import 'devices.css';

+ 1 - 1
src/styles/var.scss

@@ -1,5 +1,5 @@
 :root {
-    --el-menu-base-level-padding: 20px;
+    --el-menu-base-level-padding: 15px;
     // 后台自定义
     // el-table
     --el-table-border-radius: 8px;

+ 32 - 12
src/views/appletManage/miniProgramList/index.vue

@@ -62,22 +62,29 @@
           <el-table-column prop="type_name" label="类型" />
           <el-table-column label="操作" width="200" fixed="right">
             <template #default="scope">
-              <el-button link type="primary" size="small" @click="opendepots(scope.row)"
+              <el-button link type="primary" size="small" @click="openType('depotsVisible', scope.row)"
                 v-action="'manage.miniprogram.allocationStore'">分配</el-button>
               <el-button link type="primary" size="small" @click="open(scope.row.id)"
                 v-action="'manage.miniprogram.update'">编辑</el-button>
+              <br />
+              <el-button link type="primary" size="small" @click="openType('ranseVisible', scope.row)"
+                v-action="'manage.miniprogram.setRanseDuration'">染色规则配置</el-button>
             </template>
           </el-table-column>
         </el-table>
         <Paginate />
       </div>
       <Dialog width="800px" v-model="depotsVisible" title="分配" destroy-on-close>
-        <depotsTransfer @close="closeDeptos()" :primary="depotsData"></depotsTransfer>
+        <depotsTransfer @close="closeType('depotsVisible')" :primary="current"></depotsTransfer>
       </Dialog>
 
       <Dialog v-model="visible" :title="title" destroy-on-close>
         <Create @close="close(search)" :primary="id" :api="api" />
       </Dialog>
+
+      <Dialog v-model="ranseVisible" title="染色规则配置" destroy-on-close>
+        <Ranse @close="closeType('ranseVisible')" :primary="current" />
+      </Dialog>
     </div>
   </div>
 </template>
@@ -89,27 +96,40 @@ import depotsTransfer from './form/depotsTransfer.vue';
 import { useGetList } from '@/hook/curd/useGetList';
 import { useOpen } from '@/hook/curd/useOpen';
 import { manageMiniprogramCompanylist, manageMiniprogramTypelist } from '@/api/applet/index'
-
+import Ranse from './ranse/index.vue'
 const api = 'manage/miniprogram/index';
 const depotsVisible = ref(false)
-const depotsData = ref({})
+const ranseVisible = ref(false)
+const current = ref({})
 const companylist = ref([])
 const miniprogramTypelist = ref([])
-
 const { data, query, search, reset, loading } = useGetList(api);
 const { open, close, title, visible, id } = useOpen();
 const rolesIdentify = inject('rolesIdentify')
 const tableData = computed(() => data.value?.data);
 const isShowSecret = computed(() => !rolesIdentify.value.includes('company') && !rolesIdentify.value.includes('optimizer'))
-
-const opendepots = (data) => {
-  depotsVisible.value = true
-  depotsData.value = data
-}
-const closeDeptos = () => {
-  depotsVisible.value = false
+const closeType = (type: string) => {
+  switch (type) {
+    case 'depotsVisible':
+      depotsVisible.value = false
+      break;
+    case 'ranseVisible':
+      ranseVisible.value = false
+      break;
+  }
   search()
 }
+const openType = (type: string, data?: object) => {
+  current.value = data || {}
+  switch (type) {
+    case 'depotsVisible':
+      depotsVisible.value = true
+      break;
+    case 'ranseVisible':
+      ranseVisible.value = true
+      break;
+  }
+}
 const init = () => {
   manageMiniprogramCompanylist().then(res => {
     companylist.value = res.data

+ 102 - 0
src/views/appletManage/miniProgramList/ranse/index.vue

@@ -0,0 +1,102 @@
+<template>
+  <div class="page">
+    <div class="header">
+      <el-text class="text-notice">注意事项</el-text>
+      <el-text class="text-notice">1.充值用户>=n分钟连续未登录,如果点击新连接,会归属于新链接的优化师</el-text>
+      <el-text class="text-notice">2.未充值用户>=n分钟连续未登录,如果点击新连接,会归属于新链接的优化师</el-text>
+    </div>
+    <div class="set-box">
+      <div class="set-box-item">
+        <el-text class="lab-txt"> <label style="color: red;margin-right: 10px;">*</label>非充值用户:</el-text>
+        <el-input-number v-model="setData.no_charge_user_duration" :min="1" />
+        <el-text style="margin-left: 10px;color: #555;">分</el-text>
+      </div>
+      <div class="set-box-item">
+        <el-text class="lab-txt"> <label style="color: red;margin-right: 10px;">*</label>充值用户:</el-text>
+        <el-input-number v-model="setData.charge_user_duration" :min="1" />
+        <el-text style="margin-left: 10px;color: #555;">分</el-text>
+      </div>
+      <div class="flex justify-end">
+        <el-button style="margin-left: 140px;margin-top: 30px;" type="primary" @click="saveSetting">确定</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { getRanseDuration, setRanseDuration } from "@/api/applet/index";
+const rolesIdentify = inject('rolesIdentify');
+const setData = ref({})
+const props = defineProps({
+  primary: String | Number,
+});
+const emit = defineEmits(['close']);
+const saveSetting = () => {
+  if (!setData.value.charge_user_duration) {
+    ElMessage.error('请填写充值用户染色时间');
+    return false;
+  }
+  if (!setData.value.no_charge_user_duration) {
+    ElMessage.error('请填写未充值用户染色时间');
+    return false;
+  }
+  if (setData.value.charge_user_duration <= setData.value.no_charge_user_duration) {
+    ElMessage.error('充值用户染色时间应大于非充值用户染色时间');
+    return false;
+  }
+  setRanseDuration({ miniprogram_id: props.primary?.id, ...setData.value }).then(res => {
+    ElMessage.success(res.message);
+    emit('close')
+  })
+}
+
+const getSetData = (params: object) => {
+  getRanseDuration(params).then(res => {
+    setData.value = res.data;
+  })
+}
+
+if (props.primary) {
+  console.log(props.primary, 'props.primaryprops.primary');
+  getSetData({ miniprogram_id: props.primary?.id });
+}
+onMounted(() => {
+
+});
+</script>
+
+<style lang="scss" scoped>
+.lab-txt {
+  display: inline-table;
+  width: 120px;
+  color: #555;
+  text-align: right;
+}
+
+.page {
+  background-color: #fff;
+}
+
+.header {
+  display: flex;
+  flex-direction: column;
+  background-color: #fcd3d3;
+  padding: 20px 30px;
+}
+
+.text-notice {
+  width: 100%;
+  color: #f56c6c;
+}
+
+.set-box {
+  margin-top: 20px;
+  display: flex;
+  flex-direction: column;
+}
+
+.set-box-item {
+  width: 100%;
+  margin-top: 20px;
+}
+</style>

+ 1 - 1
src/views/customer/audience/userDetail/tables/consume.vue

@@ -6,7 +6,7 @@
         <el-table-column label="说明">
           <template #default="scope">
             <div>短剧:<span>{{ scope.row.video_name }}</span></div>
-            <div>章节名称:<span>{{ scope.row.series_name }}</span></div>
+            <div>剧集名称:<span>{{ scope.row.series_name }}</span></div>
           </template>
         </el-table-column>
         <el-table-column prop="charge_coin_cost" label="看剧币支付" />

+ 1 - 1
src/views/customer/audience/userDetail/tables/readLog.vue

@@ -6,7 +6,7 @@
         <el-table-column prop="video_id" label="短剧ID">
         </el-table-column>
         <el-table-column prop="video_name" label="短剧名称" />
-        <el-table-column prop="video_series_sequence" label="章节名称" />
+        <el-table-column prop="video_series_sequence" label="剧集名称" />
       </el-table>
       <Paginate />
     </div>

+ 83 - 76
src/views/dashboard/dataStatistics/notices.vue

@@ -1,83 +1,90 @@
 <template>
-	<div class="header-title"> <h3>最新通知 </h3> <h4 v-show="list.length > 0" @click="go2page">查看全部</h4></div>
-	<div class="noticebody">
-		<div class="notice-item" @click="titleClick(item)"  v-for="(item,index) in list" :key="index" >
-		 <el-badge v-if="item.is_read == 0" is-dot class="item">
-		    <el-image class="logo" src="./src/assets/icons/notice.png"></el-image>
-		  </el-badge>
-			   <el-image class="logo"  v-else   src="./src/assets/icons/notice.png"></el-image>
-			<label class="notices">{{item.title}}</label>
-		</div>
-	</div>
-	<div v-show="list.length < 1"  style="display: flex;justify-content: center;">
-			<label style="color: darkgray;"> 暂无新通知</label>
-	</div>
-	<el-dialog draggable v-model="centerDialogVisible" :title="notice.title" width="30%" center>
-	  <div class="flex flex-wrap break-all" v-html="notice.content"></div>
-	  <template #footer>
-	    <span class="dialog-footer">
-	      <el-button @click="readNotice">我知道了</el-button>
-	    </span>
-	  </template>
-	</el-dialog>
+  <div class="header-title">
+    <h3>最新通知 </h3>
+    <h4 v-show="list.length > 0" @click="go2page" class="cursor-pointer">查看全部</h4>
+  </div>
+  <div class="noticebody">
+    <div class="notice-item" @click="titleClick(item)" v-for="(item, index) in list" :key="index">
+      <el-badge v-if="item.is_read == 0" is-dot class="item">
+        <el-image class="logo" src="./src/assets/icons/notice.png"></el-image>
+      </el-badge>
+      <el-image class="logo" v-else src="./src/assets/icons/notice.png"></el-image>
+      <label class="notices">{{ item.title }}</label>
+    </div>
+  </div>
+  <div v-show="list.length < 1" style="display: flex;justify-content: center;">
+    <label style="color: darkgray;"> 暂无新通知</label>
+  </div>
+  <el-dialog draggable v-model="centerDialogVisible" :title="notice.title" width="30%" center>
+    <div class="flex flex-wrap break-all" v-html="notice.content"></div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="readNotice">我知道了</el-button>
+      </span>
+    </template>
+  </el-dialog>
 </template>
 
 <script lang="ts" setup>
-	import { ref } from 'vue'
-	import { Bell } from '@element-plus/icons-vue';
-	import router from '@/router';
-	import { noticeListMine, noticeDetail, noticeRead } from '@/api/notice/index';
-	const centerDialogVisible = ref(false)
-	const notice = ref({})
-	const list = ref({});
-	const readNotice = () => {
-		if(notice.value.id > 0){
-			noticeRead(notice.value.id);
-		}
-	  centerDialogVisible.value = false
-	}
-	const getList = () => {
-		noticeListMine({limit:3}).then(res => {
-			list.value = res.data;
-		})
-	};
-	const titleClick = (e) => {
-	  noticeDetail(e.id).then(res => {
-	    centerDialogVisible.value = true;
-	    notice.value.id = res.data.id
-	    notice.value.title = res.data.title
-	    notice.value.content = res.data.content
-	  })
-	}
-	const go2page = () =>{
-		router.push({ path: "/notice/mynotice" });
-	}
-	onMounted(() => {
-		getList();
-	});
+import { ref } from 'vue'
+import { Bell } from '@element-plus/icons-vue';
+import router from '@/router';
+import { noticeListMine, noticeDetail, noticeRead } from '@/api/notice/index';
+const centerDialogVisible = ref(false)
+const notice = ref({})
+const list = ref({});
+const readNotice = () => {
+  if (notice.value.id > 0) {
+    noticeRead(notice.value.id);
+  }
+  centerDialogVisible.value = false
+}
+const getList = () => {
+  noticeListMine({ limit: 3 }).then(res => {
+    list.value = res.data;
+  })
+};
+const titleClick = (e) => {
+  noticeDetail(e.id).then(res => {
+    centerDialogVisible.value = true;
+    notice.value.id = res.data.id
+    notice.value.title = res.data.title
+    notice.value.content = res.data.content
+  })
+}
+const go2page = () => {
+  router.push({ path: "/notice/mynotice" });
+}
+onMounted(() => {
+  getList();
+});
 </script>
 
 <style lang="scss" scoped>
-	
-	.header-title {
-	  display: flex;
-	  align-items: center;
-	  justify-content:space-between;
-	  h4{
-		  margin-right: 50px;
-		  color: #29d;;
-	  }
-	}
-	.notice-item{
-		display: flex;
-		margin-top: 5px;
-	}
-	.notices{
-		margin-left: 10px;
-		line-height: 18px;;
-	}
-	.logo{
-		width: 18px;
-		height: 18px;
-	}
-</style>
+.header-title {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  h4 {
+    margin-right: 50px;
+    color: #29d;
+    ;
+  }
+}
+
+.notice-item {
+  display: flex;
+  margin-top: 5px;
+}
+
+.notices {
+  margin-left: 10px;
+  line-height: 18px;
+  ;
+}
+
+.logo {
+  width: 18px;
+  height: 18px;
+}</style>

+ 2 - 1
src/views/dataStatistics/shortStatistical/excelTitle.ts

@@ -6,5 +6,6 @@ export const titleObj = {
   充值金额: 'amount',
   充值次数: 'charge_count',
   充值人数: 'charge_user_num',
-  播放次数: 'play_count'
+  播放次数: 'play_count',
+  点击人数: 'click_uv'
 };

+ 18 - 0
src/views/dataStatistics/shortStatistical/index.vue

@@ -105,6 +105,24 @@
             </div>
           </template>
         </el-table-column>
+        <el-table-column label="点击人数" sortable prop="click_uv">
+          <template #header>
+            <span>点击人数</span>
+            <el-tooltip placement="top">
+              <template #content>
+                当日绑定此短剧的所有推广链接,累计点击的人数<br />
+              </template>
+              <el-icon>
+                <InfoFilled />
+              </el-icon>
+            </el-tooltip>
+          </template>
+          <template #default="scope">
+            <div class="wrapper">
+              <div>{{ scope.row.click_uv }}</div>
+            </div>
+          </template>
+        </el-table-column>
       </el-table>
       <Paginate />
     </div>

+ 0 - 1
src/views/notice/mynotice/dataStatistics/notices.vue

@@ -25,7 +25,6 @@
 </template>
 
 <script lang="ts" setup>
-	import notice_img from "";
 	import { ref } from 'vue'
 	import { Bell } from '@element-plus/icons-vue';
 	import router from '@/router';

+ 1 - 1
src/views/notice/mynotice/dataStatistics/todayData.vue

@@ -1,5 +1,5 @@
 <template>
-	<p class="title">今日数据 <label style="color: #909399; margin-left: 50px;">注: 以下数据为当日所有小程序的累计数据,如需查看各个小程序数据,可前往<span @click="go2tj" style="color: #29d;">数据统计</span>进行查看</label></p>
+	<p class="title">今日数据 <label style="color: #909399; margin-left: 50px;">注: 以下数据为当日所有小程序的累计数据,如需查看各个小程序数据,可前往<span @click="go2tj" style="color: #29d;" class="cursor-pointer">数据统计</span>进行查看</label></p>
 	<div class="data-line">
 		<div class="box-card">
 		<!-- 	<el-tooltip placement="top">

+ 2 - 2
src/views/notice/mynotice/index.vue

@@ -2,7 +2,7 @@
 	
 	<div v-show="showAll == false"> 
 		<el-card style="padding: 5px; 15px;height: calc(24vh);">
-			<div class="header-title"> <p>最新通知 </p> <lable  @click="go2page">查看全部</lable></div>
+			<div class="header-title"> <p>最新通知 </p> <lable class="cursor-pointer"  @click="go2page">查看全部</lable></div>
 			<noticeBlock></noticeBlock>
 		</el-card>
 
@@ -24,7 +24,7 @@
 						</template>
 					  </el-table-column>
 					</el-table> -->
-						<div class="noticebody h-full">
+						<div class="h-full noticebody">
 							<div class="notice-item itme" :class="item.id == notice.id ? 'active':'' " @click="titleClick(item)"  v-for="(item,index) in currentTableData" :key="index" >
 							 <el-badge v-if="item.is_read == 0" is-dot class="item">
 								<el-icon><Bell /></el-icon>

+ 174 - 0
src/views/officialAccount/attentionReply/form/create.vue

@@ -0,0 +1,174 @@
+<template>
+  <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+    <el-form-item label="消息内容" prop="type" :rules="[{ required: true, message: '消息类型必须填写' }]" label-width="120px">
+      <el-radio-group v-model="formCallback.type">
+        <el-radio :label="2">文本消息</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="" label-width="120px">
+      <el-card class="box-card" style="width:500px;">
+        <template #header>
+          <div class="card-header">
+            <el-popover placement="right" :visible="popoverVisible" trigger="click">
+              <template #reference>
+                <el-button link :icon="Plus" @click="popoverVisible = !popoverVisible">插入内容</el-button>
+              </template>
+              <div class="flex flex-col items-start justify-start">
+                <el-link :underline="false" class="m-1" @click="insertChange('link')">插入小程序链接</el-link>
+                <el-link :underline="false" class="m-1" @click="insertChange('text')">插入纯文本</el-link>
+              </div>
+            </el-popover>
+          </div>
+        </template>
+        <div class="insert-content">
+          <div v-for="(item, index) in insertArr" :key="item">
+            <el-button type="primary" link v-if="item.link" @click="linkClick({ index, ...item })">
+              {{ item.title }}
+            </el-button>
+            <el-input style="boder:none;" v-else v-model="item.title" clearable></el-input>
+          </div>
+        </div>
+      </el-card>
+    </el-form-item>
+    <div class="flex justify-end">
+      <el-button type="primary" @click="submitForm(form)">{{
+        $t('system.confirm')
+      }}</el-button>
+    </div>
+  </el-form>
+
+  <Dialog v-model="insertVisible" :title="insertTitle" destroy-on-close>
+    <el-form :model="formCallbackinsert" label-width="120px" ref="insertform" v-loading="loading" class="pr-4">
+      <div v-if="insertType == 'link'">
+        <div class="withdraw-popup-warn">
+          <span>需先</span>
+          <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary" href="https://element-plus.org"
+            target="_blank">关联小程序</el-link>
+          <span>,已关联的小程序可被使用在自定义菜单、模板消息和附近的小程序等场景中</span>
+        </div>
+        <el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" v-model="formCallbackinsert.title" auto-complete="off"
+            placeholder="请输入标题"></el-input>
+        </el-form-item>
+        <el-form-item label="链接" prop="link" :rules="[{ required: true, message: '链接必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" v-model="formCallbackinsert.link" auto-complete="off"
+            placeholder="请输入链接"></el-input>
+          <el-button type="primary" link :icon="Plus" @click="linkVisible = true" class="mr-6">插入链接</el-button>
+        </el-form-item>
+      </div>
+      <div v-else>
+        <el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" type="textarea" v-model="formCallbackinsert.title" auto-complete="off"
+            placeholder="请输入标题"></el-input>
+        </el-form-item>
+      </div>
+      <div class="flex justify-end">
+        <el-button type="primary" @click="insertSubmitChange(insertform)">确定</el-button>
+      </div>
+    </el-form>
+  </Dialog>
+
+  <Dialog v-model="linkVisible" title="选择链接" destroy-on-close>
+    <generateLink @close="linkClose" />
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { FormInstance } from 'element-plus';
+import { useRouter, useRoute } from 'vue-router'
+import generateLink from './generateLink.vue'
+const router = useRouter()
+const route = useRoute()
+const insertform = ref()
+const form = ref()
+const props = defineProps({
+  primary: Object | null,
+});
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const linkVisible = ref(false)
+const popoverVisible = ref(false)
+const insertVisible = ref(false)
+
+const formCallback = ref({ type: 1 })
+const formCallbackinsert = ref({})
+const insertType = ref()
+const insertArr = ref([])
+const insertTitle = computed(() => insertType.value == 'link' ? '插入小程序链接' : '插入纯文本')
+const insertChange = (type: string) => {
+  insertType.value = type;
+  insertVisible.value = true;
+  popoverVisible.value = false;
+}
+const linkClick = (row: object) => {
+  formCallbackinsert.value = row
+  insertVisible.value = true
+  insertType.value = 'link'
+}
+const insertSubmitChange = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+        if (formCallbackinsert.value.index != undefined) {
+          insertArr.value[formCallbackinsert.value.index] = formCallbackinsert.value
+        } else {
+          insertArr.value.push(formCallbackinsert.value)
+        }
+        insertVisible.value = false
+        formCallbackinsert.value = {}
+        console.log(insertArr.value, 'insertArr.value');
+      } else {
+      }
+    })
+    .then(() => { });
+}
+
+const linkClose = (e: any) => {
+  console.log(e);
+}
+// 提交回传配置
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  loading.value = true;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+
+        loading.value = false;
+      } else {
+        loading.value = false;
+      }
+    })
+    .then(() => { });
+}
+
+
+if (props.primary) {
+  formCallback.value = JSON.parse(JSON.stringify(props.primary))
+}
+onMounted(() => {
+});
+</script>
+
+<style lang="scss" scoped>
+.withdraw-popup-warn {
+  font-size: 14px;
+  font-weight: 600;
+  color: #666;
+  margin-bottom: 12px;
+}
+
+.insert-content {
+  min-height: 200px;
+  max-height: 468px;
+  overflow: hidden;
+  overflow-y: auto;
+  line-height: 20px;
+  height: 20px;
+  padding: 10px;
+}
+</style>

+ 102 - 0
src/views/officialAccount/attentionReply/form/generateLink.vue

@@ -0,0 +1,102 @@
+<template>
+  <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+    <el-form-item label="消息类型" prop="roi" :rules="[{ required: true, message: '消息类型必须填写' }]" label-width="120px">
+      <el-radio-group v-model="formCallback.roi">
+        <el-radio :label="1">视频链接</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="短剧" prop="video_id">
+      <el-select v-model="formCallback.video_id" class="w-full" clearable filterable remote :remote-method="remoteMethod"
+        placeholder="请输入短剧">
+        <el-option v-for="item in videoList" :key="item.id" :label="item.name" :value="item.id" />
+      </el-select>
+    </el-form-item>
+    <el-form-item label="剧集" prop="video_id">
+      <el-select v-model="formCallback.video_id" class="w-full" clearable filterable remote :remote-method="remoteMethod"
+        placeholder="请选择剧集">
+        <el-option v-for="item in videoList" :key="item.id" :label="item.name" :value="item.id" />
+      </el-select>
+    </el-form-item>
+    <div class="flex justify-end">
+      <el-button type="primary" @click="submitForm(form)">{{
+        $t('system.confirm')
+      }}</el-button>
+    </div>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { FormInstance } from 'element-plus';
+import { videoStockVideoList } from '@/api/video/index'
+const props = defineProps({
+  primary: Object | null,
+});
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const formCallback = ref({})
+const videoList = ref([])
+
+const initVideoList = (params?: object) => {
+  videoStockVideoList({ limit: 999, ...params }).then(res => {
+    console.log(res);
+    videoList.value = res.data
+  })
+}
+const remoteMethod = (query: string) => {
+  if (query) {
+    initVideoList({ videoName: query })
+  } else {
+    initVideoList()
+  }
+}
+
+// 提交回传配置
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  loading.value = true;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+
+        loading.value = false;
+      } else {
+        loading.value = false;
+      }
+    })
+    .then(() => { });
+}
+
+
+if (props.primary) {
+  formCallback.value = JSON.parse(JSON.stringify(props.primary))
+}
+onMounted(() => {
+  initVideoList()
+});
+</script>
+
+<style lang="scss" scoped>
+.wrapper {
+  width: 280px;
+  border: 1px solid rgba($color: #dcdfe6, $alpha: 1);
+  border-top: none;
+  border-radius: 6px;
+
+  .filter-input {
+    width: 100%;
+    border-radius: 6px;
+    border: 1px solid #dcdfe6;
+    outline: none;
+    padding: 0 6px;
+  }
+
+  .wrapper-inner {
+    height: 300px;
+    overflow: auto;
+
+    padding: 9px;
+  }
+}
+</style>

+ 159 - 0
src/views/officialAccount/attentionReply/index.vue

@@ -0,0 +1,159 @@
+<template>
+  <div class="mb-3">
+    <el-card shadow="never">
+      <el-form-item label="是否启用关注回复">
+        <el-switch v-model="isEnableReply" />
+      </el-form-item>
+      <div class="items-center w-full withdraw-popup-warn">
+        <div>
+          如需关闭微信官方的关注回复消息,请登录公共号后台手动操作,
+          <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary" href="https://element-plus.org"
+            target="_blank">点击查看</el-link>
+          操作说明;如果关闭此按钮,列表所有回复全部不可用
+        </div>
+      </div>
+    </el-card>
+  </div>
+  <div class="flex flex-col justify-between w-full sm:flex-row">
+    <div class="w-full">
+      <Search :search="search" :reset="reset">
+        <template v-slot:body>
+          <el-form-item label="公众号">
+            <el-select v-model="query.type" clearable filterable placeholder="请选择">
+              <el-option v-for="(item, index) in officialAccountsList" :key="index" :label="item.name"
+                :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </template>
+        <template v-slot:content>
+          <div class="table-default">
+            <div class="pl-2">
+              <el-button type="primary" size="default" @click="openType('createVisible', null)">新增</el-button>
+            </div>
+            <el-table :data="tableData" class="mt-3" v-loading="loading">
+              <el-table-column prop="name" label="已配置公众号" min-width="200">
+                <template #default="scope">
+                  <div class="flex flex-col">
+                    <div class="text-lg font-bold text-blue-400 cursor-pointer"
+                      @click="openType('createVisible', { single: true, ...scope.row })">
+                      {{ scope.row.name }}
+                    </div>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column prop="updated_at" label="创建时间" min-width="200"></el-table-column>
+              <el-table-column label="操作" width="200" fixed="right">
+                <template #default="scope">
+                  <el-button link type="primary" size="small" @click="openType('createVisible', scope.row)">编辑</el-button>
+                  <br />
+                  <el-button link type="primary" size="small"
+                    @click="openType('createVisible', { look: true, ...scope.row })">查看</el-button>
+                  <br />
+                  <el-button link type="primary" size="small" @click="deleteChange(scope.row)">删除</el-button>
+                  <br />
+                  <el-button link type="primary" size="small"
+                    @click="openType('configPublicVisible', scope.row)">配置公众号</el-button>
+                  <br />
+                </template>
+              </el-table-column>
+            </el-table>
+            <Paginate />
+          </div>
+        </template>
+      </Search>
+      <Dialog v-model="createVisible" :title="createTitle" destroy-on-close>
+        <Create @close="closeType('createVisible')" :primary="current" />
+      </Dialog>
+      <Dialog v-model="configPublicVisible" title="配置公众号" destroy-on-close>
+        <configPublic @close="closeType('createVisible')" :primary="current" />
+      </Dialog>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref } from 'vue';
+import Create from './form/create.vue';
+import configPublic from '@/views/officialAccount/components/configPublic.vue'
+import { useGetList } from '@/hook/curd/useGetList';
+import { manageMiniprogramCompanylist, manageMiniprogramTypelist } from '@/api/applet/index'
+const api = 'manage/miniprogram/index';
+const createVisible = ref(false)
+const configPublicVisible = ref(false)
+const isEnableReply = ref(true)
+const current = ref<object | null>({})
+const messageType = ref([])
+const officialAccountsList = ref([])
+const createTitle = ref('新增')
+const { data, query, search, reset, loading } = useGetList(api);
+const rolesIdentify = inject('rolesIdentify')
+const tableData = computed(() => data.value?.data);
+
+const openType = (type: string, data: object | null) => {
+  current.value = data;
+  switch (type) {
+    case 'createVisible':
+      createVisible.value = true
+      if (current.value?.id) {
+        createTitle.value = '编辑'
+        if (current.value.single) {
+          createTitle.value = current.value.name
+        }
+      } else {
+        createTitle.value = '新增'
+      }
+      break;
+    case 'configPublicVisible':
+      configPublicVisible.value = true
+      break;
+  }
+}
+
+const closeType = (type: string) => {
+  switch (type) {
+    case 'createVisible':
+      createVisible.value = false
+      break;
+  }
+  search()
+}
+
+const deleteChange = (row: object) => {
+  ElMessageBox.confirm(
+    '确定要删除吗?',
+    '提示',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      // tableData.value.splice(index, 1)
+    })
+    .catch(() => {
+
+    })
+}
+const init = () => {
+  manageMiniprogramCompanylist().then(res => {
+    messageType.value = res.data
+  })
+  manageMiniprogramTypelist().then(res => {
+    officialAccountsList.value = res.data
+  })
+}
+
+onMounted(() => {
+  init()
+  search();
+});
+</script>
+
+<style lang="scss" scoped>
+.withdraw-popup-warn {
+  font-size: 14px;
+  font-weight: 600;
+  color: #666;
+}
+</style>

+ 122 - 0
src/views/officialAccount/components/configPublic.vue

@@ -0,0 +1,122 @@
+<template>
+  <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+    <el-form-item label="配置公众号" prop="wx_auth_ids" :rules="[{ required: true, message: '公众号必须选择' }]" label-width="120px">
+      <div class="wrapper">
+        <input class="filter-input" v-model="filterPlublic" clearable placeholder="请输入公众号名称"
+          @change="filterExistPlublic" />
+        <div class="wrapper-inner">
+          <el-checkbox v-model="selectAllValue" @change="handleCheckAllChange">全选</el-checkbox>
+          <el-checkbox-group v-model="formCallback.wx_auth_ids" @change="handleCheckedChange" class="flex flex-col">
+            <el-checkbox :key="sIndex" :label="item.id" v-for="(item, sIndex) in filteredCities"
+              :checked="Boolean(item.is_auth)">
+              {{ item.nick_name }}
+            </el-checkbox>
+          </el-checkbox-group>
+        </div>
+      </div>
+    </el-form-item>
+    <div class="flex justify-end">
+      <el-button type="primary" @click="submitForm(form)">{{
+        $t('system.confirm')
+      }}</el-button>
+    </div>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { wechatPlatformKeywordAuthList, wechatPlatformKeywordAllocation } from '@/api/officialAccount/keywordReply/index'
+import { FormInstance } from 'element-plus';
+const props = defineProps({
+  primary: Object | null,
+});
+const form = ref<FormInstance>()
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const formCallback = ref({ wx_auth_ids: [] })
+const filterPlublic = ref('')
+// const isIndeterminate = ref(false)
+let goalOptions = ref([])
+
+const filteredCities = computed(() => {
+  return goalOptions.value.filter(city => city.nick_name.includes(filterPlublic.value));
+});
+
+const selectAllValue = computed(() => {
+  return formCallback.value?.wx_auth_ids?.length > 0 && formCallback.value?.wx_auth_ids?.length == goalOptions.value.length
+});
+
+const filterExistPlublic = () => {
+  console.log('filterExistPlublicfilterExistPlublic');
+}
+const handleCheckAllChange = (val: boolean) => {
+  formCallback.value.wx_auth_ids = val ? filteredCities.value : []
+  console.log(formCallback.value.wx_auth_ids, val, 'formCallback.value.wx_auth_ids');
+  // isIndeterminate.value = false
+}
+const handleCheckedChange = (value: string[]) => {
+  // isIndeterminate.value = checkedCount > 0 && checkedCount < goalOptions.value.length
+}
+
+// 提交回传配置
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  loading.value = true;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+        console.log(formCallback.value, 'formCallback.valueformCallback.value');
+        const wx_auth_ids = formCallback.value.wx_auth_ids.join(',')
+        wechatPlatformKeywordAllocation(props.primary.id, { wx_auth_ids }).then(res => {
+          console.log(res, 'wx_auth_ids');
+          ElMessage.success(res.message)
+          emit('close')
+        })
+        loading.value = false;
+      } else {
+        loading.value = false;
+      }
+    })
+    .then(() => { });
+}
+
+
+if (props.primary) {
+  wechatPlatformKeywordAuthList(props.primary.id).then(res => {
+    goalOptions.value = res.data
+    console.log(goalOptions.value, 'goalOptionsgoalOptions');
+    const wx_auth_ids = goalOptions.value?.filter(el => el.is_auth).map(el => el.id)
+    if (wx_auth_ids.length > 0) {
+      formCallback.value.wx_auth_ids = wx_auth_ids
+    }
+  })
+  console.log(props.primary, formCallback.value, 'props.primaryprops.primary');
+}
+onMounted(() => {
+});
+</script>
+
+<style lang="scss" scoped>
+.wrapper {
+  width: 280px;
+  border: 1px solid rgba($color: #dcdfe6, $alpha: 1);
+  border-top: none;
+  border-radius: 6px;
+
+  .filter-input {
+    width: 100%;
+    border-radius: 6px;
+    border: 1px solid #dcdfe6;
+    outline: none;
+    padding: 0 6px;
+  }
+
+  .wrapper-inner {
+    height: 300px;
+    overflow: auto;
+
+    padding: 9px;
+  }
+}
+</style>

+ 216 - 0
src/views/officialAccount/keywordReply/form/create.vue

@@ -0,0 +1,216 @@
+<template>
+  <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+    <el-form-item label="关键词" prop="keyword" :rules="[{ required: true, message: '关键词必须填写' }]" label-width="120px">
+      <template #label>
+        <div class="flex items-center">
+          <el-tooltip placement="top">
+            <template #content>
+              <span>关键词</span> <br />
+            </template>
+            <el-icon>
+              <InfoFilled />
+            </el-icon>
+          </el-tooltip>
+          <span>关键词</span>
+        </div>
+      </template>
+      <el-input :disabled="props.primary?.look" v-model="formCallback.keyword" placeholder="请输入关键词" />
+    </el-form-item>
+    <el-form-item label="消息内容" prop="type" :rules="[{ required: true, message: '消息类型必须填写' }]" label-width="120px">
+      <el-radio-group v-model="formCallback.type" :disabled="props.primary?.look">
+        <el-radio label="txt">文本消息</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="" label-width="120px" prop="content" :rules="[{ required: true, message: '消息内容必须插入' }]">
+      <el-card class="box-card" style="width:500px;">
+        <template #header v-if="!props.primary?.look">
+          <div class="card-header">
+            <el-popover placement="right" :visible="popoverVisible" trigger="click">
+              <template #reference>
+                <el-button link :icon="Plus" @click="popoverVisible = !popoverVisible">插入内容</el-button>
+              </template>
+              <div class="flex flex-col items-start justify-start">
+                <el-link :underline="false" class="m-1" @click="insertChange('link')">插入小程序链接</el-link>
+                <el-link :underline="false" class="m-1" @click="insertChange('text')">插入纯文本</el-link>
+              </div>
+            </el-popover>
+          </div>
+        </template>
+        <div class="insert-content">
+          <div v-for="(item, index) in formCallback.content" :key="index">
+            <el-button type="primary" link v-if="item.url" @click="linkClick({ index, ...item })">
+              {{ item.title }}
+            </el-button>
+            <el-input style="boder:none;" :disabled="props.primary?.look" v-else v-model="item.title"
+              clearable></el-input>
+          </div>
+        </div>
+      </el-card>
+    </el-form-item>
+    <div class="flex justify-end" v-if="!props.primary?.look">
+      <el-button type="primary" @click="submitForm(form)">{{
+        $t('system.confirm')
+      }}</el-button>
+    </div>
+  </el-form>
+
+  <Dialog v-model="insertVisible" :title="insertTitle" destroy-on-close @update:modelValue="formCallbackinsert = {}">
+    <el-form :model="formCallbackinsert" label-width="120px" ref="insertform" v-loading="loading" class="pr-4">
+      <div v-if="insertType == 'link'">
+        <div class="withdraw-popup-warn">
+          <span>需先</span>
+          <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary" href="https://element-plus.org"
+            target="_blank">关联小程序</el-link>
+          <span>,已关联的小程序可被使用在自定义菜单、模板消息和附近的小程序等场景中</span>
+        </div>
+        <el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" :disabled="props.primary?.look" v-model="formCallbackinsert.title"
+            auto-complete="off" placeholder="请输入标题"></el-input>
+        </el-form-item>
+        <el-form-item label="链接" prop="url" :rules="[{ required: true, message: '链接必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" :disabled="props.primary?.look" v-model="formCallbackinsert.url"
+            auto-complete="off" placeholder="请输入链接"></el-input>
+          <el-button v-if="!props.primary?.look" type="primary" link :icon="Plus" @click="linkVisible = true"
+            class="mr-6">插入链接</el-button>
+        </el-form-item>
+      </div>
+      <div v-else>
+        <el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" :disabled="props.primary?.look" type="textarea"
+            v-model="formCallbackinsert.title" auto-complete="off" placeholder="请输入标题"></el-input>
+        </el-form-item>
+      </div>
+      <div class="flex justify-end" v-if="!props.primary?.look">
+        <el-button type="primary" @click="insertSubmitChange(insertform)">确定</el-button>
+      </div>
+    </el-form>
+  </Dialog>
+
+  <Dialog v-model="linkVisible" title="选择链接" destroy-on-close>
+    <generateLink @close="linkClose" />
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { FormInstance } from 'element-plus';
+import { useRouter, useRoute } from 'vue-router'
+import { wechatPlatformKeywordAdd, wechatPlatformKeywordEdit } from '@/api/officialAccount/keywordReply/index'
+import generateLink from './generateLink.vue'
+import Cache from '@/support/cache';
+const router = useRouter()
+const route = useRoute()
+const insertform = ref()
+const form = ref()
+const props = defineProps({
+  primary: Object | null,
+});
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const linkVisible = ref(false)
+const popoverVisible = ref(false)
+const insertVisible = ref(false)
+const formCallback = ref({ type: 'txt', content: [] })
+const formCallbackinsert = ref({})
+const insertType = ref()
+const insertTitle = computed(() => insertType.value == 'link' ? '插入小程序链接' : '插入纯文本')
+const insertChange = (type: string) => {
+  insertType.value = type;
+  insertVisible.value = true;
+  popoverVisible.value = false;
+}
+const linkClick = (row: object) => {
+  formCallbackinsert.value = row
+  insertVisible.value = true
+  insertType.value = 'link'
+}
+const insertSubmitChange = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+        if (formCallbackinsert.value.index != undefined) {
+          formCallback.value.content[formCallbackinsert.value?.index] = formCallbackinsert.value
+        } else {
+          formCallback.value.content?.push(formCallbackinsert.value)
+        }
+        insertVisible.value = false
+        formCallbackinsert.value = {}
+      } else {
+      }
+    })
+    .then(() => { });
+}
+
+const linkClose = (e: any) => {
+  console.log(e);
+}
+// 提交回传配置
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  loading.value = true;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+        let params = {
+          type: formCallback.value.type,
+          keyword: formCallback.value.keyword,
+          miniprogram_id: formCallback.value.miniprogram_id,
+          content: formCallback.value.content,
+        }
+        if (props.primary?.id) {
+          params.id = props.primary?.id
+          wechatPlatformKeywordEdit(props.primary?.id, params).then(res => {
+            ElMessage.success(res.message)
+            emit('close')
+          }).catch(e => {
+            loading.value = false;
+          })
+        } else {
+          wechatPlatformKeywordAdd(params).then(res => {
+            ElMessage.success(res.message)
+            emit('close')
+          }).catch(e => {
+            loading.value = false;
+          })
+        }
+        loading.value = false;
+      } else {
+        loading.value = false;
+      }
+    })
+    .then(() => { });
+}
+
+
+if (props.primary) {
+  console.log(props.primary, 'props.primary');
+  formCallback.value = JSON.parse(JSON.stringify(props.primary))
+}
+onMounted(() => {
+  if (JSON.parse(Cache.get('nav_data'))?.app.id) {
+    formCallback.value.miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.withdraw-popup-warn {
+  font-size: 14px;
+  font-weight: 600;
+  color: #666;
+  margin-bottom: 12px;
+}
+
+.insert-content {
+  min-height: 200px;
+  max-height: 468px;
+  overflow: hidden;
+  overflow-y: auto;
+  line-height: 20px;
+  height: 20px;
+  padding: 10px;
+}
+</style>

+ 108 - 0
src/views/officialAccount/keywordReply/form/generateLink.vue

@@ -0,0 +1,108 @@
+<template>
+  <el-tabs>
+    <el-tab-pane label="视频链接">
+      <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+        <el-form-item label="短剧" prop="video_id">
+          <el-select v-model="formCallback.video_id" class="w-full" clearable filterable remote
+            :remote-method="remoteMethod" placeholder="请输入短剧">
+            <el-option v-for="item in videoList" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="剧集" prop="sequence" v-if="formCallback.video_id">
+          <el-select v-model="formCallback.sequence" class="w-full" clearable filterable placeholder="请选择剧集">
+            <el-option v-for="item in videoEpisodeList" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <div class="flex justify-end">
+          <el-button type="primary" @click="submitForm(form)">{{
+            $t('system.confirm')
+          }}</el-button>
+        </div>
+      </el-form>
+    </el-tab-pane>
+  </el-tabs>
+</template>
+
+<script lang="ts" setup>
+import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { FormInstance } from 'element-plus';
+import { videoStockVideoList, videoStockEpisodeList } from '@/api/video/index'
+const props = defineProps({
+  primary: Object | null,
+});
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const formCallback = ref({})
+const videoList = ref([])
+const videoEpisodeList = ref([])
+
+const initVideoList = (params?: object) => {
+  videoStockVideoList({ limit: 999, ...params }).then(res => {
+    console.log(res);
+    videoList.value = res.data
+  })
+}
+
+const initVideoStockEpisodeList = (params: object) => {
+  videoStockEpisodeList(params).then(res => {
+    console.log(res, 'videoEpisodeList');
+    videoEpisodeList.value = res.data;
+  })
+}
+const remoteMethod = (query: string) => {
+  if (query) {
+    initVideoList({ videoName: query })
+  } else {
+    initVideoList()
+  }
+}
+
+// 提交回传配置
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  loading.value = true;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+
+        loading.value = false;
+      } else {
+        loading.value = false;
+      }
+    })
+    .then(() => { });
+}
+
+
+if (props.primary) {
+  formCallback.value = JSON.parse(JSON.stringify(props.primary))
+}
+onMounted(() => {
+  initVideoList()
+});
+</script>
+
+<style lang="scss" scoped>
+.wrapper {
+  width: 280px;
+  border: 1px solid rgba($color: #dcdfe6, $alpha: 1);
+  border-top: none;
+  border-radius: 6px;
+
+  .filter-input {
+    width: 100%;
+    border-radius: 6px;
+    border: 1px solid #dcdfe6;
+    outline: none;
+    padding: 0 6px;
+  }
+
+  .wrapper-inner {
+    height: 300px;
+    overflow: auto;
+
+    padding: 9px;
+  }
+}
+</style>

+ 240 - 0
src/views/officialAccount/keywordReply/index.vue

@@ -0,0 +1,240 @@
+<template>
+  <div class="mb-3">
+    <el-card shadow="never">
+      <el-form-item label="是否启用关注回复">
+        <el-switch v-model="isEnableReply" />
+      </el-form-item>
+      <div class="items-center w-full withdraw-popup-warn">
+        <div>
+          关闭时,列表所有关键词回复全部失效
+        </div>
+      </div>
+    </el-card>
+  </div>
+  <div class="flex flex-col justify-between w-full sm:flex-row">
+    <div class="w-full">
+      <Search :search="search" :reset="reset">
+        <template v-slot:body>
+          <div class="mb-6">
+            <span class="mr-5 text-lg font-bold text-dark-600">自定义关键词</span>
+            <span class="mr-5 text-sm font-bold text-gray-600">关闭时,列表所有关键词回复全部失效</span>
+          </div>
+          <el-form-item label="关键字">
+            <el-input placeholder="请输入关键字" v-model="query.key" clearable></el-input>
+          </el-form-item>
+          <el-form-item label="公众号">
+            <el-select v-model="query.type" clearable filterable placeholder="请选择">
+              <el-option v-for="(item, index) in officialAccountsList" :key="index" :label="item.name"
+                :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </template>
+        <template v-slot:content>
+          <div class="table-default">
+            <div class="pt-5 pl-2">
+              <el-button type="primary" size="default" @click="openType('createVisible', null)">新增</el-button>
+              <el-button size="default" @click="mulSet">批量删除</el-button>
+            </div>
+            <el-table :data="tableData" class="mt-3" v-loading="loading" @selection-change="handleSelectionChange">
+              <el-table-column type="selection" width="80"></el-table-column>
+              <el-table-column prop="keyword" label="关键字" min-width="200"></el-table-column>
+              <el-table-column prop="created_at" label="创建时间" min-width="200"></el-table-column>
+              <el-table-column prop="send_total" label="总发送次数" min-width="200">
+                <template #header>
+                  <div class="flex items-center">
+                    <span>总发送次数</span>
+                    <el-tooltip placement="top">
+                      <template #content> 统计该关键字及内容在所有已配置公众号中的累计发送次数<br /></template>
+                      <el-icon>
+                        <InfoFilled />
+                      </el-icon>
+                    </el-tooltip>
+                  </div>
+                </template>
+                <template #default="scope">
+                  <span>{{ scope.row.send_total }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="nick_name" label="配置公众号" min-width="200">
+                <template #default="scope">
+                  <div class="flex flex-col">
+                    <!-- text-blue-400 cursor-pointer -->
+                    <div class="text-lg font-bold " v-for="item in scope.row.wechat_accounts" :key="item.id">
+                      <!--  @click="openType('createVisible', { single: true, currentwechat: item, ...scope.row })" -->
+                      {{ item.nick_name }}
+                    </div>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="200" fixed="right">
+                <template #default="scope">
+                  <el-button link type="primary" size="small" @click="openType('createVisible', scope.row)">编辑</el-button>
+                  <br />
+                  <el-button link type="primary" size="small"
+                    @click="openType('createVisible', { look: true, ...scope.row })">查看</el-button>
+                  <br />
+                  <el-button link type="primary" size="small" @click="deleteChange(scope.row)">删除</el-button>
+                  <br />
+                  <el-button link type="primary" size="small"
+                    @click="openType('configPublicVisible', scope.row)">配置公众号</el-button>
+                  <br />
+                </template>
+              </el-table-column>
+            </el-table>
+            <Paginate />
+          </div>
+        </template>
+      </Search>
+      <Dialog v-model="createVisible" :title="createTitle" destroy-on-close>
+        <Create @close="closeType('createVisible')" :primary="current" />
+      </Dialog>
+      <Dialog v-model="configPublicVisible" title="配置公众号" destroy-on-close>
+        <configPublic @close="closeType('configPublicVisible')" :primary="current" />
+      </Dialog>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { InfoFilled } from '@element-plus/icons-vue';
+import Create from './form/create.vue';
+import configPublic from '@/views/officialAccount/components/configPublic.vue'
+import { useGetList } from '@/hook/curd/useGetList';
+import { manageMiniprogramCompanylist, manageMiniprogramTypelist } from '@/api/applet/index'
+import {
+  wechatPlatformKeywordGetConfig,
+  wechatPlatformKeywordGetConfigSet,
+  wechatPlatformKeywordDel
+} from '@/api/officialAccount/keywordReply'
+const api = 'wechatPlatform/keyword/list';
+import Cache from '@/support/cache';
+const createVisible = ref(false)
+const configPublicVisible = ref(false)
+const isEnableReply = ref(true)
+const current = ref<object | null>({})
+const messageType = ref([])
+const officialAccountsList = ref([])
+const createTitle = ref('新增')
+const { data, query, search, reset, loading } = useGetList(api);
+const rolesIdentify = inject('rolesIdentify')
+const tableData = computed(() => data.value?.data);
+
+const multipleSelection = ref([])
+// 全选
+const handleSelectionChange = (val) => {
+  multipleSelection.value = val;
+}
+const openType = (type: string, data: object | null) => {
+  current.value = data;
+  switch (type) {
+    case 'createVisible':
+      createVisible.value = true
+      if (current.value?.id) {
+        createTitle.value = '编辑'
+        if (current.value.single) {
+          createTitle.value = current.value.currentwechat.nick_name
+        } else if (current.value.look) {
+          createTitle.value = "查看"
+        }
+      } else {
+        createTitle.value = '新增'
+      }
+      break;
+    case 'configPublicVisible':
+      configPublicVisible.value = true
+      break;
+  }
+}
+
+// 批量删除
+const mulSet = () => {
+  if (multipleSelection.value.length <= 0) {
+    return ElMessage.warning('至少选择一条数据');
+  } else {
+    const content = '确定批量删除吗?'
+    ElMessageBox.confirm(
+      content,
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }
+    )
+      .then(() => {
+        const ids = multipleSelection.value.map(el => el.id).join(',')
+        wechatPlatformKeywordDel({ ids }).then(res => {
+          console.log(res);
+          ElMessage.success(res.message)
+          search()
+        })
+      })
+      .catch(() => {
+
+      })
+  }
+}
+
+const closeType = (type: string) => {
+  switch (type) {
+    case 'createVisible':
+      createVisible.value = false
+      break;
+    case 'configPublicVisible':
+      configPublicVisible.value = false
+      break;
+  }
+  search()
+}
+
+const deleteChange = (row: object) => {
+  ElMessageBox.confirm(
+    '确定要删除吗?',
+    '提示',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      wechatPlatformKeywordDel({ ids: row.id }).then(res => {
+        console.log(res);
+        ElMessage.success(res.message)
+        search()
+      })
+      // tableData.value.splice(index, 1)
+    })
+    .catch(() => {
+
+    })
+}
+const init = () => {
+  manageMiniprogramCompanylist().then(res => {
+    messageType.value = res.data
+  })
+  manageMiniprogramTypelist().then(res => {
+    officialAccountsList.value = res.data
+  })
+}
+
+onMounted(() => {
+  if (JSON.parse(Cache.get('nav_data'))?.app.id) {
+    const miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+    wechatPlatformKeywordGetConfig(miniprogram_id).then(res => {
+      console.log(res, 'wechatPlatformKeywordGetConfig');
+    })
+  }
+
+  init()
+  search();
+});
+</script>
+
+<style lang="scss" scoped>
+.withdraw-popup-warn {
+  font-size: 14px;
+  font-weight: 600;
+  color: #666;
+}
+</style>

+ 249 - 0
src/views/officialAccount/newsService/form/create.vue

@@ -0,0 +1,249 @@
+<template>
+  <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+    <el-form-item label="活动名称" prop="is_roi" :rules="[{ required: true, message: '活动名称必须填写' }]" label-width="120px">
+      <el-input v-model="formCallback.keyword" placeholder="请输入活动名称" />
+    </el-form-item>
+    <el-form-item label="消息内容" prop="type" :rules="[{ required: true, message: '消息类型必须填写' }]" label-width="120px">
+      <el-radio-group v-model="formCallback.type">
+        <el-radio label="txt">文本消息</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="" label-width="120px">
+      <el-card class="box-card" style="width:500px;">
+        <template #header>
+          <div class="card-header">
+            <el-popover placement="right" :visible="popoverVisible" trigger="click">
+              <template #reference>
+                <el-button link :icon="Plus" @click="popoverVisible = !popoverVisible">插入内容</el-button>
+              </template>
+              <div class="flex flex-col items-start justify-start">
+                <el-link :underline="false" class="m-1" @click="insertChange('link')">插入小程序链接</el-link>
+                <el-link :underline="false" class="m-1" @click="insertChange('text')">插入纯文本</el-link>
+              </div>
+            </el-popover>
+          </div>
+        </template>
+        <div class="insert-content">
+          <div v-for="(item, index) in formCallback.content" :key="index">
+            <el-button type="primary" link v-if="item.link" @click="linkClick({ index, ...item })">
+              {{ item.title }}
+            </el-button>
+            <el-input style="boder:none;" v-else v-model="item.title" clearable></el-input>
+          </div>
+        </div>
+      </el-card>
+    </el-form-item>
+    <el-form-item label="发送用户" prop="is_roi" :rules="[{ required: true, message: '活动名称必须填写' }]" label-width="120px">
+      <el-radio-group v-model="formCallback.user">
+        <el-radio :label="3">全部粉丝</el-radio>
+        <el-radio :label="6">标签用户</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="人群包" prop="callback_config_id"
+      :rules="[{ required: true, message: '请选择人群包', trigger: 'change' }]">
+      <el-select v-model="formCallback.callback_config_id" filterable remote clearable :remote-method="remoteMethod"
+        placeholder="请选择人群包">
+        <el-option v-for="(item, index) in crowdPackageList" :key="index" :label="item.name" :value="item.id" />
+      </el-select>
+      <el-button type="primary" link size="default" @click="addCrowdPackage">新增人群包</el-button>
+    </el-form-item>
+    <el-form-item label="发送时间" prop="callback_config_id"
+      :rules="[{ required: true, message: '请选择发送时间', trigger: 'change' }]">
+      <div>
+        <el-date-picker unlink-panels clearable @change="sendTimeChange" format="YYYY/MM/DD hh:mm:ss"
+          value-format="YYYY-MM-DD h:m:s" v-model="formCallback.sendTime" type="datetime" placeholder="选择日期时间" />
+      </div>
+      <div>
+        <el-button type="primary" v-for="(time, index) in timeArr" :key="index" link size="default"
+          @click="setSendTime(time)">{{ time.name }}</el-button>
+      </div>
+    </el-form-item>
+    <div class="flex justify-end">
+      <el-button type="primary" @click="submitForm(form)">确定</el-button>
+    </div>
+  </el-form>
+
+  <Dialog v-model="insertVisible" :title="insertTitle" destroy-on-close @update:modelValue="formCallbackinsert = {}">
+    <el-form :model="formCallbackinsert" label-width="120px" ref="insertform" v-loading="loading" class="pr-4">
+      <div v-if="insertType == 'link'">
+        <div class="withdraw-popup-warn">
+          <span>需先</span>
+          <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary" href="https://element-plus.org"
+            target="_blank">关联小程序</el-link>
+          <span>,已关联的小程序可被使用在自定义菜单、模板消息和附近的小程序等场景中</span>
+        </div>
+        <el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" v-model="formCallbackinsert.title" auto-complete="off"
+            placeholder="请输入标题"></el-input>
+        </el-form-item>
+        <el-form-item label="链接" prop="link" :rules="[{ required: true, message: '链接必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" v-model="formCallbackinsert.link" auto-complete="off"
+            placeholder="请输入链接"></el-input>
+          <el-button type="primary" link :icon="Plus" @click="linkVisible = true" class="mr-6">插入链接</el-button>
+        </el-form-item>
+      </div>
+      <div v-else>
+        <el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" type="textarea" v-model="formCallbackinsert.title" auto-complete="off"
+            placeholder="请输入标题"></el-input>
+        </el-form-item>
+      </div>
+      <div class="flex justify-end">
+        <el-button type="primary" @click="insertSubmitChange(insertform)">确定</el-button>
+      </div>
+    </el-form>
+  </Dialog>
+
+  <Dialog v-model="linkVisible" title="选择链接" destroy-on-close>
+    <generateLink @close="linkClose" />
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { FormInstance } from 'element-plus';
+import { useRouter, useRoute } from 'vue-router'
+import { wechatPlatformKeywordAdd, wechatPlatformKeywordEdit } from '@/api/officialAccount/keywordReply/index'
+import generateLink from './generateLink.vue'
+import moment from 'moment';
+const router = useRouter()
+const route = useRoute()
+const insertform = ref()
+const form = ref()
+const crowdPackageList = ref([])
+const props = defineProps({
+  primary: Object | null,
+});
+const timeArr = [
+  { name: '10分钟后', value: '10', format: "minutes" },
+  { name: '30分钟后', value: '30', format: "minutes" },
+  { name: '1小时后', value: '1', format: "hours" }
+]
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const linkVisible = ref(false)
+const popoverVisible = ref(false)
+const insertVisible = ref(false)
+const formCallback = ref({ type: 'txt', content: [] })
+const formCallbackinsert = ref({})
+const insertType = ref()
+const insertTitle = computed(() => insertType.value == 'link' ? '插入小程序链接' : '插入纯文本')
+const insertChange = (type: string) => {
+  insertType.value = type;
+  insertVisible.value = true;
+  popoverVisible.value = false;
+}
+const linkClick = (row: object) => {
+  formCallbackinsert.value = row
+  insertVisible.value = true
+  insertType.value = 'link'
+}
+
+const setSendTime = (item: object) => {
+  console.log(item, 'setSendTime', moment().add(item.value, item.format).format('YYYY-MM-DD HH:mm:ss'));
+  formCallback.value.sendTime = moment().add(item.value, item.format).format('YYYY-MM-DD HH:mm:ss')
+}
+const sendTimeChange = (e: object) => {
+  console.log(e, 'timeChangetimeChangetimeChange');
+}
+
+const addCrowdPackage = () => {
+  router.push({ path: '', query: {} })
+}
+
+const initRemoteOption = (type: string, params?: object) => {
+  switch (type) {
+    case '':
+      break;
+  }
+}
+
+const remoteMethod = (query: string) => {
+  if (query) {
+    initRemoteOption("callbackJuliangAccountList", { name: query })
+  } else {
+    initRemoteOption("callbackJuliangAccountList")
+  }
+}
+
+const insertSubmitChange = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+        if (formCallbackinsert.value.index != undefined) {
+          formCallback.value.content[formCallbackinsert.value?.index] = formCallbackinsert.value
+        } else {
+          formCallback.value.content.push(formCallbackinsert.value)
+        }
+        insertVisible.value = false
+        formCallbackinsert.value = {}
+      } else {
+      }
+    })
+    .then(() => { });
+}
+
+const linkClose = (e: any) => {
+  console.log(e);
+}
+// 提交回传配置
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  loading.value = true;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+        if (props.primary?.id) {
+          wechatPlatformKeywordEdit(props.primary?.id, formCallback.value).then(res => {
+            ElMessage.success(res.message)
+            emit('close')
+          }).catch(e => {
+            loading.value = false;
+          })
+        } else {
+          wechatPlatformKeywordAdd(formCallback.value).then(res => {
+            ElMessage.success(res.message)
+            emit('close')
+          }).catch(e => {
+            loading.value = false;
+          })
+        }
+        loading.value = false;
+      } else {
+        loading.value = false;
+      }
+    })
+    .then(() => { });
+}
+
+
+if (props.primary) {
+  console.log(props.primary, 'props.primary');
+  formCallback.value = JSON.parse(JSON.stringify(props.primary))
+  formCallback.value.content = [{ title: 'dsds' }, { title: 'sdds', link: 'dsdsds' }]
+}
+onMounted(() => {
+});
+</script>
+
+<style lang="scss" scoped>
+.withdraw-popup-warn {
+  font-size: 14px;
+  font-weight: 600;
+  color: #666;
+  margin-bottom: 12px;
+}
+
+.insert-content {
+  min-height: 200px;
+  max-height: 468px;
+  overflow: hidden;
+  overflow-y: auto;
+  line-height: 20px;
+  height: 20px;
+  padding: 10px;
+}
+</style>

+ 102 - 0
src/views/officialAccount/newsService/form/generateLink.vue

@@ -0,0 +1,102 @@
+<template>
+  <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+    <el-form-item label="消息类型" prop="roi" :rules="[{ required: true, message: '消息类型必须填写' }]" label-width="120px">
+      <el-radio-group v-model="formCallback.roi">
+        <el-radio :label="1">视频链接</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="短剧" prop="video_id">
+      <el-select v-model="formCallback.video_id" class="w-full" clearable filterable remote :remote-method="remoteMethod"
+        placeholder="请输入短剧">
+        <el-option v-for="item in videoList" :key="item.id" :label="item.name" :value="item.id" />
+      </el-select>
+    </el-form-item>
+    <el-form-item label="剧集" prop="video_id">
+      <el-select v-model="formCallback.video_id" class="w-full" clearable filterable remote :remote-method="remoteMethod"
+        placeholder="请选择剧集">
+        <el-option v-for="item in videoList" :key="item.id" :label="item.name" :value="item.id" />
+      </el-select>
+    </el-form-item>
+    <div class="flex justify-end">
+      <el-button type="primary" @click="submitForm(form)">{{
+        $t('system.confirm')
+      }}</el-button>
+    </div>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { FormInstance } from 'element-plus';
+import { videoStockVideoList } from '@/api/video/index'
+const props = defineProps({
+  primary: Object | null,
+});
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const formCallback = ref({})
+const videoList = ref([])
+
+const initVideoList = (params?: object) => {
+  videoStockVideoList({ limit: 999, ...params }).then(res => {
+    console.log(res);
+    videoList.value = res.data
+  })
+}
+const remoteMethod = (query: string) => {
+  if (query) {
+    initVideoList({ videoName: query })
+  } else {
+    initVideoList()
+  }
+}
+
+// 提交回传配置
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  loading.value = true;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+
+        loading.value = false;
+      } else {
+        loading.value = false;
+      }
+    })
+    .then(() => { });
+}
+
+
+if (props.primary) {
+  formCallback.value = JSON.parse(JSON.stringify(props.primary))
+}
+onMounted(() => {
+  initVideoList()
+});
+</script>
+
+<style lang="scss" scoped>
+.wrapper {
+  width: 280px;
+  border: 1px solid rgba($color: #dcdfe6, $alpha: 1);
+  border-top: none;
+  border-radius: 6px;
+
+  .filter-input {
+    width: 100%;
+    border-radius: 6px;
+    border: 1px solid #dcdfe6;
+    outline: none;
+    padding: 0 6px;
+  }
+
+  .wrapper-inner {
+    height: 300px;
+    overflow: auto;
+
+    padding: 9px;
+  }
+}
+</style>

+ 199 - 0
src/views/officialAccount/newsService/index.vue

@@ -0,0 +1,199 @@
+<template>
+  <div class="flex flex-col justify-between w-full sm:flex-row">
+    <div class="w-full">
+      <Search :search="search" :reset="reset">
+        <template v-slot:body>
+
+          <el-form-item label="关键字">
+            <el-input placeholder="请输入关键字" v-model="query.key" clearable></el-input>
+          </el-form-item>
+          <el-form-item label="公众号">
+            <el-select v-model="query.type" clearable filterable placeholder="请选择">
+              <el-option v-for="(item, index) in officialAccountsList" :key="index" :label="item.name"
+                :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </template>
+        <template v-slot:content>
+          <div class="mt-3">
+            <el-alert title="仅48小时内和公众号有互动的粉丝才能收到,最多发送5条" type="warning" show-icon :closable="false" />
+          </div>
+          <div class="table-default">
+            <div class="pt-5 pl-2">
+              <el-button type="primary" size="default" @click="openType('createVisible', null)">新增</el-button>
+              <el-button size="default" @click="mulSet">批量删除</el-button>
+            </div>
+            <el-table :data="tableData" class="mt-3" v-loading="loading" @selection-change="handleSelectionChange">
+              <el-table-column type="selection" width="80"></el-table-column>
+              <el-table-column prop="name" label="关键字" min-width="200"></el-table-column>
+              <el-table-column prop="name" label="创建时间" min-width="200"></el-table-column>
+              <el-table-column prop="name" label="总发送次数" min-width="200">
+                <template #header>
+                  <div class="flex items-center">
+                    <span>总发送次数</span>
+                    <el-tooltip placement="top">
+                      <template #content> 统计该关键字及内容在所有已配置公众号中的累计发送次数<br /></template>
+                      <el-icon>
+                        <InfoFilled />
+                      </el-icon>
+                    </el-tooltip>
+                  </div>
+                </template>
+                <template #default="scope">
+                  <span>{{ scope.row.updated_at }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="name" label="配置公众号" min-width="200">
+                <template #default="scope">
+                  <div class="flex flex-col">
+                    <div class="text-lg font-bold text-blue-400 cursor-pointer"
+                      @click="openType('createVisible', { single: true, ...scope.row })">
+                      {{ scope.row.name }}
+                    </div>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="200" fixed="right">
+                <template #default="scope">
+                  <el-button link type="primary" size="small" @click="openType('createVisible', scope.row)">编辑</el-button>
+                  <br />
+                  <el-button link type="primary" size="small"
+                    @click="openType('createVisible', { look: true, ...scope.row })">查看</el-button>
+                  <br />
+                  <el-button link type="primary" size="small" @click="deleteChange(scope.row)">删除</el-button>
+                  <br />
+                  <el-button link type="primary" size="small"
+                    @click="openType('configPublicVisible', scope.row)">配置公众号</el-button>
+                  <br />
+                </template>
+              </el-table-column>
+            </el-table>
+            <Paginate />
+          </div>
+        </template>
+      </Search>
+      <Dialog v-model="createVisible" :title="createTitle" destroy-on-close>
+        <Create @close="closeType('createVisible')" :primary="current" />
+      </Dialog>
+      <Dialog v-model="configPublicVisible" title="配置公众号" destroy-on-close>
+        <configPublic @close="closeType('createVisible')" :primary="current" />
+      </Dialog>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { InfoFilled } from '@element-plus/icons-vue';
+import Create from './form/create.vue';
+import configPublic from '@/views/officialAccount/components/configPublic.vue'
+import { useGetList } from '@/hook/curd/useGetList';
+import { manageMiniprogramCompanylist, manageMiniprogramTypelist } from '@/api/applet/index'
+const api = 'wechatPlatform/keyword/list';
+const createVisible = ref(false)
+const configPublicVisible = ref(false)
+const isEnableReply = ref(true)
+const current = ref<object | null>({})
+const messageType = ref([])
+const officialAccountsList = ref([])
+const createTitle = ref('新增')
+const { data, query, search, reset, loading } = useGetList(api);
+const rolesIdentify = inject('rolesIdentify')
+const tableData = computed(() => data.value?.data);
+
+const multipleSelection = ref([])
+// 全选
+const handleSelectionChange = (val) => {
+  multipleSelection.value = val;
+}
+const openType = (type: string, data: object | null) => {
+  current.value = data;
+  switch (type) {
+    case 'createVisible':
+      createVisible.value = true
+      if (current.value?.id) {
+        createTitle.value = '编辑'
+        if (current.value.single) {
+          createTitle.value = current.value.name
+        }
+      } else {
+        createTitle.value = '新增'
+      }
+      break;
+    case 'configPublicVisible':
+      configPublicVisible.value = true
+      break;
+  }
+}
+
+// 批量删除
+const mulSet = () => {
+  if (multipleSelection.value.length <= 0) {
+    return ElMessage.warning('至少选择一条数据');
+  } else {
+    const content = '确定批量删除吗?'
+    ElMessageBox.confirm(
+      content,
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }
+    )
+      .then(() => {
+
+      })
+      .catch(() => {
+
+      })
+  }
+}
+
+const closeType = (type: string) => {
+  switch (type) {
+    case 'createVisible':
+      createVisible.value = false
+      break;
+  }
+  search()
+}
+
+const deleteChange = (row: object) => {
+  ElMessageBox.confirm(
+    '确定要删除吗?',
+    '提示',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      // tableData.value.splice(index, 1)
+    })
+    .catch(() => {
+
+    })
+}
+const init = () => {
+  manageMiniprogramCompanylist().then(res => {
+    messageType.value = res.data
+  })
+  manageMiniprogramTypelist().then(res => {
+    officialAccountsList.value = res.data
+  })
+}
+
+onMounted(() => {
+  init()
+  search();
+});
+</script>
+
+<style lang="scss" scoped>
+.withdraw-popup-warn {
+  font-size: 14px;
+  font-weight: 600;
+  color: #666;
+}
+</style>

+ 41 - 0
src/views/officialAccount/officialList/index.vue

@@ -0,0 +1,41 @@
+<template>
+  <div class="flex flex-col justify-between w-full sm:flex-row">
+    <div class="w-full">
+      <Search :search="search" :reset="reset">
+        <template v-slot:body>
+          <el-form-item label="公众号名称">
+            <el-input v-model="query.nick_name" size="default" placeholder="请输入公众号名称" clearable></el-input>
+          </el-form-item>
+        </template>
+      </Search>
+      <div class="table-default">
+        <el-table :data="tableData" class="mt-3" v-loading="loading">
+          <el-table-column label="公众号名称" prop="nick_name">
+          </el-table-column>
+          <el-table-column label="优化师" v-if="rolesIdentify.includes('company')" prop="username">
+          </el-table-column>
+          <el-table-column label="已关联小程序" prop="xcx_name">
+          </el-table-column>
+          <el-table-column label="appid" prop="authorizer_appid">
+          </el-table-column>
+          <el-table-column label="粉丝数" prop="fans_count">
+          </el-table-column>
+        </el-table>
+        <Paginate />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts"  setup>
+import { InfoFilled } from '@element-plus/icons-vue';
+import { useGetList } from '@/hook/curd/useGetList';
+const rolesIdentify = inject('rolesIdentify')
+const api = 'wechatPlatform/officialAccount/list';
+const { data, query, search, reset, loading } = useGetList(api, true);
+const tableData = computed(() => data.value?.data);
+onMounted(() => {
+  search();
+});
+</script>
+<style  lang='scss' scoped></style>

+ 459 - 0
src/views/officialAccount/publicCustomMenu/index.vue

@@ -0,0 +1,459 @@
+<template>
+  <div class="custom-menu">
+    <div class="menu-container">
+      <div class="device-wrap">
+        <div class="device device-iphone-x">
+          <div class="device-frame">
+            <div class="device-content">
+              <div class="wechat-header">
+                <div class="header-inner">
+                  <el-icon class="ml-3">
+                    <ArrowLeft />
+                  </el-icon>
+                  公众号
+                  <el-icon class="mr-3">
+                    <User />
+                  </el-icon>
+                </div>
+              </div>
+              <div class="wechat-menu">
+                <Draggable :list="menus.buttons" classString="flex-warpper" @end="onEnd">
+                  <template #default="{ item, index }">
+                    <div class="wechat-menu-item" :key="index">
+                      <p :class="['menu-name', { 'cur-menu': (index) === menus.curActiveIndex }]" @click="addMenu(index)">
+                        {{ item.name }}
+                      </p>
+                      <div class="children-menu-items" v-if="isShowChild(item)">
+                        <Draggable :list="item.sub_button" @end="onEndChild">
+                          <template #default="{ item, index }">
+                            <div class="children-menu-item" :key="index" @click="changeCurSubMenuIndex(index)">
+                              <p :class="['menu-name', { 'cur-name': menus.curSubActiveIndex === index }]">
+                                {{ item.name }}
+                              </p>
+                            </div>
+                          </template>
+                        </Draggable>
+                        <div v-if="item?.sub_button?.length < 5" class="children-menu-item" @click="addSubMenu">
+                          <el-icon>
+                            <Plus />
+                          </el-icon>
+                        </div>
+                      </div>
+                    </div>
+                  </template>
+                </Draggable>
+              </div>
+            </div>
+          </div>
+          <div class="device-stripe"></div>
+          <div class="device-header"></div>
+          <div class="device-sensors"></div>
+          <div class="device-btns"></div>
+          <div class="device-power"></div>
+        </div>
+      </div>
+      <div class="edit-wrap">
+        <div v-if="menus.init">
+          <div class="edit-content-wrap">
+            <p class="edit-title">
+              {{
+                menus.clickSubMenu ?
+                menus?.buttons[menus.curActiveIndex]?.sub_button[menus.curSubActiveIndex]?.name
+                : menus?.buttons[menus.curActiveIndex]?.name
+              }}
+              <span class="del-menu" @click="delMenus(menus.clickSubMenu)">
+                删除{{ menus.clickSubMenu ? "子菜单" : "菜单" }}
+              </span>
+            </p>
+            <p class="notice-text" v-if="(menus.buttons[menus.curActiveIndex].hasChild) && !menus.clickSubMenu">
+              已添加子菜单,仅可设置菜单名称。
+            </p>
+            <div class="edit-form">
+              <div v-if="menus.clickSubMenu">
+                <el-form :model="menus.buttons[menus.curActiveIndex].sub_button[menus.curSubActiveIndex]"
+                  label-width="100px" label-position="left">
+                  <el-form-item label="子菜单名称" prop="title" v-if="hasChild">
+                    <el-input v-model="menus.buttons[menus.curActiveIndex].sub_button[menus.curSubActiveIndex].name"
+                      style="width: 260px"></el-input>
+                  </el-form-item>
+                  <el-form-item label="子菜单内容" v-if="hasChild">
+                    <el-radio-group
+                      v-model="menus.buttons[menus.curActiveIndex].sub_button[menus.curSubActiveIndex].type">
+                      <el-radio class="radio" label="link">跳转网页</el-radio>
+                      <el-radio class="radio" label="event">发送消息</el-radio>
+                      <el-radio class="radio" label="hdlink">互动链</el-radio>
+                    </el-radio-group>
+                  </el-form-item>
+                </el-form>
+              </div>
+              <div v-else>
+                <el-form :model="menus?.buttons[menus?.curActiveIndex]" label-width="100px" label-position="left">
+                  <el-form-item label="菜单名称" prop="title">
+                    <el-input v-model="menus.buttons[menus.curActiveIndex].name" style="width: 260px" :maxlength="10"
+                      placeholder="请输入菜单名称(最多10个字符)"></el-input>
+                    <span style="color:#fd555d;padding-left:10px">受微信限制,如您发现微信显示有问题,请减少内容</span>
+                  </el-form-item>
+                  <div v-if="!menus.buttons[menus.curActiveIndex].sub_button.length">
+                    <el-form-item label="菜单内容">
+                      <el-radio-group v-model="menus.buttons[menus.curActiveIndex].type">
+                        <el-radio class="radio" label="link">跳转网页</el-radio>
+                        <el-radio class="radio" label="event">发送消息</el-radio>
+                        <el-radio class="radio" label="hdlink">互动链</el-radio>
+                      </el-radio-group>
+                    </el-form-item>
+                  </div>
+                </el-form>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div v-else>
+          <p class="no-init">点击左侧菜单进行编辑操作</p>
+        </div>
+      </div>
+    </div>
+    <div class="menu-save">
+      <el-button type="primary" :disabled="btnDisable">保存</el-button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { Plus, ArrowLeft, User } from '@element-plus/icons-vue';
+const limitButtons = ref(3)
+const menus = ref<object>({
+  curActiveIndex: null,
+  curSubActiveIndex: null,
+  clickSubMenu: false,
+  init: false,
+  buttons: [],
+})
+const isShowChild = (item: object) => {
+  return item.showChildMenu
+}
+
+const hasChild = computed(() =>
+  menus.value?.buttons[menus.value?.curActiveIndex]?.sub_button[menus.value?.curSubActiveIndex]?.name
+  ||
+  menus.value?.buttons[menus.value?.curActiveIndex]?.sub_button[menus.value?.curSubActiveIndex]?.type
+)
+const btnDisable = ref(true)
+const onEnd = (e: any) => {
+  menus.value.init = true;
+  menus.value.clickSubMenu = false;
+  menus.value.curSubActiveIndex = null;
+  menus.value.curActiveIndex = e.newDraggableIndex;
+  menus.value.buttons.forEach(el => el.showChildMenu = false)
+  menus.value.buttons[e.newDraggableIndex].showChildMenu = true
+}
+const onEndChild = (e: any) => {
+  // * 修改子菜单索引
+  menus.value.curSubActiveIndex = e.newDraggableIndex;
+}
+const addMenu = (i: any) => {
+  console.log(i, 'addMenuaddMenuaddMenu');
+  menus.value.init = true;
+  menus.value.clickSubMenu = false;
+  menus.value.curSubActiveIndex = null;
+  let buttons = menus.value.buttons;
+  if (i === menus.value.curActiveIndex) return;
+  // * 切换别的菜单时 隐藏当前菜单
+  if (menus.value.curActiveIndex !== null) {
+    menus.value.buttons[menus.value.curActiveIndex] &&
+      (menus.value.buttons[menus.value.curActiveIndex].showChildMenu = false);
+  }
+  menus.value.curActiveIndex = i;
+  if (buttons[i]) {
+    // * 已经有 则显示childMenu
+    menus.value.buttons[menus.value.curActiveIndex] &&
+      (menus.value.buttons[menus.value.curActiveIndex].showChildMenu = true);
+    return;
+  }
+  if (buttons.length >= limitButtons.value) return;
+  const menuObj = {
+    name: "菜单名称",
+    hasChild: false,
+    showChildMenu: true,
+    type: "link",
+    url: "",
+    url2: "",
+    key: "",
+    keyword: "",
+    linkcontent: "",
+    imgUrl: '',
+    sendMsgType: "template",
+    sendCustomUrl: "",
+    sub_button: [],
+  };
+  menus.value.buttons.push(menuObj);
+}
+const changeCurSubMenuIndex = (i: any) => {
+  // * 修改子菜单索引
+  if (i === menus.value.curSubActiveIndex) return;
+  menus.value.init = true;
+  menus.value.clickSubMenu = true;
+  menus.value.curSubActiveIndex = i;
+}
+const addSubMenu = () => {
+  // * 添加子菜单
+  menus.value.init = true;
+  menus.value.clickSubMenu = true;
+  let curActiveIndex = menus.value.curActiveIndex;
+  let subMenuObj = {
+    name: "子菜单名称",
+    url: "",
+    url2: "",
+    key: "",
+    type: "link",
+    keyword: "",
+    linkcontent: "",
+    imgUrl: '',
+    sendMsgType: "template",
+    sendCustomUrl: ""
+  };
+  menus.value.buttons[curActiveIndex].sub_button.push(subMenuObj);
+  // * 默认选择最新增加的子菜单
+  menus.value.curSubActiveIndex = menus.value.buttons[curActiveIndex].sub_button.length - 1;
+  menus.value.buttons[curActiveIndex].hasChild = true;
+}
+const delMenus = (isSubMenu: boolean) => {
+  // * 删除菜单或者子菜单内容
+  let curActiveIndex = menus.value.curActiveIndex;
+  let curSubActiveIndex = menus.value.curSubActiveIndex;
+  ElMessageBox.confirm(
+    isSubMenu
+      ? "删除后“子菜单名称”菜单下设置的内容将被删除"
+      : "删除后“菜单名称”菜单下设置的内容将被删除",
+    "温馨提示",
+    {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning",
+    }
+  )
+    .then(() => {
+      console.log("del");
+      if (isSubMenu) {
+        menus.value.buttons[curActiveIndex].sub_button.splice(
+          curSubActiveIndex,
+          1
+        );
+        menus.value.buttons[curActiveIndex].hasChild =
+          menus.value.buttons[curActiveIndex].sub_button.length > 0;
+        menus.value.curSubActiveIndex = null;
+        menus.value.init = false;
+      } else {
+        menus.value.buttons.splice(curActiveIndex, 1);
+        if (!menus.value.buttons.length) btnDisable.value = true;
+        reset();
+      }
+    })
+    .catch(() => {
+      console.log("cancel");
+    });
+}
+const reset = () => {
+  // * 重置内容
+  menus.value.curActiveIndex = null;
+  menus.value.curSubActiveIndex = null;
+  menus.value.init = false;
+}
+
+onMounted(() => {
+  menus.value = {
+    curActiveIndex: 0,
+    curSubActiveIndex: null,
+    clickSubMenu: false,
+    init: true,
+    buttons:
+      [
+        { name: "签到", hasChild: false, showChildMenu: true, type: "link", url: "", url2: "", key: "daily_sign", sub_button: [], keyword: "", linkcontent: "", imgUrl: "", sendMsgType: "template" },
+        { name: "最近阅读", hasChild: false, showChildMenu: false, type: "event", url: "", url2: "", key: "recent_read", sub_button: [], keyword: "", linkcontent: "", imgUrl: "", sendMsgType: "template" },
+        {
+          name: "菜单名称3", hasChild: true, showChildMenu: false, type: "link", url: "", url2: "", key: "", keyword: "", linkcontent: "", imgUrl: "", sendMsgType: "template", sendCustomUrl: "",
+          sub_button: [
+            { key: "", type: "link", name: "子菜单名称", url: "http://sitexyvz5mexll52mzn4.pre.aizhuishu.com/recent", url2: "", keyword: "", linkcontent: "", sendMsgType: "template", imgUrl: "" }]
+        }
+      ]
+  }
+}
+)
+</script>
+
+<style lang="scss" scoped>
+.menu-container {
+  display: flex;
+  justify-content: flex-start;
+  align-items: flex-start;
+}
+
+.device-wrap {
+  width: 350px;
+  margin-left: 20px;
+  margin-top: 20px;
+
+  img {
+    width: 100%;
+    height: 100%;
+    border-bottom-left-radius: 40px;
+    border-bottom-right-radius: 40px;
+  }
+}
+
+.edit-wrap {
+  flex: 1;
+  min-height: 668px;
+  background: #fff;
+  margin: 20px 20px 0 20px;
+  position: relative;
+  border: 1px solid #333;
+  border-radius: 9px;
+}
+
+.wechat-header {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 80px;
+  line-height: 80px;
+  background: #eee;
+  color: #000;
+  padding-top: 20px;
+  text-align: center;
+  border-bottom: 1px solid #d6d5da;
+  border-top-left-radius: 40px;
+  border-top-right-radius: 40px;
+
+  .header-inner {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+}
+
+.wechat-menu {
+  font-size: 12px;
+  position: absolute;
+  width: 100%;
+  bottom: 0;
+  left: 0;
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  border-top: 1px solid #d6d5da;
+
+  .flex-warpper {
+    width: 100%;
+    display: flex;
+  }
+
+  .wechat-menu-item {
+    flex: 1;
+    position: relative;
+    cursor: pointer;
+
+    p {
+      text-align: center;
+      padding: 15px 0;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+
+      &.cur-menu {
+        color: #39a4ff;
+      }
+    }
+
+    &:not(:last-child) {
+      border-right: 1px solid #d6d5da;
+    }
+  }
+}
+
+.children-menu-items {
+  font-size: 12px;
+  position: absolute;
+  background: #f8f8f8;
+  border-radius: 3px;
+  left: 0%;
+  bottom: 120%;
+  width: 100%;
+  border: 1px solid #d6d5da;
+  z-index: 9;
+
+  .children-menu-item {
+    padding: 10px 0;
+    text-align: center;
+    background: #f8f8f8;
+    cursor: pointer;
+    border: 1px solid rgba($color: #d6d5da, $alpha: 1.0);
+
+    p {
+      padding: 0;
+      width: 100%;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+
+      &.cur-name {
+        color: #39a4ff;
+      }
+    }
+  }
+}
+
+.no-init {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  font-size: 16px;
+}
+
+.edit-content-wrap {
+  position: relative;
+  margin: 15px;
+
+  .edit-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-size: 14px;
+    padding-bottom: 10px;
+    border-bottom: 1px solid #999;
+
+    .del-menu {
+      color: #39a4ff;
+      cursor: pointer;
+    }
+  }
+
+  .notice-text {
+    font-size: 14px;
+    margin: 5px 0;
+    color: #999;
+  }
+
+  .edit-form {
+    margin-top: 20px;
+  }
+}
+
+.menu-save {
+  text-align: center;
+  margin-top: 20px;
+  padding-bottom: 20px;
+}
+
+.choose-mode {
+  position: absolute;
+  right: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+
+  span {
+    color: #39a4ff;
+  }
+}
+</style>

+ 1 - 1
src/views/ordersManage/tabs/rechargeList/userDetail/tables/consume.vue

@@ -6,7 +6,7 @@
         <el-table-column label="说明">
           <template #default="scope">
             <div>短剧:<span>{{ scope.row.video_name }}</span></div>
-            <div>章节名称:<span>{{ scope.row.series_name }}</span></div>
+            <div>剧集名称:<span>{{ scope.row.series_name }}</span></div>
           </template>
         </el-table-column>
         <el-table-column prop="charge_coin_cost" label="看剧币支付" />

+ 1 - 1
src/views/ordersManage/tabs/rechargeList/userDetail/tables/readLog.vue

@@ -6,7 +6,7 @@
         <el-table-column prop="video_id" label="短剧ID">
         </el-table-column>
         <el-table-column prop="video_name" label="短剧名称" />
-        <el-table-column prop="video_series_sequence" label="章节名称" />
+        <el-table-column prop="video_series_sequence" label="剧集名称" />
       </el-table>
       <Paginate />
     </div>

+ 1 - 1
src/views/payBack/juliangAccount/tabs/advertiserList/form/create.vue

@@ -93,7 +93,7 @@ const route = useRoute()
 const emit = defineEmits(['close']);
 const loading = ref(false)
 const form = ref()
-const promotion = ref('')
+const promotion = ref<string | null | number>('')
 const formCallback = ref({
   account_id: '',
   account_name: '',

+ 0 - 2
src/views/payBack/juliangAccount/tabs/advertiserList/form/paybackConfig.vue

@@ -89,7 +89,6 @@ const router = useRouter()
 const route = useRoute()
 const emit = defineEmits(['close']);
 const loading = ref(false)
-const promotion = ref('')
 const form = ref()
 const formCallback = ref({
   ids: [],
@@ -211,7 +210,6 @@ if (props.primary) {
   formCallback.value = JSON.parse(JSON.stringify(props.primary))
 }
 onMounted(() => {
-  promotion.value = route.query?.promotionId
 });
 </script>
 

+ 9 - 8
src/views/payBack/juliangAccount/tabs/advertiserList/index.vue

@@ -19,16 +19,17 @@
     </template>
   </Search>
   <div class="table-default">
-    <div class="pt-5 pl-2" v-action="'callback.JuliangAccount.addAccount'">
-      <el-button type="primary" size="default" @click="openForm(null)">新增账户</el-button>
-      <el-button type="primary" size="default" @click="mulSet">批量设置</el-button>
+    <div class="pt-5 pl-2">
+      <el-button type="primary" v-action="'callback.JuliangAccount.addAccount'" size="default"
+        @click="openForm(null)">新增账户</el-button>
+      <el-button v-action="'callback.JuliangAccount.updateCallbackConfig'" size="default" @click="mulSet">批量设置</el-button>
     </div>
     <el-table :data="tableData" class="mt-3" v-loading="loading" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="80"></el-table-column>
-      <el-table-column label="回传配置ID" min-width="260" prop="id"></el-table-column>
-      <el-table-column label="巨量账户ID" min-width="260" show-overflow-tooltip prop="adv_account_id"></el-table-column>
+      <el-table-column label="回传配置ID" min-width="100" prop="id"></el-table-column>
+      <el-table-column label="巨量账户ID" min-width="180" show-overflow-tooltip prop="adv_account_id"></el-table-column>
       <!-- <el-table-column label="巨量账户" width="260" prop="adv_account_name"></el-table-column> -->
-      <el-table-column label="回传开关" width="150" v-action="'callback.JuliangAccount.turnCallbackState'">
+      <el-table-column label="回传开关" min-width="150" v-action="'callback.JuliangAccount.turnCallbackState'">
         <template #default="scope">
           <div>
             <el-switch v-model="scope.row.state" @change="switchStatus(scope.row)" :active-value="1" :inactive-value="0">
@@ -36,9 +37,9 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column label="关联推广名称" min-width="260" show-overflow-tooltip prop="promotion_name">
+      <el-table-column label="关联推广名称" min-width="180" show-overflow-tooltip prop="promotion_name">
       </el-table-column>
-      <el-table-column label="关联推广ID" show-overflow-tooltip prop="promotion_id">
+      <el-table-column label="关联推广ID" min-width="100" show-overflow-tooltip prop="promotion_id">
         <template #default="scope">
           <el-button link type="primary" size="default" @click="goToPromotion(scope.row)">{{ scope.row.promotion_id
           }}</el-button>

+ 1 - 2
src/views/payBack/juliangAccount/tabs/logList/excelTitle.ts

@@ -1,11 +1,10 @@
 // 导出中文/字段
 export const titleObj = {
   所属账户ID: 'advertiser_id',
-  // 所属账户: 'advertiser_name',
   订单号: 'order_no',
   用户ID: 'uid',
   注册IP: 'user_ranse_ip',
-  注册时间: 'user_ranse_start_at',
+  染色注册时间: 'user_ranse_start_at',
   充值时间: 'order_created_at',
   充值金额: 'order_price',
   是否回传: 'filter_type_str',

+ 1 - 1
src/views/payBack/juliangAccount/tabs/logList/index.vue

@@ -30,7 +30,7 @@
         </el-form-item>
       </template>
       <template v-slot:extra_button>
-        <exportExcel api="callback/juliangAccount/log/list" sheet_name="(巨量账户级)回传日志" :title_obj="titleObj"
+        <exportExcel api="callback/juliangAccount/log/list" sheet_name="头条-微信(账户级)" :title_obj="titleObj"
           :extro_params="{ is_export: true, ...query }">
         </exportExcel>
       </template>

+ 32 - 0
src/views/payBack/juliangPlus/index.vue

@@ -0,0 +1,32 @@
+<template>
+  <div>
+    <el-card shadow="always" :body-style="{ padding: '20px' }">
+      <el-tabs v-model="activeName" class="demo-tabs" @tab-change="handChange">
+        <el-tab-pane label="回传配置列表" name="backConfiguration">
+          <advertiser v-if="activeName == 'backConfiguration'"></advertiser>
+        </el-tab-pane>
+        <el-tab-pane label="回传日志" name="log">
+          <logList v-if="activeName == 'log'"></logList>
+        </el-tab-pane>
+      </el-tabs>
+    </el-card>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { useRouter, useRoute } from 'vue-router'
+import type { TabsPaneContext } from 'element-plus'
+import advertiser from "./tabs/advertiserList/index.vue"
+import logList from "./tabs/logList/index.vue"
+const router = useRouter()
+const route = useRoute()
+const handChange = (tab: TabsPaneContext, event: Event) => {
+  activeName.value = tab
+}
+const activeName = ref('backConfiguration')
+onMounted(() => {
+  activeName.value = route.query.tab || 'backConfiguration'
+});
+</script>
+
+<style scoped lang="scss"></style>

+ 231 - 0
src/views/payBack/juliangPlus/tabs/advertiserList/form/create.vue

@@ -0,0 +1,231 @@
+<template>
+  <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+    <el-form-item label="配置名称" prop="name" :rules="[{ required: true, message: '配置名称必须填写' }]" label-width="120px">
+      <el-input v-model="formCallback.name" auto-complete="off" placeholder="请输入配置名称"></el-input>
+    </el-form-item>
+    <el-form-item label="ROI全量上报" prop="is_roi" :rules="[{ required: true, message: 'ROI全量上报必须选择' }]" label-width="120px">
+      <template #label>
+        <div class="flex items-center">
+          <el-tooltip placement="top">
+            <template #content>
+              <span>ROI全量上报用户染色时间范围内的有效订单</span> <br />
+            </template>
+            <el-icon>
+              <InfoFilled />
+            </el-icon>
+          </el-tooltip>
+          <span>ROI全量上报</span>
+        </div>
+      </template>
+      <el-radio-group v-model="formCallback.is_roi">
+        <el-radio :label="1">是</el-radio>
+        <el-radio :label="2">否</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <div v-if="formCallback.is_roi != 1">
+      <el-form-item label="充值行为" prop="charge_type" :rules="[{ required: true, message: '充值行为必须选择' }]"
+        label-width="120px">
+        <template #label>
+          <div class="flex items-center">
+            <el-tooltip placement="top">
+              <template #content>
+                <span>首充:染色时间范围内新增用户的第一笔充值</span> <br />
+              </template>
+              <el-icon>
+                <InfoFilled />
+              </el-icon>
+            </el-tooltip>
+            <span>充值行为</span>
+          </div>
+        </template>
+        <el-radio-group v-model="formCallback.charge_type">
+          <el-radio :label="1">首充</el-radio>
+          <el-radio :label="0">所有充值</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="金额项" prop="charge_money_map" :rules="[{ required: true, message: '金额项必须填写' }]">
+        <div>
+          <el-button type="primary" @click="addBack">添加</el-button>
+          <div class="custom-item" v-for="(item, index) in formCallback.charge_money_map" :key="index">
+            <div class="flex items-end w-full">
+              <span>
+                <el-icon @click="removeItem(index)" class="cursor-pointer"
+                  :color="index === formCallback.charge_money_map.length - 1 && formCallback.charge_money_map.length > 1 ? '#000' : '#999'">
+                  <Delete />
+                </el-icon>
+              </span>
+              <div class="flex flex-col">
+                <div style="width:150px;margin:0 5px;">最小金额(包含)</div>
+                <el-input style="width:150px;margin:0 5px;" disabled :min="0" v-model.number="item.min_money"
+                  type="number">
+                  <template #append>元</template>
+                </el-input>
+              </div>
+              <span>—</span>
+              <div class="flex flex-col">
+                <div style="width:150px;margin:0 5px;">最大金额(不包含)</div>
+                <el-input style="width:150px;margin:0 5px;" :min="0" v-model.number="item.max_money" type="number">
+                  <template #append>元</template>
+                </el-input>
+              </div>
+              <div class="flex flex-col ml-3 mr-3">
+                <div style="width:200px;margin:0 5px;">回传参数</div>
+                <el-radio-group v-model="item.callback_type">
+                  <el-radio :label="1">全部回传</el-radio>
+                  <el-radio :label="2">全部不回传</el-radio>
+                  <el-radio :label="3">自定义</el-radio>
+                </el-radio-group>
+              </div>
+              <div v-if="item.callback_type == 3">
+                <el-select v-model="item.callback_param" filterable clearable placeholder="请选择">
+                  <el-option v-for="item in customOptions" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-form-item>
+    </div>
+    <div class="flex justify-end">
+      <el-button type="primary" @click="submitForm(form)">{{
+        $t('system.confirm')
+      }}</el-button>
+    </div>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+import { Delete, InfoFilled } from '@element-plus/icons-vue';
+import { FormInstance } from 'element-plus';
+import { optionsJLEventCustomRate, callbackJlEventAdd, callbackJlEventUpdate } from '@/api/backConfig/juliangPlus/index'
+import { useRouter, useRoute } from 'vue-router'
+const router = useRouter()
+const route = useRoute()
+const props = defineProps({
+  primary: Object | null,
+});
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const form = ref()
+const promotion = ref<string | null | number>('')
+const formCallback = ref({
+  name: '',
+  is_roi: 2,
+  charge_type: 1,
+  charge_money_map: [{
+    min_money: 0,
+    max_money: null,
+    callback_type: 1,
+    callback_param: '',
+  }],
+})
+const customOptions = ref([])
+const init = () => {
+  optionsJLEventCustomRate().then(res => {
+    console.log(res);
+    customOptions.value = res.data
+  })
+}
+
+// 提交回传配置
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  loading.value = true;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+        if (formCallback.value.is_roi == 2) {
+          if (!ValiteFunc1()) {
+            loading.value = false;
+            return
+          }
+        } else if (formCallback.value.is_roi == 1) {
+          delete formCallback.value.charge_money_map
+        }
+        let api;
+        if (props?.primary?.id) {
+          api = callbackJlEventUpdate
+        } else {
+          api = callbackJlEventAdd
+        }
+        api(formCallback.value).then(res => {
+          ElMessage.success(res.message)
+          loading.value = false;
+          emit('close')
+          if (promotion.value) {
+            router.push({ path: '/promotion/promotionList', query: { tab: 0 } })
+          }
+        }).catch(e => {
+          loading.value = false;
+        })
+      } else {
+        loading.value = false;
+      }
+    })
+    .then(() => { });
+}
+
+const ValiteFunc1 = () => {
+  let valite = true
+  formCallback.value.charge_money_map.forEach((item, idx) => {
+    if (item.min_money == null || item.max_money == null) {
+      ElMessage.error('请输入金额')
+      valite = false;
+    } else if (
+      item.max_money <=
+      item.min_money &&
+      item.max_money
+    ) {
+      ElMessage.error("金额上限小于或等于金额下限,请重新输入");
+      valite = false;
+    }
+  })
+  return valite;
+}
+
+const removeItem = (idx: number) => {
+  if (formCallback.value.charge_money_map.length > 1 && idx === formCallback.value.charge_money_map.length - 1) {
+    formCallback.value.charge_money_map.splice(idx, 1);
+  }
+}
+const addBack = () => {
+  if (!ValiteFunc1()) {
+    return
+  }
+  formCallback.value.charge_money_map.push({
+    min_money: formCallback.value.charge_money_map.at(-1)?.max_money || 0,
+    max_money: null,
+    callback_type: 1,
+    callback_param: '',
+  });
+}
+
+if (props.primary) {
+  formCallback.value = JSON.parse(JSON.stringify(props.primary))
+}
+onMounted(() => {
+  promotion.value = route.query?.promotionId
+  init()
+});
+</script>
+
+<style lang="scss" scoped>
+.custom-item {
+  margin: 10px 0;
+  background: #f7f7f7;
+  padding: 20px;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+}
+
+:deep(.small-title) {
+  font-size: 10px;
+  color: grey;
+}
+
+:deep(.el-radio-group) {
+  flex-wrap: nowrap;
+}
+</style>

+ 235 - 0
src/views/payBack/juliangPlus/tabs/advertiserList/form/paybackConfig.vue

@@ -0,0 +1,235 @@
+<template>
+  <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+    <el-form-item label="回传配置ID" prop="ids" :rules="[{ required: false, message: '巨量账户ID必须填写' }]" label-width="120px">
+      <span v-for="item in formCallback.ids" :key="item" class="account-ids">{{ item }}</span>
+    </el-form-item>
+    <el-form-item label="ROI全量上报" prop="is_roi" :rules="[{ required: true, message: 'ROI全量上报必须选择' }]" label-width="120px">
+      <template #label>
+        <div class="flex items-center">
+          <el-tooltip placement="top">
+            <template #content>
+              <span>ROI全量上报用户染色时间范围内的有效订单</span> <br />
+            </template>
+            <el-icon>
+              <InfoFilled />
+            </el-icon>
+          </el-tooltip>
+          <span>ROI全量上报</span>
+        </div>
+      </template>
+      <el-radio-group v-model="formCallback.is_roi">
+        <el-radio :label="1">是</el-radio>
+        <el-radio :label="2">否</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <div v-if="formCallback.is_roi != 1">
+      <el-form-item label="充值行为" prop="charge_type" :rules="[{ required: true, message: '充值行为必须选择' }]"
+        label-width="120px">
+        <template #label>
+          <div class="flex items-center">
+            <el-tooltip placement="top">
+              <template #content>
+                <span>首充:染色时间范围内新增用户的第一笔充值</span> <br />
+              </template>
+              <el-icon>
+                <InfoFilled />
+              </el-icon>
+            </el-tooltip>
+            <span>充值行为</span>
+          </div>
+        </template>
+        <el-radio-group v-model="formCallback.charge_type">
+          <el-radio :label="1">首充</el-radio>
+          <el-radio :label="0">所有充值</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="金额项" prop="charge_money_map" :rules="[{ required: true, message: '金额项必须填写' }]">
+        <div>
+          <el-button type="primary" @click="addBack">添加</el-button>
+          <div class="custom-item" v-for="(item, index) in formCallback.charge_money_map" :key="index">
+            <div class="flex items-end w-full">
+              <span>
+                <el-icon @click="removeItem(index)" class="cursor-pointer"
+                  :color="index === formCallback.charge_money_map.length - 1 && formCallback.charge_money_map.length > 1 ? '#000' : '#999'">
+                  <Delete />
+                </el-icon>
+              </span>
+              <div class="flex flex-col">
+                <div style="width:150px;margin:0 5px;">最小金额(包含)</div>
+                <el-input style="width:150px;margin:0 5px;" disabled :min="0" v-model.number="item.min_money"
+                  type="number">
+                  <template #append>元</template>
+                </el-input>
+              </div>
+              <span>—</span>
+              <div class="flex flex-col">
+                <div style="width:150px;margin:0 5px;">最大金额(不包含)</div>
+                <el-input style="width:150px;margin:0 5px;" :min="0" v-model.number="item.max_money" type="number">
+                  <template #append>元</template>
+                </el-input>
+              </div>
+              <div class="flex flex-col ml-3 mr-3">
+                <div style="width:200px;margin:0 5px;">回传参数</div>
+                <el-radio-group v-model="item.callback_type">
+                  <el-radio :label="1">全部回传</el-radio>
+                  <el-radio :label="2">全部不回传</el-radio>
+                  <el-radio :label="3">自定义</el-radio>
+                </el-radio-group>
+              </div>
+              <div v-if="item.callback_type == 3">
+                <el-select v-model="item.callback_param" filterable clearable placeholder="请选择">
+                  <el-option v-for="item in customOptions" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-form-item>
+    </div>
+    <div class="flex justify-end">
+      <el-button type="primary" @click="submitForm(form)">{{
+        $t('system.confirm')
+      }}</el-button>
+    </div>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+import { Delete, InfoFilled } from '@element-plus/icons-vue';
+import { FormInstance } from 'element-plus';
+import { optionsJLEventCustomRate, callbackJlEventUpdate } from '@/api/backConfig/juliangPlus/index'
+import { useRouter, useRoute } from 'vue-router'
+const router = useRouter()
+const route = useRoute()
+const props = defineProps({
+  primary: Object | null,
+});
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const form = ref()
+const formCallback = ref({
+  name: '',
+  is_roi: 2,
+  charge_type: 1,
+  charge_money_map: [{
+    min_money: 0,
+    max_money: null,
+    callback_type: 1,
+    callback_param: '',
+  }],
+})
+const customOptions = ref([])
+const init = () => {
+  optionsJLEventCustomRate().then(res => {
+    console.log(res);
+    customOptions.value = res.data
+  })
+}
+
+// 提交回传配置
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  loading.value = true;
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+        if (formCallback.value.is_roi == 2) {
+          if (!ValiteFunc1()) {
+            loading.value = false;
+            return
+          }
+        } else if (formCallback.value.is_roi == 1) {
+          delete formCallback.value.charge_money_map
+        }
+        if (props?.primary?.ids) {
+          callbackJlEventUpdate(formCallback.value).then(res => {
+            ElMessage.success(res.message)
+            loading.value = false;
+            emit('close')
+          }).catch(e => {
+            loading.value = false;
+          })
+        }
+
+      } else {
+        loading.value = false;
+      }
+    })
+    .then(() => { });
+}
+
+const ValiteFunc1 = () => {
+  let valite = true
+  formCallback.value.charge_money_map.forEach((item, idx) => {
+    if (item.min_money == null || item.max_money == null) {
+      ElMessage.error('请输入金额')
+      valite = false;
+    } else if (
+      item.max_money <=
+      item.min_money &&
+      item.max_money
+    ) {
+      ElMessage.error("金额上限小于或等于金额下限,请重新输入");
+      valite = false;
+    }
+  })
+  return valite;
+}
+
+const removeItem = (idx: number) => {
+  if (formCallback.value.charge_money_map.length > 1 && idx === formCallback.value.charge_money_map.length - 1) {
+    formCallback.value.charge_money_map.splice(idx, 1);
+  }
+}
+const addBack = () => {
+  if (!ValiteFunc1()) {
+    return
+  }
+  formCallback.value.charge_money_map.push({
+    min_money: formCallback.value.charge_money_map.at(-1)?.max_money || 0,
+    max_money: null,
+    callback_type: 1,
+    callback_param: '',
+  });
+}
+
+if (props.primary) {
+  console.log(props.primary, 'props.primaryprops.primary');
+  if (props.primary.ids) {
+    formCallback.value = JSON.parse(JSON.stringify(props.primary))
+  }
+}
+onMounted(() => {
+  init()
+});
+</script>
+
+<style lang="scss" scoped>
+.custom-item {
+  margin: 10px 0;
+  background: #f7f7f7;
+  padding: 20px;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+}
+
+:deep(.small-title) {
+  font-size: 10px;
+  color: grey;
+}
+
+:deep(.el-radio-group) {
+  flex-wrap: nowrap;
+}
+
+.account-ids {
+  padding: 0 10px;
+  border: 1px solid #c0c4cc;
+  margin-right: 5px;
+  border-radius: 5px;
+  background-color: #c0c4cc;
+  box-shadow: 0 0 0 1px #c0c4cc inset;
+}
+</style>

+ 159 - 0
src/views/payBack/juliangPlus/tabs/advertiserList/index.vue

@@ -0,0 +1,159 @@
+<template>
+  <Search :search="search" :reset="reset">
+    <template v-slot:body>
+      <el-form-item label="回传配置ID">
+        <el-input placeholder="请输入回传配置ID" class="input" icon="search" v-model="query.id" clearable></el-input>
+      </el-form-item>
+      <el-form-item label="配置名称">
+        <el-input placeholder="请输入配置名称" class="input" icon="search" v-model="query.name" clearable></el-input>
+      </el-form-item>
+      <el-form-item label="关联推广ID">
+        <el-input placeholder="关联推广ID" class="input" icon="search" v-model="query.promotion_id" clearable></el-input>
+      </el-form-item>
+    </template>
+  </Search>
+  <div class="table-default">
+    <div class="pt-5 pl-2">
+      <el-button type="primary" v-action="'callback.JLEvent\\JLEvent.add'" size="default"
+        @click="openForm(null)">新增回传配置</el-button>
+      <el-button size="default" v-action="'callback.JLEvent\\JLEvent.update'" @click="mulSet">批量设置</el-button>
+    </div>
+    <el-table :data="tableData" class="mt-3" v-loading="loading" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="80"></el-table-column>
+      <el-table-column label="回传配置ID" min-width="100" prop="id"></el-table-column>
+      <el-table-column label="配置名称" min-width="180" show-overflow-tooltip prop="name"></el-table-column>
+      <el-table-column label="充值行为" min-width="160" prop="charge_type_str"></el-table-column>
+      <el-table-column label="关联推广ID" min-width="100" show-overflow-tooltip prop="promotion_id">
+        <template #default="scope">
+          <el-button link type="primary" size="default" @click="goToPromotion(scope.row)">
+            {{ scope.row.promotion_id }}
+          </el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="最新更改时间" width="260">
+        <template #header>
+          <div class="flex items-center">
+            <span>最新更改时间</span>
+            <el-tooltip placement="top">
+              <template #content> 更改时间之后的订单全部重新计算<br /></template>
+              <el-icon>
+                <InfoFilled />
+              </el-icon>
+            </el-tooltip>
+          </div>
+        </template>
+        <template #default="scope">
+          <span>{{ scope.row.updated_at }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" min-width="200" fixed="right">
+        <template #default="scope">
+          <el-button link type="primary" size="small" @click="handleEdit(scope.$index, scope.row)"
+            v-action="'callback.JLEvent\\JLEvent.update'">编辑</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <Paginate />
+  </div>
+  <!-- 批量回传配置 -->
+  <Dialog v-model="paybackVisible" title="批量回传配置" width="1000px" destroy-on-close>
+    <paybackConfig @close="closeType('paybackVisible')" :primary="currentPayback"></paybackConfig>
+  </Dialog>
+
+  <Dialog v-model="addAccountVisible" title="新增回传配置" width="1000px" destroy-on-close>
+    <create @close="closeType('addAccountVisible')" :primary="currentPayback"></create>
+  </Dialog>
+</template>
+
+<script lang="ts"  setup>
+import { InfoFilled } from '@element-plus/icons-vue';
+import { computed, onMounted, ref } from 'vue';
+import create from './form/create.vue';
+import paybackConfig from './form/paybackConfig.vue';
+import { useGetList } from '@/hook/curd/useGetList';
+const router = useRouter();
+const route = useRoute();
+const api = 'callback/jlEvent/list';
+
+const { data, query, search, reset, loading } = useGetList(api, true);
+const tableData = computed(() => data.value?.data);
+const addAccountVisible = ref(false)
+const currentPayback = ref<object | null>(null)
+
+const multipleSelection = ref([])
+const paybackVisible = ref(false) //设置回传弹窗
+
+const closeType = (type: string) => {
+  switch (type) {
+    case 'paybackVisible':
+      paybackVisible.value = false
+      break;
+    case 'addAccountVisible':
+      addAccountVisible.value = false
+      break;
+  }
+  search()
+}
+
+const goToPromotion = (row: object) => {
+  router.push({ path: '/promotion/promotionList', query: { tab: 1, id: row.promotion_id } })
+}
+
+// 批量设置
+const mulSet = () => {
+  if (multipleSelection.value.length <= 0) {
+    return ElMessage.warning('至少选择一条数据');
+  } else {
+    const content = '确定批量设置回传配置吗?'
+
+    ElMessageBox.confirm(
+      content,
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }
+    )
+      .then(() => {
+        paybackVisible.value = true;
+        currentPayback.value = {
+          name: '',
+          is_roi: 2,
+          charge_type: 1,
+          charge_money_map: [{
+            min_money: 0,
+            max_money: null,
+            callback_type: 1,
+            callback_param: '',
+          }],
+        }
+        currentPayback.value.ids = multipleSelection.value.map(el => el.id)
+      })
+      .catch(() => {
+
+      })
+  }
+}
+// 编辑
+const handleEdit = (index, row) => {
+  console.log(row)
+  currentPayback.value = row
+  currentPayback.value.ids = [row.id]
+  addAccountVisible.value = true
+}
+// 全选
+const handleSelectionChange = (val) => {
+  multipleSelection.value = val;
+}
+
+const openForm = (data: any) => {
+  currentPayback.value = null;
+  addAccountVisible.value = true
+};
+onMounted(() => {
+  query.value.id = route.query.id
+  search();
+});
+</script>
+<style  lang='scss' scoped></style>

+ 16 - 0
src/views/payBack/juliangPlus/tabs/logList/excelTitle.ts

@@ -0,0 +1,16 @@
+// 导出中文/字段
+export const titleObj = {
+  回传配置名称: 'callback_config_name',
+  订单号: 'order_no',
+  用户ID: 'uid',
+  注册IP: 'user_ranse_ip',
+  染色注册时间: 'user_ranse_start_at',
+  充值时间: 'order_created_at',
+  充值金额: 'order_price',
+  是否回传: 'filter_type_str',
+  备注: 'filter_reason',
+  广告计划ID: 'adv_promotion_id',
+  回传百分比: 'current_rate',
+  推广名称: 'ranse_name',
+  配置比例: 'ext_a'
+};

+ 284 - 0
src/views/payBack/juliangPlus/tabs/logList/index.vue

@@ -0,0 +1,284 @@
+<template>
+  <div>
+    <Search :search="search" :reset="resetQuery">
+      <template v-slot:body>
+        <el-form-item label="金额筛选范围" prop="callback_config_id" class="flex items-center">
+          <div class="flex items-end w-full">
+            <div class="flex flex-col">
+              <el-input style="width:200px;margin:0 5px;" clearable placeholder="最小金额(包含)"
+                v-model.number="query.order_price_start">
+                <template #append>元</template>
+              </el-input>
+            </div>
+            <span>—</span>
+            <div class="flex flex-col">
+              <el-input style="width:200px;margin:0 5px;" clearable placeholder="最大金额(不包含)"
+                v-model.number="query.order_price_end">
+                <template #append>元</template>
+              </el-input>
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="回传配置名称" prop="callback_config_id">
+          <el-select v-model="query.callback_config_id" class="w-full" clearable filterable remote
+            :remote-method="(query) => { remoteMethod(query, 'callbackJlEventList') }" placeholder="请选择回传配置名称">
+            <el-option v-for="item in jlEventList" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="小程序" prop="miniprogram_id">
+          <el-select v-model="query.miniprogram_id" filterable remote
+            :remote-method="(query) => { remoteMethod(query, 'channelMiniprogram') }" clearable placeholder="选择小程序">
+            <el-option v-for="item in channelMiniprogram" :key="item.miniprogram_id" :label="item.name"
+              :value="item.miniprogram_id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="订单号" prop="order_no">
+          <el-input v-model="query.order_no" placeholder="请输入订单号" clearable />
+        </el-form-item>
+        <el-form-item label="用户ID" prop="uid">
+          <el-input v-model="query.uid" placeholder="请输入用户ID" clearable />
+        </el-form-item>
+        <el-form-item label="广告计划ID" prop="adv_promotion_id">
+          <el-input v-model="query.adv_promotion_id" placeholder="请输入广告计划ID" clearable />
+        </el-form-item>
+        <el-form-item label="染色注册时间">
+          <el-date-picker unlink-panels clearable @change="userTimeChange" format="YYYY/MM/DD" value-format="YYYY-MM-DD"
+            v-model="query.userTime" type="daterange" :shortcuts="shortcuts" range-separator="To" start-placeholder="开始时间"
+            end-placeholder="结束时间" />
+        </el-form-item>
+        <el-form-item label="充值时间">
+          <el-date-picker unlink-panels clearable @change="orderTimeChange" format="YYYY/MM/DD" value-format="YYYY-MM-DD"
+            v-model="query.orderTime" type="daterange" :shortcuts="shortcuts" range-separator="To"
+            start-placeholder="开始时间" end-placeholder="结束时间" />
+        </el-form-item>
+
+      </template>
+      <template v-slot:extra_button>
+        <exportExcel api="callback/juliangAccount/log/list" sheet_name="巨量2.0事件-微信小程序" :title_obj="titleObj"
+          :extro_params="{ is_export: true, callback_type: 2, ...query }">
+        </exportExcel>
+      </template>
+    </Search>
+    <div class="table-default">
+      <el-table :data="tableData" class="mt-3" v-loading="loading">
+        <el-table-column prop="callback_config_name" label="回传配置名称" fixed="left" min-width="200px" />
+        <el-table-column prop="order_no" show-overflow-tooltip label="订单号" min-width="200px">
+        </el-table-column>
+        <el-table-column prop="uid" label="用户ID" show-overflow-tooltip>
+        </el-table-column>
+        <el-table-column prop="user_ranse_ip" label="注册IP" show-overflow-tooltip min-width="200px">
+        </el-table-column>
+        <el-table-column prop="user_ranse_start_at" label="染色注册时间" show-overflow-tooltip min-width="200px" />
+        <el-table-column prop="order_created_at" label="充值时间" show-overflow-tooltip min-width="200px" />
+        <el-table-column prop="order_price" label="充值金额" show-overflow-tooltip min-width="150px">
+        </el-table-column>
+        <el-table-column prop="filter_type_str" label="是否回传" show-overflow-tooltip>
+          <template #default="scope">
+            <div class="wrapper">
+              <span class="text-lg font-bold content">
+                {{ scope.row.filter_type_str }}
+              </span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="filter_reason" label="备注" show-overflow-tooltip min-width="200px">
+        </el-table-column>
+        <el-table-column prop="adv_promotion_id" label="广告计划ID" show-overflow-tooltip min-width="150px">
+        </el-table-column>
+        <el-table-column prop="current_rate" label="回传百分比" show-overflow-tooltip min-width="150px">
+          <template #header>
+            <div class="flex items-center">
+              <span>回传百分比</span>
+              <el-tooltip placement="top">
+                <template #content>
+                  回传百分比=已成功回传的订单数(不包括补传)/(比例过滤掉的订单数+成功回传的订单数(不包括补传))*100%</template>
+                <el-icon>
+                  <InfoFilled />
+                </el-icon>
+              </el-tooltip>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="ranse_name" label="推广名称" show-overflow-tooltip min-width="150px">
+        </el-table-column>
+        <el-table-column prop="ext_a" label="配置比例" show-overflow-tooltip min-width="150px">
+        </el-table-column>
+        <el-table-column label="操作" fixed="right">
+          <template #default="scope">
+            <el-button @click="openType('repairVisible', scope.row)" link type="primary" size="small"
+              v-action="'callback.CallbackLog.callbackAgain'">补传</el-button>
+            <br />
+            <el-button link type="primary" size="small" @click="openType('backConfigVisible', scope.row)">回传信息</el-button>
+            <br />
+          </template>
+        </el-table-column>
+      </el-table>
+      <Paginate />
+    </div>
+    <el-dialog v-model="repairVisible" destroy-on-close title="补传" draggable width="30%">
+      <div>金额: <span class="text-lg font-bold content">{{ repairData.order_price }}</span></div>
+      <div>订单号:<span class="text-lg font-bold content">{{ repairData.order_no }}</span></div>
+      <template #footer>
+        <el-button @click="repairVisible = false">取消</el-button>
+        <el-button type="primary" @click="repairChange">
+          确定
+        </el-button>
+      </template>
+    </el-dialog>
+    <el-dialog v-model="backConfigVisible" destroy-on-close draggable title="回传信息" width="30%">
+      <el-form ref="form" :model="backConfigData" label-width="80px">
+        <el-form-item label="请求信息">
+          <el-input type="textarea" v-model="backConfigData.report_param" :rows="6" disabled></el-input>
+        </el-form-item>
+        <el-form-item label="请求结果">
+          <el-input type="textarea" v-model="backConfigData.report_result" :rows="6" disabled></el-input>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { shortcuts } from '@/utils/shortcuts'
+import { useRouter, useRoute } from 'vue-router'
+import { InfoFilled } from '@element-plus/icons-vue';
+import { useGetList } from '@/hook/curd/useGetList';
+import { titleObj } from "./excelTitle"
+import Cache from '@/support/cache';
+import { callbackJlEventList } from '@/api/backConfig/juliangPlus/index'
+import { callbackJuliangAccountLogCallbackAgain } from '@/api/backConfig/index'
+import {
+  channelMiniprogramUseList,
+} from '@/api/orders/index'
+import moment from 'moment';
+const api = 'callback/juliangAccount/log/list';
+const router = useRouter()
+const route = useRoute()
+const { data, query, search, reset, loading } = useGetList(api);
+const repairVisible = ref(false)
+const repairData = ref({})
+const backConfigVisible = ref(false)
+const backConfigData = ref({})
+const current = ref({})
+const start_date = moment().format('YYYY-MM-DD')
+const end_date = moment().format('YYYY-MM-DD')
+const jlEventList = ref([])
+const channelMiniprogram = ref([])
+
+const remoteMethod = (query: string, type: string,) => {
+  console.log(query, 'queryquery', type);
+  switch (type) {
+    case 'callbackJlEventList':
+      initRemoteOption('callbackJlEventList', { name: query })
+      break;
+    case 'channelMiniprogram':
+      initRemoteOption('channelMiniprogram', { name: query })
+      break;
+  }
+}
+
+const initRemoteOption = (type: string, params?: object) => {
+  switch (type) {
+    case 'callbackJlEventList':
+      callbackJlEventList({ limit: 999, ...params }).then(res => {
+        console.log(res);
+        jlEventList.value = res.data
+      })
+      break;
+    case 'channelMiniprogram':
+      channelMiniprogramUseList({ limit: 30, ...params }).then(res => {
+        channelMiniprogram.value = res.data
+      })
+      break;
+  }
+}
+
+const userTimeChange = (e: object) => {
+  console.log(e, query.value.userTime, 'timeChangetimeChangetimeChange');
+  if (query.value.userTime) {
+    const timeArr = toRaw(e);
+    query.value.user_ranse_start_at_begin_time = timeArr[0]
+    query.value.user_ranse_start_at_end_time = timeArr[1]
+  } else {
+    delete query.value.user_ranse_start_at_begin_time
+    delete query.value.user_ranse_start_at_end_time
+  }
+}
+const orderTimeChange = (e: object) => {
+  console.log(e, 'timeChangetimeChangetimeChange');
+  if (query.value.orderTime) {
+    const timeArr = toRaw(e);
+    query.value.order_created_at_begin_time = timeArr[0]
+    query.value.order_created_at_end_time = timeArr[1]
+  } else {
+    delete query.value.order_created_at_begin_time
+    delete query.value.order_created_at_end_time
+  }
+}
+//补传
+const repairChange = () => {
+  callbackJuliangAccountLogCallbackAgain({ log_id: repairData.value.id }).then(res => {
+    console.log(res, 'callbackJuliangAccountLogCallbackAgain');
+    if (res.data == '补传失败') {
+      ElMessage.error(res.data)
+    } else if (res.data == '补传成功') {
+      ElMessage.success(res.data)
+    }
+  })
+}
+
+const resetQuery = () => {
+  query.value = Object.assign({ page: query.value.page, limit: query.value.limit, callback_type: query.value.callback_type });
+  search()
+}
+const openType = (type: string, data: object) => {
+  current.value = data;
+  switch (type) {
+    case 'repairVisible':
+      repairVisible.value = true
+      repairData.value = data
+      break;
+    case 'backConfigVisible':
+      backConfigVisible.value = true
+      backConfigData.value = data
+      break;
+  }
+}
+const tableData = computed(() => data.value?.data);
+
+onMounted(() => {
+  query.value.orderTime = [start_date, end_date];
+  query.value.order_created_at_begin_time = start_date
+  query.value.order_created_at_end_time = end_date
+  query.value.callback_type = 2
+  if (JSON.parse(Cache.get('nav_data'))?.app.id) {
+    query.value.miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+  }
+  initRemoteOption('channelMiniprogram')
+  initRemoteOption('callbackJlEventList')
+  search();
+});
+</script>
+
+<style scoped lang="scss">
+.table-default {
+  .set-warpper {
+    height: 60px;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+  }
+
+  .wrapper {
+    margin: 8px;
+
+    .label {
+      margin-right: 6px;
+    }
+
+    .content {
+      font-size: 15px;
+    }
+  }
+}
+</style>

+ 57 - 15
src/views/promotion/promotionList/form/backConfig.vue

@@ -11,12 +11,11 @@
         <el-option v-for="(item, index) in callbackTypeList" :key="index" :label="item.name" :value="item.value" />
       </el-select>
     </el-form-item>
-    <el-form-item label="回传配置" prop="callback_config_id"
+    <el-form-item label="回传配置" prop="callback_config_id" v-if="formDataForm.callback_type"
       :rules="[{ required: true, message: '请选择回传配置', trigger: 'change' }]">
       <el-select v-model="formDataForm.callback_config_id" filterable remote clearable :remote-method="remoteMethod"
         placeholder="选择回传配置">
-        <el-option v-for="(item, index) in callbackConfigList" :key="index" :label="item.adv_account_id"
-          :value="item.id" />
+        <el-option v-for="(item, index) in callbackConfigList" :key="index" :label="item.name" :value="item.id" />
       </el-select>
       <el-button type="primary" link size="default" @click="addPayback">新增回传</el-button>
     </el-form-item>
@@ -37,6 +36,7 @@ import { FormInstance } from 'element-plus';
 import { InfoFilled } from '@element-plus/icons-vue';
 import { callbackJuliangAccountList } from '@/api/backConfig/index'
 import { tuiguangPromotionUpdateCallbackConfig } from '@/api/promotion/index'
+import { callbackJlEventList } from '@/api/backConfig/juliangPlus/index'
 const props = defineProps({
   primary: Object,
 });
@@ -46,21 +46,64 @@ const callbackConfigList = ref([])
 const form = ref<FormInstance>();
 const loading = ref(false);
 const formDataForm = ref({ videos: [] })
-const callbackTypeList = ref([{ id: 1, value: 1, name: '巨量账户级回传' }])
+const callbackTypeList = ref([{ id: 1, value: 1, name: '头条-微信(账户级)' }, { id: 2, value: 2, name: '巨量2.0事件-微信小程序' }])
 const emit = defineEmits(['close']);
-const init = (params?: object) => {
-  callbackJuliangAccountList({ limit: 666, unbind: 1, ...params }).then(res => {
-    callbackConfigList.value = res.data
-  })
+const initRemoteOption = (type: string, params?: object) => {
+  switch (type) {
+    case 'callbackJlEventList':
+      callbackJlEventList({ limit: 999, unbind: 1, ...params }).then(res => {
+        console.log(res);
+        callbackConfigList.value = res.data
+      })
+      break;
+    case 'callbackJuliangAccountList':
+      callbackJuliangAccountList({ limit: 666, unbind: 1, ...params }).then(res => {
+        callbackConfigList.value = res.data
+      })
+      break;
+  }
 }
+
+watch(() => formDataForm.value.callback_type, (newValue, oldValue) => {
+  formDataForm.value.callback_config_id = ''
+  let type = formDataForm.value.callback_type
+  switch (type) {
+    case 1:
+      initRemoteOption("callbackJuliangAccountList")
+      break;
+    case 2:
+      initRemoteOption("callbackJlEventList")
+      break;
+  }
+})
 const addPayback = () => {
-  router.push({ path: '/payback/juliangAccount', query: { promotionId: props.primary.id } })
+  let type = formDataForm.value.callback_type
+  switch (type) {
+    case 1:
+      router.push({ path: '/payback/juliangAccount', query: { promotionId: props.primary.id } })
+      break;
+    case 2:
+      router.push({ path: '/payback/jlEvent', query: { promotionId: props.primary.id } })
+      break;
+  }
 }
 const remoteMethod = (query: string) => {
-  if (query) {
-    init({ account_name: query })
-  } else {
-    init()
+  let type = formDataForm.value.callback_type
+  switch (type) {
+    case 1:
+      if (query) {
+        initRemoteOption("callbackJuliangAccountList", { name: query })
+      } else {
+        initRemoteOption("callbackJuliangAccountList")
+      }
+      break;
+    case 2:
+      if (query) {
+        initRemoteOption("callbackJlEventList", { name: query })
+      } else {
+        initRemoteOption("callbackJlEventList")
+      }
+      break;
   }
 }
 const submitForm = (formEl: FormInstance | undefined) => {
@@ -88,7 +131,6 @@ if (props.primary) {
   formDataForm.value.name = props.primary.name
   formDataForm.value.id = props.primary.id
 }
-onMounted(async () => {
-  init()
+onMounted(() => {
 });
 </script>

+ 3 - 3
src/views/promotion/promotionList/form/create.vue

@@ -3,9 +3,9 @@
     <el-form-item label="推广名称" prop="name" :rules="[{ required: false, message: '推广名称必须填写' }]">
       <el-input disabled v-model="props.primary.name" clearable />
     </el-form-item>
-    <el-form-item label="入口章节" prop="series_sequence"
-      :rules="[{ required: true, message: '入口章节必须填写', trigger: 'change' }]">
-      <el-select v-model="formDataForm.series_sequence" filterable clearable placeholder="选择入口章节">
+    <el-form-item label="入口剧集" prop="series_sequence"
+      :rules="[{ required: true, message: '入口剧集必须填写', trigger: 'change' }]">
+      <el-select v-model="formDataForm.series_sequence" filterable clearable placeholder="选择入口剧集">
         <el-option v-for="(item, index) in sequenceOptions" :key="index" :label="item.series_name"
           :value="item.series_sequence" />
       </el-select>

+ 17 - 6
src/views/promotion/promotionList/index.vue

@@ -38,7 +38,7 @@
                 </template>
               </el-table-column>
               <el-table-column prop="miniprogram_name" label="小程序名称" min-width="200px" />
-              <el-table-column prop="total_episode_num" label="入口章节" min-width="200px">
+              <el-table-column prop="total_episode_num" label="入口剧集" min-width="200px">
                 <template #default="scope">
                   <div class="wrapper">
                     <span class="text-lg content">
@@ -87,8 +87,8 @@
                   </div>
                 </template>
               </el-table-column>
-              <el-table-column prop="callback_type_str" label="回传类型" />
-              <el-table-column prop="callback_config_id" label="回传配置ID" min-width="160px">
+              <el-table-column prop="callback_type_str" label="回传类型" min-width="130px" />
+              <el-table-column prop="callback_config_id" label="回传配置ID" min-width="100px">
                 <template #default="scope">
                   <el-button link type="primary" size="default" @click="goToTtHuicHuan(scope.row)">{{
                     scope.row.callback_config_id }}</el-button>
@@ -162,7 +162,7 @@
                   </div>
                 </template>
               </el-table-column>
-              <el-table-column prop="total_episode_num" label="入口章节">
+              <el-table-column prop="total_episode_num" label="入口剧集">
                 <template #default="scope">
                   <div class="wrapper">
                     <span class="content">
@@ -334,13 +334,24 @@ const openDetail = (row: object) => {
 }
 
 const goToTtHuicHuan = (row: object) => {
-  router.push({ path: '/payback/juliangAccount', query: { id: row.callback_config_id } })
+  let type = row.callback_type
+  switch (type) {
+    case 1:
+      router.push({ path: '/payback/juliangAccount', query: { id: row.callback_config_id } })
+      break;
+    case 2:
+      router.push({ path: '/payback/jlEvent', query: { id: row.callback_config_id } })
+      break;
+  }
 }
 const handChange = (tab: TabsPaneContext, event: Event) => {
   console.log(tab, event)
   router.push({ path: '/promotion/promotionList', query: { tab } })
   activeName.value = tab
   query.value.is_config = tab
+  if (!query.value.is_config) {
+    query.value = Object.assign({});
+  }
   search()
 }
 
@@ -367,7 +378,7 @@ const deletePromotion = (row: object) => {
 }
 
 const resetQuery = () => {
-  query.value = Object.assign({ page: query.value.page, limit: query.value.limit, miniprogram_id: query.value.miniprogram_id, is_config: query.value.is_config, id: query.value.id });
+  query.value = Object.assign({ page: query.value.page, limit: query.value.limit, miniprogram_id: query.value.miniprogram_id, is_config: query.value.is_config });
   search()
 }
 const openType = (type: string, data: object) => {

+ 0 - 122
src/views/promotion/ranse/index.vue

@@ -1,122 +0,0 @@
-<template>
-	<div class="page">
-		<div class="header">
-			<el-text class="text-notice">注意事项</el-text>
-			<el-text class="text-notice">1.充值用户 >=n 分钟连续未登录,如果点击新连接,会归属于新链接的优化师</el-text>
-			<el-text class="text-notice">2.未充值用户 >=n 分钟连续未登录,如果点击新连接,会归属于新链接的优化师</el-text>
-		</div>
-		
-		<div class="set-box" v-show="init_finish" >
-			<view class="set-box-item">
-				<el-text class="lab-txt"> <label style="color: red;margin-right: 10px;">*</label>非充值用户:</el-text>
-				<el-input-number v-model="no_charge_user_duration" :min="0"  @change="handleUnChargeUserChange" />
-				<el-text style="margin-left: 10px;color: 555;">分</el-text>
-			</view>
-			<view class="set-box-item">
-				<el-text class="lab-txt"> <label style="color: red;margin-right: 10px;">*</label>充值用户:</el-text>
-				<el-input-number v-model="charge_user_duration" :min="0"  @change="handleChargeUserChange" />
-				<el-text style="margin-left: 10px;color: 555;">分</el-text>
-			</view>
-			<view class="set-box-item"  v-action="'tuiguang.RanseConfig.setRanseDuration'" >
-				<el-button :disabled="permission" style="margin-left: 140px;margin-top: 30px;" type="primary" @click="saveSetting">确认</el-button>
-			</view>
-			
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-	import {getRanseDuration,setRanseDuration} from "@/api/promotion/ranse";
-	const rolesIdentify = inject('rolesIdentify');
-	const setData = ref({charge_user_duration:0,no_charge_user_duration:0})
-	const  charge_user_duration = ref(0);
-	const  no_charge_user_duration = ref(0);
-	const  init_finish = ref(0);
-	const permission = ref(true);
-	
-	const handleUnChargeUserChange = (value: number) => {
-		no_charge_user_duration.value = value;
-	}
-	const handleChargeUserChange = (value: number) => {
-		charge_user_duration.value = value;
-	}
-
-	const saveSetting =  ()  => {
-		if(charge_user_duration.value < 1){
-			ElMessage.error( '请填写充值用户染色时间');
-			return false;
-		}
-		if(no_charge_user_duration.value < 1){
-			ElMessage.error( '请填写未充值用户染色时间');
-			return false;
-		}
-			
-		if(charge_user_duration.value <= no_charge_user_duration.value){
-			ElMessage.error( '充值用户染色时间应大于非充值用户染色时间');
-			return false;
-		}
-		if(setData.value.charge_user_duration == charge_user_duration.value && setData.value.no_charge_user_duration == no_charge_user_duration.value){
-			ElMessage.success("操作成功")
-			return true;
-		}
-		setRanseDuration({charge_user_duration:charge_user_duration.value,no_charge_user_duration:no_charge_user_duration.value}).then(res => {
-			if(res.code ==10000){
-				ElMessage.success(res.message);
-				getSetData();
-			}
-		})
-	}
-	
-	const getSetData = async () =>{
-		if(rolesIdentify.value.includes('optimizer')){
-			await getRanseDuration().then(res => {
-				if(res.code ==10000){
-					setData.value.charge_user_duration = res.data.charge_user_duration;
-					setData.value.no_charge_user_duration = res.data.no_charge_user_duration;
-					charge_user_duration.value = res.data.charge_user_duration;
-					no_charge_user_duration.value = res.data.no_charge_user_duration;
-					init_finish.value = 1;
-				}
-				permission.value = false;
-			});
-		}
-		init_finish.value = 1;
-		
-		
-	}
-	onMounted(async () =>{
-		await getSetData();
-	});
-</script>
-
-<style lang="scss" scoped>
-.lab-txt{
-	display: inline-table;
-	width: 120px;
-	color: #555;
-	text-align: right;
-}
-.page{
-	background-color: #fff;
-	padding:40px 60px;
-}
-.header{
-	display: flex;
-	flex-direction: column;
-	background-color:#fcd3d3 ;
-	padding: 20px 30px;
-}
-.text-notice{
-	width: 100%;
-	color:#f56c6c;
-}
-.set-box{
-	margin-top: 20px;
-	display: flex;
-	flex-direction: column;
-}
-.set-box-item{
-	width: 100%;
-	margin-top: 20px;
-}
-</style>

+ 0 - 56
src/views/settleManage/financialControl/tabs/rechargeSettle/excelTitle.ts

@@ -66,59 +66,3 @@ export const titleObj = {
     备注: 'remark'
   }
 };
-
-/**
- * 
- *  <el-table-column prop="owner_name" label="商户名称" min-width="200px" v-if="!([4, 5].includes(query.status))">
-          <template #default="scope">
-            <div>{{ scope.row.owner_name }}</div>
-            <div>ID:{{ scope.row.company_uid }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column v-if="!([4, 5].includes(query.status))" prop="business_str" show-overflow-tooltip label="所属商务"
-          min-width="200px">
-        </el-table-column>
-        <el-table-column prop="tixian_money" label="提现金额" show-overflow-tooltip min-width="200px">
-        </el-table-column>
-        <el-table-column v-if="!([4, 5].includes(query.status))" prop="tixian_money" label="打款金额" show-overflow-tooltip
-          min-width="200px">
-        </el-table-column>
-        <el-table-column prop="created_at" show-overflow-tooltip label="提现时间" min-width="200px">
-        </el-table-column>
-        <el-table-column prop="card_no" label="提现账户" show-overflow-tooltip>
-        </el-table-column>
-        <el-table-column prop="status_str" label="状态" show-overflow-tooltip min-width="200px">
-          <template #default="scope">
-            <el-text class="mx-1" :type="colorType(scope.row)">{{ scope.row.status_str }}</el-text>
-            <div v-if="query.status == 4">
-              <el-text class="mx-1" :type="colorType(scope.row)">{{ scope.row.updated_at }}</el-text>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column prop="name_of_payee" label="收款人姓名" show-overflow-tooltip min-width="200px">
-        </el-table-column>
-        <el-table-column prop="remark" label="备注" show-overflow-tooltip>
-        </el-table-column>
-        <el-table-column label="操作" v-if="isShowOperate">
-          <template #default="scope">
-            <el-button link type="primary" size="small" v-if="query.status == 1"
-              @click="openType('withdrawDetailVisible', scope.row)">提现详情</el-button>
-            <br />
-            <div v-if="query.status == 1">
-              <el-button link type="primary" size="small" @click="openType('approvedVisible', scope.row)">审核通过</el-button>
-              <br />
-              <el-button link type="primary" size="small"
-                @click="openType('noapprovedVisible', scope.row)">审核不通过</el-button>
-              <br />
-            </div>
-            <div v-if="query.status == 3">
-              <el-button link type="primary" size="small"
-                @click="openType('applyVisible', scope.row, '打款成功')">打款成功</el-button>
-              <br />
-              <el-button link type="primary" size="small"
-                @click="openType('applyVisible', scope.row, '打款失败')">打款失败</el-button>
-              <br />
-            </div>
-          </template>
-        </el-table-column>
- */

+ 2 - 2
src/views/videoManage/videoLibraryList/detail.vue

@@ -52,14 +52,14 @@
   <div class="flex flex-col justify-between w-full sm:flex-row" style="width:100%;">
     <div class="table-default" style="width:100%;">
       <el-table :data="tableData" class="w-full mt-3" v-loading="loading" style="width:100%;">
-        <el-table-column prop="series_name" label="章节名称" />
+        <el-table-column prop="series_name" label="剧集名称" />
         <el-table-column prop="is_charge" label="是否付费">
           <template #default="scope">
             <span :class="scope.row.is_charge ? 'text-red-600' : 'text-green-300'">
               {{ scope.row.is_charge ? '【付费】' : '【免费】' }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="duration_str" label="章节时长" />
+        <el-table-column prop="duration_str" label="剧集时长" />
         <el-table-column label="操作" width="200">
           <template #default="scope">
             <el-button link type="primary" size="small" @click="play(scope.row)">播放</el-button>

+ 1 - 1
src/views/videoManage/videoLibraryList/form/detailForm.vue

@@ -3,7 +3,7 @@
     <el-form-item label="推广名称" prop="name" :rules="[{ required: true, message: '推广名称必须填写' }]">
       <el-input v-model="formDataForm.name" name="name" clearable />
     </el-form-item>
-    <el-form-item label="入口章节" prop="series_sequence" :rules="[{ required: false, message: '入口章节必须填写' }]">
+    <el-form-item label="入口剧集" prop="series_sequence" :rules="[{ required: false, message: '入口剧集必须填写' }]">
       <el-input disabled v-model="formDataForm.series_name" name="author" clearable />
     </el-form-item>
     <el-form-item label="充值模板配置">

+ 21 - 4
src/views/videoManage/videoLibraryList/form/uploadVideo.vue

@@ -15,13 +15,13 @@
       </div>
       <div class="w-full">
         <video-uploader :fileList="videofileList" :public_video_url='formDataForm.cover_image' @fileRemove="fileRemove"
-          @success="fileSuccess"></video-uploader>
+          @success="fileSuccess" @error="flieError"></video-uploader>
       </div>
     </el-form-item>
     <div class="flex justify-end">
-      <el-button type="primary" @click="submitForm(form)">{{
+      <!-- <el-button type="primary" @click="submitForm(form)">{{
         $t('system.confirm')
-      }}</el-button>
+      }}</el-button> -->
     </div>
   </el-form>
 </template>
@@ -36,6 +36,7 @@ const props = defineProps({
 const form = ref<FormInstance>();
 const loading = ref(false);
 const formDataForm = ref({ videos: [] })
+const subimtData =ref({ videos: [] });
 const videofileList = ref([])
 const emit = defineEmits(['close']);
 const fileRemove = (e: object) => {
@@ -43,10 +44,26 @@ const fileRemove = (e: object) => {
   console.log(formDataForm.value, e, 'fileRemove');
 }
 const fileSuccess = (e: object) => {
+  // console.log('111111111111111111111111111');
+  subimtData.value.videos = [];
+  
   formDataForm.value.videos.push({ name: e.fname, duration: parseInt(e.duration), url: e.url, key: e.key, })
-  console.log(formDataForm.value, 'fileSuccess');
+  subimtData.value.videos.push({ name: e.fname, duration: parseInt(e.duration), url: e.url, key: e.key, })
+  let params = {
+    video_id: formDataForm.value.video_id,
+    videos: subimtData.value.videos
+  }
+  videoStockEpisodeAdd(params).then(res => {
+		 ElMessage.success(e.fname +"上传成功!");
+  }).catch(e => {
+   
+  })
+  // console.log(formDataForm.value, 'fileSuccess');
 }
 
+// const flieError = (e:object) =>{
+// 	  console.log(e, 'rrrrrrrrrrrrrrrrr');
+// }
 const submitForm = (formEl: FormInstance | undefined) => {
   loading.value = true;
   console.log(formDataForm.value, formEl, 'formEl');

+ 4 - 2
vite.config.js

@@ -76,7 +76,7 @@ export default defineConfig(({ command, mode }) => {
             enabledCollections: ['ep'] //@iconify-json/ep 是 Element Plus 的图标库
           }),
           // 自动导入 Element Plus 组件//dev环境会特别慢加上,build的时候可以放开
-          ElementPlusResolver()
+          // ElementPlusResolver()
         ],
         dirs: ['src/components/', 'src/layout/'],
 
@@ -106,11 +106,13 @@ export default defineConfig(({ command, mode }) => {
         'vue-router',
         'element-plus/es/locale/lang/zh-cn',
         'element-plus/es/locale/lang/en',
+        'element-plus',
         'pinia',
         '@vueuse/core',
         'axios',
         '@wangeditor/editor',
-        '@wangeditor/editor-for-vue'
+        '@wangeditor/editor-for-vue',
+        '@tinymce/tinymce-vue'
       ]
     },
     publicDir: false,