Преглед на файлове

巨量2.0事件-微信小程序等

pansl преди 1 година
родител
ревизия
a1bb3e7235
променени са 40 файла, в които са добавени 2908 реда и са изтрити 156 реда
  1. 1 0
      package.json
  2. 26 0
      src/api/backConfig/juliangPlus/index.ts
  3. 59 0
      src/components/Draggable/index.vue
  4. 17 15
      src/components/admin/table/search.vue
  5. 15 15
      src/router/modules/charge.ts
  6. 153 0
      src/styles/devices.css
  7. 2 0
      src/styles/index.scss
  8. 1 1
      src/views/customer/audience/userDetail/tables/consume.vue
  9. 1 1
      src/views/customer/audience/userDetail/tables/readLog.vue
  10. 83 76
      src/views/dashboard/dataStatistics/notices.vue
  11. 0 1
      src/views/notice/mynotice/dataStatistics/notices.vue
  12. 1 1
      src/views/notice/mynotice/dataStatistics/todayData.vue
  13. 2 2
      src/views/notice/mynotice/index.vue
  14. 174 0
      src/views/officialAccount/attentionReply/form/create.vue
  15. 102 0
      src/views/officialAccount/attentionReply/form/generateLink.vue
  16. 159 0
      src/views/officialAccount/attentionReply/index.vue
  17. 147 0
      src/views/officialAccount/components/configPublic.vue
  18. 139 0
      src/views/officialAccount/keywordReply/form/create.vue
  19. 102 0
      src/views/officialAccount/keywordReply/form/generateLink.vue
  20. 211 0
      src/views/officialAccount/keywordReply/index.vue
  21. 459 0
      src/views/officialAccount/publicCustomMenu/index.vue
  22. 1 1
      src/views/ordersManage/tabs/rechargeList/userDetail/tables/consume.vue
  23. 1 1
      src/views/ordersManage/tabs/rechargeList/userDetail/tables/readLog.vue
  24. 1 1
      src/views/payBack/juliangAccount/tabs/advertiserList/form/create.vue
  25. 0 2
      src/views/payBack/juliangAccount/tabs/advertiserList/form/paybackConfig.vue
  26. 9 8
      src/views/payBack/juliangAccount/tabs/advertiserList/index.vue
  27. 1 2
      src/views/payBack/juliangAccount/tabs/logList/excelTitle.ts
  28. 1 1
      src/views/payBack/juliangAccount/tabs/logList/index.vue
  29. 32 0
      src/views/payBack/juliangPlus/index.vue
  30. 231 0
      src/views/payBack/juliangPlus/tabs/advertiserList/form/create.vue
  31. 235 0
      src/views/payBack/juliangPlus/tabs/advertiserList/form/paybackConfig.vue
  32. 159 0
      src/views/payBack/juliangPlus/tabs/advertiserList/index.vue
  33. 16 0
      src/views/payBack/juliangPlus/tabs/logList/excelTitle.ts
  34. 284 0
      src/views/payBack/juliangPlus/tabs/logList/index.vue
  35. 57 15
      src/views/promotion/promotionList/form/backConfig.vue
  36. 3 3
      src/views/promotion/promotionList/form/create.vue
  37. 17 6
      src/views/promotion/promotionList/index.vue
  38. 2 2
      src/views/videoManage/videoLibraryList/detail.vue
  39. 1 1
      src/views/videoManage/videoLibraryList/form/detailForm.vue
  40. 3 1
      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",

+ 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);
+}

+ 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>

+ 15 - 15
src/router/modules/charge.ts

@@ -5,34 +5,34 @@
 //   {
 //     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' },
-//         component: () =>
-//           import('@/views/dataStatistics/shortStatistical/index.vue')
+//         meta: { title: '巨量2.0事件-微信小程序', icon: 'home' },
+//         component: () => import('@/views/payBack/juliangPlus/index.vue')
 //       },
 //       {
-//         path: 'index3',
-//         name: 'user-account112',
-//         meta: { title: '投入产出', icon: 'home' },
+//         path: 'index1',
+//         name: 'user-account',
+//         meta: { title: '被关注回复', icon: 'home' },
 //         component: () =>
-//           import('@/views/dataStatistics/roiStatistical/index.vue')
+//           import('@/views/officialAccount/attentionReply/index.vue')
 //       },
 //       {
-//         path: 'index1',
-//         name: 'user-account',
-//         meta: { title: '充值统计', icon: 'home' },
+//         path: 'index3',
+//         name: 'user-account33',
+//         meta: { title: '关键字回复', icon: 'home' },
 //         component: () =>
-//           import('@/views/dataStatistics/rechargeStatistics/index.vue')
+//           import('@/views/officialAccount/keywordReply/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/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>

+ 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>

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

@@ -0,0 +1,147 @@
+<template>
+  <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+    <el-form-item label="配置公众号" prop="checkPlublics" :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.checkPlublics" @change="handleCheckedChange" class="flex flex-col">
+            <el-checkbox :key="sIndex" :label="item" v-for="(item, sIndex) in filteredCities">
+              {{ item.label }}
+            </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 { FormInstance } from 'element-plus';
+const props = defineProps({
+  primary: Object | null,
+});
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const formCallback = ref({ type: 1 })
+const filterPlublic = ref('')
+// const isIndeterminate = ref(false)
+const goalOptions = [
+  {
+    value: 'Option1',
+    label: 'Option1',
+  },
+  {
+    value: 'Option2',
+    label: 'Option2',
+  },
+  {
+    value: 'Option3',
+    label: 'Option3',
+  },
+  {
+    value: 'Option4',
+    label: 'Option4',
+  },
+  {
+    value: 'Option5',
+    label: 'Option5',
+  },
+  {
+    value: 'Option6',
+    label: 'Option6',
+  },
+  {
+    value: 'Option7',
+    label: 'Option7',
+  },
+  {
+    value: 'Option8',
+    label: 'Option8',
+  },
+  {
+    value: 'Option9',
+    label: 'Option9',
+  },
+  {
+    value: 'Option10',
+    label: 'Option10',
+  },
+]
+
+const filteredCities = computed(() => {
+  return goalOptions.filter(city => city.label.includes(filterPlublic.value));
+});
+
+const selectAllValue = computed(() => {
+  return formCallback.value?.checkPlublics?.length == goalOptions.length
+});
+
+const filterExistPlublic = () => {
+  console.log('filterExistPlublicfilterExistPlublic');
+}
+const handleCheckAllChange = (val: boolean) => {
+  formCallback.value.checkPlublics = val ? filteredCities.value : []
+  console.log(formCallback.value.checkPlublics, val, 'formCallback.value.checkPlublics');
+  // 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) {
+
+        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>
+.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>

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

@@ -0,0 +1,139 @@
+<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">
+      <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 v-model="formCallback.key" 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="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 v-for="o in 4" :key="o" class="text item">{{ 'List item ' + o }}</div>
+      </el-card>
+    </el-form-item>
+    <Dialog v-model="insertVisible" :title="insertTitle" destroy-on-close>
+      <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="account_id" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" v-model="formCallback.account_id" auto-complete="off"
+            placeholder="请输入标题"></el-input>
+        </el-form-item>
+        <el-form-item label="链接" prop="account_id" :rules="[{ required: true, message: '链接必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" v-model="formCallback.account_id" 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="account_id" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" type="textarea" v-model="formCallback.account_id" auto-complete="off"
+            placeholder="请输入标题"></el-input>
+        </el-form-item>
+      </div>
+    </Dialog>
+
+    <div class="flex justify-end">
+      <el-button type="primary" @click="submitForm(form)">{{
+        $t('system.confirm')
+      }}</el-button>
+    </div>
+  </el-form>
+  <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 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 insertType = ref()
+const insertTitle = computed(() => insertType.value == 'link' ? '插入小程序链接' : '插入纯文本')
+const insertChange = (type: string) => {
+  insertType.value = type;
+  insertVisible.value = true;
+  popoverVisible.value = false;
+}
+
+
+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;
+}
+</style>

+ 102 - 0
src/views/officialAccount/keywordReply/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>

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

@@ -0,0 +1,211 @@
+<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="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 = '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 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>

+ 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) => {

+ 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="充值模板配置">

+ 3 - 1
vite.config.js

@@ -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,