Bladeren bron

公众号运营等

pansl 1 jaar geleden
bovenliggende
commit
a060fd2e3e
28 gewijzigde bestanden met toevoegingen van 3138 en 758 verwijderingen
  1. 26 0
      src/api/customer/segment/index.ts
  2. 82 0
      src/api/officialAccount/attentionReply/index.ts
  3. 9 9
      src/api/officialAccount/keywordReply/index.ts
  4. 46 0
      src/api/officialAccount/newsService/index.ts
  5. 50 0
      src/api/officialAccount/publicCustomMenu/index.ts
  6. 7 1
      src/router/modules/charge.ts
  7. 543 0
      src/views/customer/segment/form/create.vue
  8. 47 0
      src/views/customer/segment/form/detail.vue
  9. 213 0
      src/views/customer/segment/form/validation.ts
  10. 167 0
      src/views/customer/segment/index.vue
  11. 78 33
      src/views/officialAccount/attentionReply/form/create.vue
  12. 46 28
      src/views/officialAccount/attentionReply/form/generateLink.vue
  13. 106 35
      src/views/officialAccount/attentionReply/index.vue
  14. 29 7
      src/views/officialAccount/components/configPublic.vue
  15. 116 0
      src/views/officialAccount/components/configPublicPlus.vue
  16. 48 25
      src/views/officialAccount/keywordReply/form/create.vue
  17. 19 7
      src/views/officialAccount/keywordReply/form/generateLink.vue
  18. 76 38
      src/views/officialAccount/keywordReply/index.vue
  19. 100 66
      src/views/officialAccount/newsService/form/create.vue
  20. 46 28
      src/views/officialAccount/newsService/form/generateLink.vue
  21. 89 0
      src/views/officialAccount/newsService/form/testSend.vue
  22. 184 52
      src/views/officialAccount/newsService/index.vue
  23. 51 0
      src/views/officialAccount/publicCustomMenu/form/create.vue
  24. 643 0
      src/views/officialAccount/publicCustomMenu/form/customMenu.vue
  25. 119 0
      src/views/officialAccount/publicCustomMenu/form/generateLink.vue
  26. 37 0
      src/views/officialAccount/publicCustomMenu/form/validation.ts
  27. 160 428
      src/views/officialAccount/publicCustomMenu/index.vue
  28. 1 1
      vite.config.js

+ 26 - 0
src/api/customer/segment/index.ts

@@ -0,0 +1,26 @@
+import http from '@/api/http';
+/**
+ * 用户分群-列表
+ */
+export function audienceManageUserGroupList(params: object) {
+  return http.get('/audienceManage/userGroup/list', params);
+}
+/**
+ * 用户分群-详情
+ */
+export function audienceManageUserGroupDetail(params: object) {
+  return http.get('/audienceManage/userGroup/detail', params);
+}
+/**
+ * 用户分群-添加
+ */
+export function audienceManageUserGroupAdd(params: object) {
+  return http.post('/audienceManage/userGroup/add', params);
+}
+/**
+ * 用户分群-删除
+ */
+export function audienceManageUserGroupDelete(params: object) {
+  return http.post('/audienceManage/userGroup/delete', params);
+}
+

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

@@ -0,0 +1,82 @@
+import http from '@/api/http';
+/**
+ * 关注回复-列表
+ */
+export function wechatPlatformSubscribeList(params: object) {
+  return http.get(`/wechatPlatform/subscribe/list`, params);
+}
+/**
+ * 关注回复-详情
+ */
+export function wechatPlatformSubscribeDetail(
+  id: number | string,
+  params?: object
+) {
+  return http.get(`/wechatPlatform/subscribe/detail/${id}`, params);
+}
+/**
+ * 关注回复-配置公众号列表
+ */
+export function wechatPlatformSubscribeAuthList(
+  id: number | string,
+  params?: object
+) {
+  return http.get(`/wechatPlatform/subscribe/auth_list/${id}`, params);
+}
+
+/**
+ * 关注回复-添加
+ */
+export function wechatPlatformSubscribeAdd(params: object) {
+  return http.post('/wechatPlatform/subscribe/add', params);
+}
+/**
+ * 关注回复-编辑保存
+ */
+export function wechatPlatformSubscribeEdit(
+  id: number | string,
+  params: object
+) {
+  return http.post(`/wechatPlatform/subscribe/edit/${id}`, params);
+}
+/**
+ * 关注回复-删除
+ */
+export function wechatPlatformSubscribeDel(params: object) {
+  return http.post(`/wechatPlatform/subscribe/del`, params);
+}
+/**
+ * 关注回复-分配公众号
+ */
+export function wechatPlatformSubscribeAllocation(
+  id: number | string,
+  params: object
+) {
+  return http.post(`/wechatPlatform/subscribe/allocation/${id}`, params);
+}
+
+/**
+ * 关注回复-全局设置获取
+ */
+export function wechatPlatformSubscribeGetConfig(
+  miniprogramId: number | string,
+  params?: object
+) {
+  return http.get(
+    `/wechatPlatform/subscribe/getConfig/${miniprogramId}`,
+    params
+  );
+}
+
+/**
+ * 关注回复-全局设置更新
+ */
+export function wechatPlatformSubscribeSetConfig(
+  miniprogramId: number | string,
+  params: object
+) {
+  return http.post(
+    `/wechatPlatform/subscribe/setConfig/${miniprogramId}`,
+    params
+  );
+}

+ 9 - 9
src/api/officialAccount/keywordReply/index.ts

@@ -1,12 +1,12 @@
 import http from '@/api/http';
 /**
- * 关键-列表
+ * 关键-列表
  */
 export function wechatPlatformKeywordList(params: object) {
   return http.get(`/wechatPlatform/keyword/list`, params);
 }
 /**
- * 关键-详情
+ * 关键-详情
  */
 export function wechatPlatformKeywordDetail(
   id: number | string,
@@ -15,7 +15,7 @@ export function wechatPlatformKeywordDetail(
   return http.get(`/wechatPlatform/keyword/detail/${id}`, params);
 }
 /**
- * 关键-配置公众号列表
+ * 关键-配置公众号列表
  */
 export function wechatPlatformKeywordAuthList(
   id: number | string,
@@ -31,25 +31,25 @@ export function wechatPlatformCommonGetPlayUrl(params: object) {
   return http.post('/wechatPlatform/common/get_play_url', params);
 }
 /**
- * 关键-添加
+ * 关键-添加
  */
 export function wechatPlatformKeywordAdd(params: object) {
   return http.post('/wechatPlatform/keyword/add', params);
 }
 /**
- * 关键-编辑保存
+ * 关键-编辑保存
  */
 export function wechatPlatformKeywordEdit(id: number | string, params: object) {
   return http.post(`/wechatPlatform/keyword/edit/${id}`, params);
 }
 /**
- * 关键-删除
+ * 关键-删除
  */
 export function wechatPlatformKeywordDel(params: object) {
   return http.post(`/wechatPlatform/keyword/del`, params);
 }
 /**
- * 关键-分配公众号
+ * 关键-分配公众号
  */
 export function wechatPlatformKeywordAllocation(
   id: number | string,
@@ -71,12 +71,12 @@ export function wechatPlatformKeywordGetConfig(
 /**
  * 关键字-全局设置更新
  */
-export function wechatPlatformKeywordGetConfigSet(
+export function wechatPlatformKeywordSetConfig(
   miniprogramId: number | string,
   params: object
 ) {
   return http.post(
-    `/wechatPlatform/keyword/getConfig/${miniprogramId}`,
+    `/wechatPlatform/keyword/setConfig/${miniprogramId}`,
     params
   );
 }

+ 46 - 0
src/api/officialAccount/newsService/index.ts

@@ -0,0 +1,46 @@
+import http from '@/api/http';
+/**
+ * 客服消息-列表
+ */
+export function wechatPlatformKfMessageList(params: object) {
+  return http.get(`/wechatPlatform/kfMessage/list`, params);
+}
+/**
+ * 客服消息-配置公众号
+ */
+export function wechatPlatformKfMessageUpdateGZH(params: object) {
+  return http.post(`/wechatPlatform/kfMessage/updateGZH`, params);
+}
+
+/**
+ * 客服消息-添加
+ */
+export function wechatPlatformKfMessageAdd(params: object) {
+  return http.post('/wechatPlatform/kfMessage/add', params);
+}
+/**
+ * 客服消息-更新内容
+ */
+export function wechatPlatformKfMessageUpdateContent(params: object) {
+  return http.post(`/wechatPlatform/kfMessage/updateContent`, params);
+}
+/**
+ * 客服消息-停止
+ */
+export function wechatPlatformKfMessageStop(params: object) {
+  return http.post(`/wechatPlatform/kfMessage/stop`, params);
+}
+/**
+ * 客服消息-删除
+ */
+export function wechatPlatformKfMessageDelete(params: object) {
+  return http.post(`/wechatPlatform/kfMessage/delete`, params);
+}
+
+/**
+ * 客服消息-测试发送
+ */
+export function wechatPlatformKfMessageTestSend(params: object) {
+  return http.post(`/wechatPlatform/kfMessage/testSend`, params);
+}
+

+ 50 - 0
src/api/officialAccount/publicCustomMenu/index.ts

@@ -0,0 +1,50 @@
+import http from '@/api/http';
+/**
+ * 公众号菜单-列表
+ */
+export function wechatPlatformMenuList(params: object) {
+  return http.get(`/wechatPlatform/menu/list`, params);
+}
+/**
+ * 公众号菜单-详情
+ */
+export function wechatPlatformMenuDetail(id: number | string, params?: object) {
+  return http.get(`/wechatPlatform/menu/detail/${id}`, params);
+}
+/**
+ * 关键字-配置公众号列表
+ */
+export function wechatPlatformMenuAuthList(
+  id: number | string,
+  params?: object
+) {
+  return http.get(`/wechatPlatform/menu/auth_list/${id}`, params);
+}
+
+/**
+ * 公众号菜单-添加
+ */
+export function wechatPlatformMenuAdd(params: object) {
+  return http.post('/wechatPlatform/menu/add', params);
+}
+/**
+ * 公众号菜单-编辑
+ */
+export function wechatPlatformMenuEdit(id: number | string, params: object) {
+  return http.post(`/wechatPlatform/menu/edit/${id}`, params);
+}
+/**
+ * 公众号菜单-删除
+ */
+export function wechatPlatformMenuDel(params: object) {
+  return http.post(`/wechatPlatform/menu/del`, params);
+}
+/**
+ * 公众号菜单-分配公众号
+ */
+export function wechatPlatformMenuAllocation(
+  id: number | string,
+  params: object
+) {
+  return http.post(`/wechatPlatform/menu/allocation/${id}`, params);
+}

+ 7 - 1
src/router/modules/charge.ts

@@ -41,9 +41,15 @@
 //         component: () => import('@/views/officialAccount/newsService/index.vue')
 //       },
 //       {
+//         path: 'index6',
+//         name: 'user-account66',
+//         meta: { title: '用户分群', icon: 'home' },
+//         component: () => import('@/views/customer/segment/index.vue')
+//       },
+//       {
 //         path: 'index2',
 //         name: 'user-account123',
-//         meta: { title: '菜单', icon: 'home', hidden: true },
+//         meta: { title: '菜单', icon: 'home', hidden: false },
 //         component: () =>
 //           import('@/views/officialAccount/publicCustomMenu/index.vue')
 //       }

+ 543 - 0
src/views/customer/segment/form/create.vue

@@ -0,0 +1,543 @@
+<template>
+  <el-form :model="formCallback" label-position="right" 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="配置公众号" prop="gzh_ids" :rules="[{ required: true, message: '公众号必须选择' }]" label-width="120px">
+      <configPublicPlus :isShowButton="false" :required="true" @change="configPublicChange"></configPublicPlus>
+    </el-form-item>
+    <el-form-item label="选择标签" prop="name" :rules="[{ required: false, message: '' }]" label-width="90px">
+      <div>
+        <el-tabs type="card">
+          <el-tab-pane label="活跃信息">
+            <el-form-item label="仅48小时内和公众号有互动的粉丝才能收到" label-width="300px"
+              :rules="[{ required: false, message: '金额项必须填写' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.in_48_hour">
+                  <el-radio :label="1">是</el-radio>
+                  <el-radio :label="2">否</el-radio>
+                </el-radio-group>
+              </div>
+            </el-form-item>
+            <el-form-item label="关注时间" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.attention_hour">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio label="0-6">6h以内</el-radio>
+                  <el-radio label="24-28">24-28h</el-radio>
+                  <el-radio label="0-48">48h以内</el-radio>
+                  <el-radio label="custom">自定义</el-radio>
+                </el-radio-group>
+                <el-tooltip placement="top">
+                  <template #content>
+                    <span>
+                      可自主选择用户上次互动时间范围,只填写左侧输入框即为n小时以上(包含n小时),只填写右侧输入框即为n小时以内(包含n小时)</span> <br />
+                  </template>
+                  <el-icon>
+                    <InfoFilled />
+                  </el-icon>
+                </el-tooltip>
+              </div>
+              <div v-if="formCallback.tags.attention_hour == 'custom'" class="flex">
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="0"
+                    v-model.number="formCallback._tags.attention_hour.min" type="number">
+                    <template #append>h</template>
+                  </el-input>
+                </div>
+                <span>—</span>
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="formCallback?._tags?.attention_hour?.min ?? 0"
+                    v-model.number="formCallback._tags.attention_hour.max" :max="720" type="number">
+                    <template #append>h</template>
+                  </el-input>
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item label="上次互动时间" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.interact_hour">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio label="0-1">1h以内</el-radio>
+                  <el-radio label="1-6">1-6h</el-radio>
+                  <el-radio label="6-12">6-12h</el-radio>
+                  <el-radio label="12-36">12-36h</el-radio>
+                  <el-radio label="36-48">36-48h</el-radio>
+                  <el-radio label="custom">自定义</el-radio>
+                </el-radio-group>
+                <el-tooltip placement="top">
+                  <template #content>
+                    <span>
+                      可自主选择用户上次互动时间范围,只填写左侧输入框即为n小时以上(包含n小时),只填写右侧输入框即为n小时以内(包含n小时)</span> <br />
+                  </template>
+                  <el-icon>
+                    <InfoFilled />
+                  </el-icon>
+                </el-tooltip>
+              </div>
+              <div v-if="formCallback.tags.interact_hour == 'custom'" class="flex">
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="0"
+                    v-model.number="formCallback._tags.interact_hour.min" type="number">
+                    <template #append>h</template>
+                  </el-input>
+                </div>
+                <span>—</span>
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="formCallback?._tags?.interact_hour?.min ?? 0"
+                    v-model.number="formCallback._tags.interact_hour.max" :max="720" type="number">
+                    <template #append>h</template>
+                  </el-input>
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item label="最近观看日期" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.last_watch_day">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio label="0-2">近两天内</el-radio>
+                  <el-radio label="2-5">2-5天内</el-radio>
+                  <el-radio label="5-7">2-5天内</el-radio>
+                  <el-radio label="7-15">7-15天内</el-radio>
+                  <el-radio label="15-30">15-30天内</el-radio>
+                  <el-radio label="custom">自定义</el-radio>
+                </el-radio-group>
+                <el-tooltip placement="top">
+                  <template #content>
+                    <span>
+                      可自主选择用户最近阅读日期范围,只填写左侧输入框即为距离今天n天前(包含n天),只填写右侧输入框即为距离今天n天以内(包含n天)</span> <br />
+                  </template>
+                  <el-icon>
+                    <InfoFilled />
+                  </el-icon>
+                </el-tooltip>
+              </div>
+              <div v-if="formCallback.tags.last_watch_day == 'custom'" class="flex">
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="0"
+                    v-model.number="formCallback._tags.last_watch_day.min" type="number">
+                    <template #append>天</template>
+                  </el-input>
+                </div>
+                <span>—</span>
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="formCallback?._tags?.last_watch_day?.min ?? 0"
+                    v-model.number="formCallback._tags.last_watch_day.max" :max="30" type="number">
+                    <template #append>天</template>
+                  </el-input>
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item label="用户注册日期" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.register_day">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio label="0-2">近两天内</el-radio>
+                  <el-radio label="2-5">2-5天内</el-radio>
+                  <el-radio label="5-7">2-5天内</el-radio>
+                  <el-radio label="7-15">7-15天内</el-radio>
+                  <el-radio label="15-30">15-30天内</el-radio>
+                  <el-radio label="custom">自定义</el-radio>
+                </el-radio-group>
+                <el-tooltip placement="top">
+                  <template #content>
+                    <span>可自主选择用户最近阅读日期范围,只填写左侧输入框即为距离今天n天前(包含n天),只填写右侧输入框即为距离今天n天以内(包含n天)</span> <br />
+                  </template>
+                  <el-icon>
+                    <InfoFilled />
+                  </el-icon>
+                </el-tooltip>
+              </div>
+              <div v-if="formCallback.tags.register_day == 'custom'" class="flex">
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="0"
+                    v-model.number="formCallback._tags.register_day.min" type="number">
+                    <template #append>天</template>
+                  </el-input>
+                </div>
+                <span>—</span>
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="formCallback?._tags?.register_day?.min ?? 0"
+                    v-model.number="formCallback._tags.register_day.max" :max="30" type="number">
+                    <template #append>天</template>
+                  </el-input>
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item label="累计观看天数" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.total_watch_day">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio label="0-2">近两天内</el-radio>
+                  <el-radio label="2-5">2-5天内</el-radio>
+                  <el-radio label="5-7">2-5天内</el-radio>
+                  <el-radio label="7-15">7-15天内</el-radio>
+                  <el-radio label="15-30">15-30天内</el-radio>
+                  <el-radio label="custom">自定义</el-radio>
+                </el-radio-group>
+                <el-tooltip placement="top">
+                  <template #content>
+                    <span>可自主选择用户累计观看天数范围,只填写左侧输入框即为n天以上(包含n天),只填写右侧输入框即为n天以内(包含n天),自定义最大范围在30天以内</span> <br />
+                  </template>
+                  <el-icon>
+                    <InfoFilled />
+                  </el-icon>
+                </el-tooltip>
+              </div>
+              <div v-if="formCallback.tags.total_watch_day == 'custom'" class="flex">
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="0"
+                    v-model.number="formCallback._tags.total_watch_day.min" type="number">
+                    <template #append>天</template>
+                  </el-input>
+                </div>
+                <span>—</span>
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="formCallback?._tags?.total_watch_day?.min ?? 0"
+                    v-model.number="formCallback._tags.total_watch_day.max" :max="30" type="number">
+                    <template #append>天</template>
+                  </el-input>
+                </div>
+              </div>
+            </el-form-item>
+          </el-tab-pane>
+          <el-tab-pane label="付费信息">
+            <el-form-item label="付费情况" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.charge_type">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio :label="1">未付费</el-radio>
+                  <el-radio :label="2">待支付</el-radio>
+                  <el-radio :label="3">已付费</el-radio>
+                  <el-radio :label="4">vip用户</el-radio>
+                </el-radio-group>
+              </div>
+            </el-form-item>
+            <el-form-item label="累计充值金额" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.total_charge_money">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio label="custom">自定义</el-radio>
+                </el-radio-group>
+                <el-tooltip placement="top">
+                  <template #content>
+                    <span>可自主选择用户累计充值金额范围,只填写左侧输入框即为n元以上(包含n元),只填写右侧输入框即为n元以内(包含n元),左右输入框一致即为n元</span> <br />
+                  </template>
+                  <el-icon>
+                    <InfoFilled />
+                  </el-icon>
+                </el-tooltip>
+              </div>
+              <div v-if="formCallback.tags.total_charge_money == 'custom'" class="flex">
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="0"
+                    v-model.number="formCallback._tags.total_charge_money.min" type="number">
+                    <template #append>元</template>
+                  </el-input>
+                </div>
+                <span>—</span>
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="formCallback?._tags?.total_charge_money?.min ?? 0"
+                    v-model.number="formCallback._tags.total_charge_money.max" type="number">
+                    <template #append>元</template>
+                  </el-input>
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item label="平均充值金额" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.avg_charge_money">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio label="custom">自定义</el-radio>
+                </el-radio-group>
+                <el-tooltip placement="top">
+                  <template #content>
+                    <span>可自主选择用户累计充值金额范围,只填写左侧输入框即为n元以上(包含n元),只填写右侧输入框即为n元以内(包含n元),左右输入框一致即为n元</span> <br />
+                  </template>
+                  <el-icon>
+                    <InfoFilled />
+                  </el-icon>
+                </el-tooltip>
+              </div>
+              <div v-if="formCallback.tags.avg_charge_money == 'custom'" class="flex">
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="0"
+                    v-model.number="formCallback._tags.avg_charge_money.min" type="number">
+                    <template #append>元</template>
+                  </el-input>
+                </div>
+                <span>—</span>
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="formCallback?._tags?.avg_charge_money?.min ?? 0"
+                    v-model.number="formCallback._tags.avg_charge_money.max" type="number">
+                    <template #append>元</template>
+                  </el-input>
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item label="充值次数" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.charge_num">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio label="0-0">0次</el-radio>
+                  <el-radio label="1-1">1次</el-radio>
+                  <el-radio label="3-3">3次</el-radio>
+                  <el-radio label="5-5">5次</el-radio>
+                  <el-radio label="10-0">10次以上</el-radio>
+                  <el-radio label="custom">自定义</el-radio>
+                </el-radio-group>
+                <el-tooltip placement="top">
+                  <template #content>
+                    <span>可自主选择用户充值次数范围,只填写左侧输入框即为n次以上(包含n次),只填写右侧输入框即为n次(包含n次)以内,左右输入框一致即为n次</span> <br />
+                  </template>
+                  <el-icon>
+                    <InfoFilled />
+                  </el-icon>
+                </el-tooltip>
+              </div>
+              <div v-if="formCallback.tags.charge_num == 'custom'" class="flex">
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="0" v-model.number="formCallback._tags.charge_num.min"
+                    type="number">
+                    <template #append>次</template>
+                  </el-input>
+                </div>
+                <span>—</span>
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="formCallback?._tags?.charge_num?.min ?? 0"
+                    v-model.number="formCallback._tags.charge_num.max" type="number">
+                    <template #append>次</template>
+                  </el-input>
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item label="看币余额" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.remain_coin">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio label="custom">自定义</el-radio>
+                </el-radio-group>
+                <el-tooltip placement="top">
+                  <template #content>
+                    <span>可自主选择用户书币余额范围,只填写左侧输入框即为n书币以上(包含n书币),只填写右侧输入框即为n书币以内(包含n书币),左右输入框一致即为n书币</span> <br />
+                  </template>
+                  <el-icon>
+                    <InfoFilled />
+                  </el-icon>
+                </el-tooltip>
+              </div>
+              <div v-if="formCallback.tags.remain_coin == 'custom'" class="flex">
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="0" v-model.number="formCallback._tags.remain_coin.min"
+                    type="number">
+                    <template #append>元</template>
+                  </el-input>
+                </div>
+                <span>—</span>
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="formCallback?._tags?.remain_coin?.min ?? 0"
+                    v-model.number="formCallback._tags.remain_coin.max" type="number">
+                    <template #append>元</template>
+                  </el-input>
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item label="上次充值日期" label-width="120px" :rules="[{ required: false, message: '' }]">
+              <div class="flex items-center mb-3 mr-3">
+                <el-radio-group v-model="formCallback.tags.last_charge_day">
+                  <el-radio label="">不限</el-radio>
+                  <el-radio label="0-2">近两天内</el-radio>
+                  <el-radio label="2-5">2-5天内</el-radio>
+                  <el-radio label="5-7">2-5天内</el-radio>
+                  <el-radio label="7-15">7-15天内</el-radio>
+                  <el-radio label="15-30">15-30天内</el-radio>
+                  <el-radio label="custom">自定义</el-radio>
+                </el-radio-group>
+                <el-tooltip placement="top">
+                  <template #content>
+                    <span>可自主选择用户上次充值日期范围,只填写左侧输入框即为距离今天n天前(包含n天),只填写右侧输入框即为距离今天n天以内(包含n天)</span> <br />
+                  </template>
+                  <el-icon>
+                    <InfoFilled />
+                  </el-icon>
+                </el-tooltip>
+              </div>
+              <div v-if="formCallback.tags.last_charge_day == 'custom'" class="flex">
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="0"
+                    v-model.number="formCallback._tags.last_charge_day.min" type="number">
+                    <template #append>h</template>
+                  </el-input>
+                </div>
+                <span>—</span>
+                <div class="flex flex-col">
+                  <el-input style="width:150px;margin:0 5px;" :min="formCallback?._tags?.last_charge_day?.min ?? 0"
+                    v-model.number="formCallback._tags.last_charge_day.max" :max="30" type="number">
+                    <template #append>h</template>
+                  </el-input>
+                </div>
+              </div>
+            </el-form-item>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </el-form-item>
+    <el-form-item label="历史观看的短剧" prop="video_watch" label-width="130px"
+      :rules="[{ required: false, message: '请选择历史观看的短剧' }]">
+      <el-select v-model="formCallback.video_watch" class="w-full" multiple 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_charge" label-width="130px"
+      :rules="[{ required: false, message: '请选择充值过的短剧' }]">
+      <el-select v-model="formCallback.video_charge" multiple 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="remark" :rules="[{ required: false, message: '备注必须填写' }]" label-width="130px">
+      <el-input v-model="formCallback.remark" auto-complete="off" type="textarea" placeholder="请输入备注"></el-input>
+    </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 { Delete, InfoFilled } from '@element-plus/icons-vue';
+import { validation } from './validation'
+import configPublicPlus from '@/views/officialAccount/components/configPublicPlus.vue'
+import { videoStockVideoList } from '@/api/video/index'
+import { FormInstance } from 'element-plus';
+import { useRouter, useRoute } from 'vue-router'
+import { audienceManageUserGroupAdd } from '@/api/customer/segment/index'
+const router = useRouter()
+const route = useRoute()
+const props = defineProps({
+  primary: Object | null,
+});
+const videoList = ref([])
+const emit = defineEmits(['close']);
+const loading = ref(false)
+const form = ref<FormInstance>()
+const promotion = ref<string | null | number>('')
+const formCallback = ref({
+  name: '',
+  remark: '',
+  gzh_ids: [],
+  video_watch: [],
+  video_charge: [],
+  tags: {
+    in_48_hour: 1,
+    charge_type: '',
+    attention_hour: '',
+    interact_hour: '',
+    last_watch_day: '',
+    register_day: '',
+    total_watch_day: '',
+    total_charge_money: '',
+    avg_charge_money: '',
+    charge_num: '',
+    remain_coin: '',
+    last_charge_day: '',
+  },
+  _tags: {
+    attention_hour: {},
+    interact_hour: {},
+    last_watch_day: {},
+    register_day: {},
+    total_watch_day: {},
+    total_charge_money: {},
+    avg_charge_money: {},
+    charge_num: {},
+    remain_coin: {},
+    last_charge_day: {},
+  }
+})
+const configPublicChange = (e: any) => {
+  console.log(e, 'configPublicChangeconfigPublicChange');
+  formCallback.value.gzh_ids = e
+}
+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) {
+        if (!validation(formCallback.value)) return loading.value = false;
+        console.log(formCallback.value, 'formCallback.value');
+        let params = {
+          name: formCallback.value.name,
+          remark: formCallback.value.remark,
+          gzh_ids: formCallback.value.gzh_ids,
+          tags: {
+            attention_hour: formCallback.value?.tags?.attention_hour,
+            in_48_hour: formCallback.value?.tags?.in_48_hour,
+            interact_hour: formCallback.value?.tags?.interact_hour,
+            last_watch_day: formCallback.value?.tags?.last_watch_day,
+            register_day: formCallback.value?.tags?.register_day,
+            total_watch_day: formCallback.value?.tags?.total_watch_day,
+            charge_type: formCallback.value?.tags?.charge_type,
+            total_charge_money: formCallback.value?.tags?.total_charge_money,
+            avg_charge_money: formCallback.value?.tags?.avg_charge_money,
+            charge_num: formCallback.value?.tags?.charge_num,
+            remain_coin: formCallback.value?.tags?.remain_coin,
+            last_charge_day: formCallback.value?.tags?.last_charge_day,
+            video_watch: formCallback.value.video_watch,
+            video_charge: formCallback.value.video_charge,
+          }
+        }
+        audienceManageUserGroupAdd(params).then(res => {
+          console.log(res, 'audienceManageUserGroupAdd');
+          ElMessage.success(res.message)
+          loading.value = false;
+          emit('close')
+        }).catch(e => {
+          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>
+:deep(.small-title) {
+  font-size: 10px;
+  color: grey;
+}
+
+:deep(.el-radio-group) {
+  flex-wrap: nowrap;
+}
+</style>

+ 47 - 0
src/views/customer/segment/form/detail.vue

@@ -0,0 +1,47 @@
+<template>
+  <el-descriptions title="" direction="horizontal" border size="default" :column="1">
+    <el-descriptions-item label="人群包名称" min-width="120">{{ detail?.name }}</el-descriptions-item>
+    <el-descriptions-item label="预估人数" min-width="120">{{ detail?.user_num }}</el-descriptions-item>
+    <el-descriptions-item label="已配置公众号" min-width="120">
+      <el-tag class="m-1" type="success" v-for="(item, index) in detail?.gzh_names" :key="index">
+        {{ item }}
+      </el-tag>
+    </el-descriptions-item>
+    <el-descriptions-item label="内容详情" min-width="120">
+      <el-descriptions title="" direction="vertical" border size="default" :column="2">
+        <el-descriptions-item label="活跃信息" min-width="120">
+          <div v-for="(item, index) in detail?.tags_arr?.active" :key="index">{{ item }}</div>
+        </el-descriptions-item>
+        <el-descriptions-item label="付费信息" min-width="120">
+          <div v-for="(item, index) in detail?.tags_arr?.pay" :key="index">{{ item }}</div>
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-descriptions-item>
+  </el-descriptions>
+</template>
+
+<script setup lang="ts">
+import { audienceManageUserGroupDetail } from '@/api/customer/segment/index'
+const props = defineProps({
+  primary: Object | null,
+});
+const detail = ref({})
+
+if (props.primary) {
+  console.log(props.primary, 'props.primary');
+  audienceManageUserGroupDetail({ id: props.primary.id }).then(res => {
+    console.log(res);
+    detail.value = res.data;
+  })
+}
+
+
+onMounted(() => {
+});
+</script>
+
+<style scoped>
+.el-descriptions {
+  margin-top: 20px;
+}
+</style>

+ 213 - 0
src/views/customer/segment/form/validation.ts

@@ -0,0 +1,213 @@
+export function validation(validationData: object) {
+  var validate = true;
+  if (
+    validationData?.tags?.attention_hour == 'custom' &&
+    (validationData?._tags?.attention_hour?.min == undefined ||
+      validationData?._tags?.attention_hour?.max == undefined)
+  ) {
+    validate = false;
+    ElMessage.error('请输入自定义关注时间');
+  } else if (
+    validationData?._tags?.attention_hour?.min >
+    validationData?._tags?.attention_hour?.max
+  ) {
+    validate = false;
+    ElMessage.error('自定义关注时间区间范围错误');
+  } else if (validationData?._tags?.attention_hour?.max > 720) {
+    validate = false;
+    ElMessage.error('自定义关注时间不能超过720h(30天)');
+  }
+
+  if (
+    validationData?.tags?.interact_hour == 'custom' &&
+    (validationData?._tags?.interact_hour?.min == undefined ||
+      validationData?._tags?.interact_hour?.max == undefined)
+  ) {
+    validate = false;
+    ElMessage.error('请输入自定义上次互动时间');
+  } else if (
+    validationData?._tags?.interact_hour?.min >
+    validationData?._tags?.interact_hour?.max
+  ) {
+    validate = false;
+    ElMessage.error('自定义上次互动时间区间范围错误');
+  } else if (validationData?._tags?.interact_hour?.max > 720) {
+    validate = false;
+    ElMessage.error('自定义上次互动时间不能超过720h(30天)');
+  }
+
+  if (
+    validationData?.tags?.last_watch_day == 'custom' &&
+    (validationData?._tags?.last_watch_day?.min == undefined ||
+      validationData?._tags?.last_watch_day?.max == undefined)
+  ) {
+    validate = false;
+    ElMessage.error('请输入自定义最近观看日期');
+  } else if (
+    validationData?._tags?.last_watch_day?.min >
+    validationData?._tags?.last_watch_day?.max
+  ) {
+    validate = false;
+    ElMessage.error('自定义最近观看日期区间范围错误');
+  } else if (validationData?._tags?.last_watch_day?.max > 30) {
+    validate = false;
+    ElMessage.error('自定义最近观看日期不能超过30天');
+  }
+
+  if (
+    validationData?.tags?.register_day == 'custom' &&
+    (validationData?._tags?.register_day?.min == undefined ||
+      validationData?._tags?.register_day?.max == undefined)
+  ) {
+    validate = false;
+    ElMessage.error('请输入自定义用户注册日期');
+  } else if (
+    validationData?._tags?.register_day?.min >
+    validationData?._tags?.register_day?.max
+  ) {
+    validate = false;
+    ElMessage.error('自定义用户注册日期区间范围错误');
+  } else if (validationData?._tags?.register_day?.max > 30) {
+    validate = false;
+    ElMessage.error('自定义用户注册日期不能超过30天');
+  }
+
+  if (
+    validationData?.tags?.total_watch_day == 'custom' &&
+    (validationData?._tags?.total_watch_day?.min == undefined ||
+      validationData?._tags?.total_watch_day?.max == undefined)
+  ) {
+    validate = false;
+    ElMessage.error('请输入自定义累计观看天数');
+  } else if (
+    validationData?._tags?.total_watch_day?.min >
+    validationData?._tags?.total_watch_day?.max
+  ) {
+    validate = false;
+    ElMessage.error('自定义累计观看天数区间范围错误');
+  } else if (validationData?._tags?.total_watch_day?.max > 30) {
+    validate = false;
+    ElMessage.error('自定义累计观看天数不能超过30天');
+  }
+
+  if (
+    validationData?.tags?.total_charge_money == 'custom' &&
+    (validationData?._tags?.total_charge_money?.min == undefined ||
+      validationData?._tags?.total_charge_money?.max == undefined)
+  ) {
+    validate = false;
+    ElMessage.error('请输入自定义累计充值金额');
+  } else if (
+    validationData?._tags?.total_charge_money?.min >
+    validationData?._tags?.total_charge_money?.max
+  ) {
+    validate = false;
+    ElMessage.error('自定义累计充值金额区间范围错误');
+  }
+
+  if (
+    validationData?.tags?.avg_charge_money == 'custom' &&
+    (validationData?._tags?.avg_charge_money?.min == undefined ||
+      validationData?._tags?.avg_charge_money?.max == undefined)
+  ) {
+    validate = false;
+    ElMessage.error('请输入自定义平均充值金额');
+  } else if (
+    validationData?._tags?.avg_charge_money?.min >
+    validationData?._tags?.avg_charge_money?.max
+  ) {
+    validate = false;
+    ElMessage.error('自定义平均充值金额区间范围错误');
+  }
+
+  if (
+    validationData?.tags?.charge_num == 'custom' &&
+    (validationData?._tags?.charge_num?.min == undefined ||
+      validationData?._tags?.charge_num?.max == undefined)
+  ) {
+    validate = false;
+    ElMessage.error('请输入自定义充值次数');
+  } else if (
+    validationData?._tags?.charge_num?.min >
+    validationData?._tags?.charge_num?.max
+  ) {
+    validate = false;
+    ElMessage.error('自定义充值次数区间范围错误');
+  }
+
+  if (
+    validationData?.tags?.remain_coin == 'custom' &&
+    (validationData?._tags?.remain_coin?.min == undefined ||
+      validationData?._tags?.remain_coin?.max == undefined)
+  ) {
+    validate = false;
+    ElMessage.error('请输入自定义看币余额');
+  } else if (
+    validationData?._tags?.remain_coin?.min >
+    validationData?._tags?.remain_coin?.max
+  ) {
+    validate = false;
+    ElMessage.error('自定义看币余额区间范围错误');
+  }
+
+  if (
+    validationData?.tags?.last_charge_day == 'custom' &&
+    (validationData?._tags?.last_charge_day?.min == undefined ||
+      validationData?._tags?.last_charge_day?.max == undefined)
+  ) {
+    validate = false;
+    ElMessage.error('请输入自定义上次充值日期');
+  } else if (
+    validationData?._tags?.last_charge_day?.min >
+    validationData?._tags?.last_charge_day?.max
+  ) {
+    validate = false;
+    ElMessage.error('自定义上次充值日期区间范围错误');
+  } else if (validationData?._tags?.last_charge_day?.max > 30) {
+    validate = false;
+    ElMessage.error('自定义上次充值日期不能超过30天');
+  }
+
+  if (validate) {
+    if (validationData?.tags?.attention_hour == 'custom') {
+      validationData.tags.attention_hour = `${validationData?._tags?.attention_hour?.min}-${validationData?._tags?.attention_hour?.max}`;
+    }
+
+    if (validationData?.tags?.interact_hour == 'custom') {
+      validationData.tags.interact_hour = `${validationData?._tags?.interact_hour?.min}-${validationData?._tags?.interact_hour?.max}`;
+    }
+
+    if (validationData?.tags?.last_watch_day == 'custom') {
+      validationData.tags.last_watch_day = `${validationData?._tags?.last_watch_day?.min}-${validationData?._tags?.last_watch_day?.max}`;
+    }
+
+    if (validationData?.tags?.register_day == 'custom') {
+      validationData.tags.register_day = `${validationData?._tags?.register_day?.min}-${validationData?._tags?.register_day?.max}`;
+    }
+
+    if (validationData?.tags?.total_watch_day == 'custom') {
+      validationData.tags.total_watch_day = `${validationData?._tags?.total_watch_day?.min}-${validationData?._tags?.total_watch_day?.max}`;
+    }
+
+    if (validationData?.tags?.total_charge_money == 'custom') {
+      validationData.tags.total_charge_money = `${validationData?._tags?.total_charge_money?.min}-${validationData?._tags?.total_charge_money?.max}`;
+    }
+
+    if (validationData?.tags?.avg_charge_money == 'custom') {
+      validationData.tags.avg_charge_money = `${validationData?._tags?.avg_charge_money?.min}-${validationData?._tags?.avg_charge_money?.max}`;
+    }
+
+    if (validationData?.tags?.charge_num == 'custom') {
+      validationData.tags.charge_num = `${validationData?._tags?.charge_num?.min}-${validationData?._tags?.charge_num?.max}`;
+    }
+
+    if (validationData?.tags?.remain_coin == 'custom') {
+      validationData.tags.remain_coin = `${validationData?._tags?.remain_coin?.min}-${validationData?._tags?.remain_coin?.max}`;
+    }
+
+    if (validationData?.tags?.last_charge_day == 'custom') {
+      validationData.tags.last_charge_day = `${validationData?._tags?.last_charge_day?.min}-${validationData?._tags?.last_charge_day?.max}`;
+    }
+  }
+  return validate;
+}

+ 167 - 0
src/views/customer/segment/index.vue

@@ -0,0 +1,167 @@
+<template>
+  <div class="flex flex-col justify-between w-full sm:flex-row">
+    <div class="w-full">
+      <Search :search="search" :reset="resetQuery">
+        <template v-slot:body>
+          <el-form-item label="人群包名称">
+            <el-input placeholder="请输入人群包名称" v-model="query.name" clearable></el-input>
+          </el-form-item>
+        </template>
+        <template v-slot:content>
+          <div class="table-default">
+            <div class="pt-5 pl-2">
+              <el-button type="primary" size="default" v-action="'audience.UserGroup.add'"
+                @click="openType('createVisible', null)">新增</el-button>
+              <el-button size="default" @click="mulSet" v-action="'audience.UserGroup.delete'">批量删除</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="created_at" label="创建时间" min-width="200"></el-table-column>
+              <el-table-column prop="remark" 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" v-action="'audience.UserGroup.detail'"
+                    @click="openType('configPublicVisible', scope.row)">查看</el-button>
+                  <br />
+                  <el-button link type="primary" size="small" v-action="'audience.UserGroup.delete'"
+                    @click="deleteChange(scope.row)">删除</el-button>
+                  <br />
+                </template>
+              </el-table-column>
+            </el-table>
+            <Paginate />
+          </div>
+        </template>
+      </Search>
+      <Dialog v-model="createVisible" :title="createTitle" width="1000px" destroy-on-close>
+        <Create @close="closeType('createVisible')" :primary="current" />
+      </Dialog>
+      <Dialog v-model="configPublicVisible" width="900px" title="人群包详情" destroy-on-close>
+        <detail :primary="current"></detail>
+      </Dialog>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { InfoFilled } from '@element-plus/icons-vue';
+import Create from './form/create.vue';
+import detail from './form/detail.vue'
+import { useGetList } from '@/hook/curd/useGetList';
+import {
+  wechatPlatformKeywordGetConfig,
+  wechatPlatformKeywordDel
+} from '@/api/officialAccount/keywordReply/index'
+import { audienceManageUserGroupDelete } from '@/api/customer/segment/index'
+import { wechatPlatformOfficialAccountList } from '@/api/officialAccount/officialList/index'
+const api = 'audienceManage/userGroup/list';
+import Cache from '@/support/cache';
+const createVisible = ref(false)
+const configPublicVisible = ref(false)
+const current = ref<object | null>({})
+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 = '编辑'
+      } else {
+        createTitle.value = '新增'
+      }
+      break;
+    case 'configPublicVisible':
+      configPublicVisible.value = true
+      break;
+  }
+}
+const resetQuery = () => {
+  query.value = Object.assign({ page: query.value.page, limit: query.value.limit, miniprogram_id: query.value.miniprogram_id });
+  search()
+}
+// 批量删除
+const mulSet = () => {
+  if (multipleSelection.value.length <= 0) {
+    return ElMessage.warning('至少选择一条数据');
+  } else {
+    const content = '确定批量删除吗?'
+    ElMessageBox.confirm(
+      content,
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }
+    )
+      .then(() => {
+        const ids = multipleSelection.value.map(el => el.id)
+        audienceManageUserGroupDelete({ ids }).then(res => {
+          console.log(res);
+          ElMessage.success(res.message)
+          search()
+        })
+      })
+      .catch(() => {
+
+      })
+  }
+}
+
+const closeType = (type: string) => {
+  switch (type) {
+    case 'createVisible':
+      createVisible.value = false
+      break;
+    case 'configPublicVisible':
+      configPublicVisible.value = false
+      break;
+  }
+  search()
+}
+
+const deleteChange = (row: object) => {
+  ElMessageBox.confirm(
+    '确定要删除吗?',
+    '提示',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      audienceManageUserGroupDelete({ ids: [row.id] }).then(res => {
+        console.log(res);
+        ElMessage.success(res.message)
+        search()
+      })
+    })
+    .catch(() => {
+
+    })
+}
+
+onMounted(() => {
+  search();
+});
+</script>
+
+<style lang="scss" scoped>
+.withdraw-popup-warn {
+  font-size: 14px;
+  font-weight: 600;
+  color: #666;
+}
+</style>

+ 78 - 33
src/views/officialAccount/attentionReply/form/create.vue

@@ -1,13 +1,13 @@
 <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 v-model="formCallback.type" :disabled="props.primary?.look">
+        <el-radio label="txt">文本消息</el-radio>
       </el-radio-group>
     </el-form-item>
-    <el-form-item label="" label-width="120px">
+    <el-form-item label="" label-width="120px" prop="content" :rules="[{ required: true, message: '消息内容必须插入' }]">
       <el-card class="box-card" style="width:500px;">
-        <template #header>
+        <template #header v-if="!props.primary?.look">
           <div class="card-header">
             <el-popover placement="right" :visible="popoverVisible" trigger="click">
               <template #reference>
@@ -21,63 +21,73 @@
           </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 })">
+          <div v-for="(item, index) in formCallback.content" :key="index" class="flex items-center">
+            <span class="mr-5">
+              <el-icon @click="removeItem(index)" class="cursor-pointer">
+                <Delete />
+              </el-icon>
+            </span>
+            <el-button type="primary" link v-if="item.url" @click="linkClick({ index, ...item })">
               {{ item.title }}
             </el-button>
-            <el-input style="boder:none;" v-else v-model="item.title" clearable></el-input>
+            <el-input style="boder:none;" type="textarea" autosize :disabled="props.primary?.look" v-else
+              v-model="item.title" clearable></el-input>
           </div>
         </div>
       </el-card>
     </el-form-item>
-    <div class="flex justify-end">
+    <div class="flex justify-end" v-if="!props.primary?.look">
       <el-button type="primary" @click="submitForm(form)">{{
         $t('system.confirm')
       }}</el-button>
     </div>
   </el-form>
 
-  <Dialog v-model="insertVisible" :title="insertTitle" destroy-on-close>
+  <Dialog v-model="insertVisible" :title="insertTitle" destroy-on-close @update:modelValue="formCallbackinsert = {}">
     <el-form :model="formCallbackinsert" label-width="120px" ref="insertform" v-loading="loading" class="pr-4">
       <div v-if="insertType == 'link'">
         <div class="withdraw-popup-warn">
           <span>需先</span>
-          <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary" href="https://element-plus.org"
-            target="_blank">关联小程序</el-link>
+          <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary"
+            href="https://docs.qq.com/doc/DUWlNb0RsTXFQelFF" 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-input style="width:300px;" :disabled="props.primary?.look" type="textarea" autosize
+            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 label="链接" prop="url" :rules="[{ required: true, message: '链接必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" :disabled="props.primary?.look" v-model="formCallbackinsert.url"
+            auto-complete="off" placeholder="请输入链接"></el-input>
+          <el-button v-if="!props.primary?.look" type="primary" link :icon="Plus" @click="linkVisible = true"
+            class="mr-6">插入链接</el-button>
         </el-form-item>
       </div>
       <div v-else>
-        <el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
-          <el-input style="width:300px;" type="textarea" v-model="formCallbackinsert.title" auto-complete="off"
-            placeholder="请输入标题"></el-input>
+        <el-form-item label="内容" prop="title" :rules="[{ required: true, message: '内容必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" :disabled="props.primary?.look" type="textarea" autosize
+            v-model="formCallbackinsert.title" auto-complete="off" placeholder="请输入内容"></el-input>
         </el-form-item>
       </div>
-      <div class="flex justify-end">
+      <div class="flex justify-end" v-if="!props.primary?.look">
         <el-button type="primary" @click="insertSubmitChange(insertform)">确定</el-button>
       </div>
     </el-form>
   </Dialog>
 
   <Dialog v-model="linkVisible" title="选择链接" destroy-on-close>
-    <generateLink @close="linkClose" />
+    <generateLink @success="linkSuccess" />
   </Dialog>
 </template>
 
 <script lang="ts" setup>
-import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { Plus, InfoFilled, Delete } from '@element-plus/icons-vue';
 import { FormInstance } from 'element-plus';
 import { useRouter, useRoute } from 'vue-router'
+import { wechatPlatformSubscribeAdd, wechatPlatformSubscribeEdit, wechatPlatformSubscribeDetail } from '@/api/officialAccount/attentionReply/index'
 import generateLink from './generateLink.vue'
+import { checkPermission } from '@/directives/permission';
+import Cache from '@/support/cache';
 const router = useRouter()
 const route = useRoute()
 const insertform = ref()
@@ -90,11 +100,9 @@ const loading = ref(false)
 const linkVisible = ref(false)
 const popoverVisible = ref(false)
 const insertVisible = ref(false)
-
-const formCallback = ref({ type: 1 })
+const formCallback = ref({ type: 'txt', content: [] })
 const formCallbackinsert = ref({})
 const insertType = ref()
-const insertArr = ref([])
 const insertTitle = computed(() => insertType.value == 'link' ? '插入小程序链接' : '插入纯文本')
 const insertChange = (type: string) => {
   insertType.value = type;
@@ -106,6 +114,10 @@ const linkClick = (row: object) => {
   insertVisible.value = true
   insertType.value = 'link'
 }
+const removeItem = (idx: number) => {
+  formCallback.value.content.splice(idx, 1);
+}
+
 const insertSubmitChange = (formEl: FormInstance | undefined) => {
   if (!formEl) return;
   if (!formEl) return;
@@ -113,21 +125,22 @@ const insertSubmitChange = (formEl: FormInstance | undefined) => {
     .validate(valid => {
       if (valid) {
         if (formCallbackinsert.value.index != undefined) {
-          insertArr.value[formCallbackinsert.value.index] = formCallbackinsert.value
+          formCallback.value.content[formCallbackinsert.value?.index] = formCallbackinsert.value
         } else {
-          insertArr.value.push(formCallbackinsert.value)
+          formCallback.value.content?.push(formCallbackinsert.value)
         }
         insertVisible.value = false
         formCallbackinsert.value = {}
-        console.log(insertArr.value, 'insertArr.value');
       } else {
       }
     })
     .then(() => { });
 }
 
-const linkClose = (e: any) => {
+const linkSuccess = (e: any) => {
+  linkVisible.value = false;
   console.log(e);
+  formCallbackinsert.value.url = e.data.url
 }
 // 提交回传配置
 const submitForm = (formEl: FormInstance | undefined) => {
@@ -137,7 +150,28 @@ const submitForm = (formEl: FormInstance | undefined) => {
   formEl
     .validate(valid => {
       if (valid) {
-
+        let params = {
+          type: formCallback.value.type,
+          keyword: formCallback.value.keyword,
+          miniprogram_id: formCallback.value.miniprogram_id,
+          content: formCallback.value.content,
+        }
+        if (props.primary?.id) {
+          params.id = props.primary?.id
+          wechatPlatformSubscribeEdit(props.primary?.id, params).then(res => {
+            ElMessage.success(res.message)
+            emit('close')
+          }).catch(e => {
+            loading.value = false;
+          })
+        } else {
+          wechatPlatformSubscribeAdd(params).then(res => {
+            ElMessage.success(res.message)
+            emit('close')
+          }).catch(e => {
+            loading.value = false;
+          })
+        }
         loading.value = false;
       } else {
         loading.value = false;
@@ -145,12 +179,23 @@ const submitForm = (formEl: FormInstance | undefined) => {
     })
     .then(() => { });
 }
-
+const detailInit = (id: number | string) => {
+  wechatPlatformSubscribeDetail(id).then(res => {
+    formCallback.value = res.data
+  })
+}
 
 if (props.primary) {
-  formCallback.value = JSON.parse(JSON.stringify(props.primary))
+  if (checkPermission('wechatPlatform.WechatSubscribe.detail')) {
+    detailInit(props.primary.id)
+  } else {
+    formCallback.value = JSON.parse(JSON.stringify(props.primary))
+  }
 }
 onMounted(() => {
+  if (JSON.parse(Cache.get('nav_data'))?.app.id) {
+    formCallback.value.miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+  }
 });
 </script>
 

+ 46 - 28
src/views/officialAccount/attentionReply/form/generateLink.vue

@@ -1,41 +1,44 @@
 <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>
+  <el-tabs>
+    <el-tab-pane label="视频链接">
+      <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+        <el-form-item label="短剧" prop="video_id" :rules="[{ required: true, message: '请选择短剧' }]">
+          <el-select v-model="formCallback.video_id" class="w-full" clearable filterable remote
+            :remote-method="remoteMethod" placeholder="请输入短剧">
+            <el-option v-for="item in videoList" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="剧集" prop="sequence" v-if="formCallback.video_id"
+          :rules="[{ required: true, message: '请选择剧集' }]">
+          <el-select v-model="formCallback.sequence" class="w-full" clearable filterable placeholder="请选择剧集">
+            <el-option v-for="item in videoEpisodeList" :key="item.id" :label="item.series_name"
+              :value="item.series_sequence" />
+          </el-select>
+        </el-form-item>
+        <div class="flex justify-end">
+          <el-button type="primary" @click="submitForm(form)">{{
+            $t('system.confirm')
+          }}</el-button>
+        </div>
+      </el-form>
+    </el-tab-pane>
+  </el-tabs>
 </template>
 
 <script lang="ts" setup>
 import { Plus, InfoFilled } from '@element-plus/icons-vue';
 import { FormInstance } from 'element-plus';
-import { videoStockVideoList } from '@/api/video/index'
+import { videoStockVideoList, videoStockEpisodeList } from '@/api/video/index'
+import { wechatPlatformCommonGetPlayUrl } from '@/api/officialAccount/keywordReply/index'
 const props = defineProps({
   primary: Object | null,
 });
-const emit = defineEmits(['close']);
+const emit = defineEmits(['close', 'success']);
+const form = ref<FormInstance>()
 const loading = ref(false)
 const formCallback = ref({})
 const videoList = ref([])
+const videoEpisodeList = ref([])
 
 const initVideoList = (params?: object) => {
   videoStockVideoList({ limit: 999, ...params }).then(res => {
@@ -43,6 +46,16 @@ const initVideoList = (params?: object) => {
     videoList.value = res.data
   })
 }
+watch(() => formCallback.value.video_id, (newValue, oldValue) => {
+  initVideoStockEpisodeList({ video_id: newValue })
+})
+
+const initVideoStockEpisodeList = (params: object) => {
+  videoStockEpisodeList({ is_fee: 1, ...params }).then(res => {
+    console.log(res, 'videoEpisodeList');
+    videoEpisodeList.value = res.data;
+  })
+}
 const remoteMethod = (query: string) => {
   if (query) {
     initVideoList({ videoName: query })
@@ -59,8 +72,13 @@ const submitForm = (formEl: FormInstance | undefined) => {
   formEl
     .validate(valid => {
       if (valid) {
-
-        loading.value = false;
+        wechatPlatformCommonGetPlayUrl(formCallback.value).then(res => {
+          console.log(res, 'wechatPlatformCommonGetPlayUrl');
+          emit('success', res)
+          loading.value = false;
+        }).catch(e => {
+          loading.value = false;
+        })
       } else {
         loading.value = false;
       }

+ 106 - 35
src/views/officialAccount/attentionReply/index.vue

@@ -1,57 +1,73 @@
 <template>
-  <div class="mb-3">
+  <div class="mb-3" v-action="'wechatPlatform.WechatSubscribe.setConfig'">
     <el-card shadow="never">
       <el-form-item label="是否启用关注回复">
-        <el-switch v-model="isEnableReply" />
+        <el-switch v-model="isEnableReply" @change="isEnableReplyChange" :active-value="1" :inactive-value="0" />
       </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>
+          <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary"
+            href="https://docs.qq.com/doc/DRHpxaENaWUtZY01L" target="_blank">点击查看</el-link>
           操作说明;如果关闭此按钮,列表所有回复全部不可用
         </div>
       </div>
     </el-card>
   </div>
-  <div class="flex flex-col justify-between w-full sm:flex-row">
+  <div class="flex flex-col justify-between w-full sm:flex-row" v-if="isEnableReply">
     <div class="w-full">
-      <Search :search="search" :reset="reset">
+      <Search :search="search" :reset="resetQuery">
         <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 v-model="query.wechat_authorization_info_id" filterable remote clearable
+              :remote-method="(query) => { remoteMethod(query, 'wechatPlatformOfficialAccountList') }"
+              placeholder="选择公众号">
+              <el-option v-for="item in officialAccountsList" :key="item.id" :label="item.nick_name" :value="item.id" />
             </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 class="pt-5 pl-2">
+              <el-button v-action="'wechatPlatform.WechatSubscribe.add'" 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">
+              <el-table-column prop="nick_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">
                   <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 }}
+                    <!--text-lg font-bold text-blue-400 cursor-pointer -->
+                    <div v-for="item in scope.row.wechat_accounts" :key="item.id">
+                      <!--  @click="openType('createVisible', { single: true, currentwechat: item, ...scope.row })" -->
+                      {{ item.nick_name }}
                     </div>
                   </div>
                 </template>
               </el-table-column>
-              <el-table-column prop="updated_at" label="创建时间" min-width="200"></el-table-column>
+              <el-table-column prop="created_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>
+                  <el-button link type="primary" v-action="'wechatPlatform.WechatSubscribe.edit'" size="small"
+                    @click="openType('createVisible', scope.row)">编辑</el-button>
                   <br />
-                  <el-button link type="primary" size="small"
+                  <el-button link type="primary" size="small" v-action="'wechatPlatform.WechatSubscribe.detail'"
                     @click="openType('createVisible', { look: true, ...scope.row })">查看</el-button>
                   <br />
-                  <el-button link type="primary" size="small" @click="deleteChange(scope.row)">删除</el-button>
+                  <el-button link type="primary" size="small" v-action="'wechatPlatform.WechatSubscribe.del'"
+                    @click="deleteChange(scope.row)">删除</el-button>
                   <br />
-                  <el-button link type="primary" size="small"
+                  <el-button link type="primary" size="small" v-action="'wechatPlatform.WechatSubscribe.allocation'"
                     @click="openType('configPublicVisible', scope.row)">配置公众号</el-button>
                   <br />
                 </template>
@@ -65,30 +81,49 @@
         <Create @close="closeType('createVisible')" :primary="current" />
       </Dialog>
       <Dialog v-model="configPublicVisible" title="配置公众号" destroy-on-close>
-        <configPublic @close="closeType('createVisible')" :primary="current" />
+        <configPublic @close="closeType('configPublicVisible')" typeName="关注回复" :primary="current" />
       </Dialog>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, ref } from 'vue';
+import { InfoFilled } from '@element-plus/icons-vue';
+import { checkPermission } from '@/directives/permission';
 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';
+import {
+  wechatPlatformSubscribeDel,
+  wechatPlatformSubscribeGetConfig,
+  wechatPlatformSubscribeSetConfig
+} from '@/api/officialAccount/attentionReply/index'
+import { wechatPlatformOfficialAccountList } from '@/api/officialAccount/officialList/index'
+const api = 'wechatPlatform/subscribe/list';
+import Cache from '@/support/cache';
 const createVisible = ref(false)
 const configPublicVisible = ref(false)
-const isEnableReply = ref(true)
+const isEnableReply = ref(0)
 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 isEnableReplyChange = (e: any) => {
+  console.log(e);
+  if (!checkPermission('wechatPlatform.WechatSubscribe.setConfig')) return;
+  if (JSON.parse(Cache.get('nav_data'))?.app.id) {
+    const miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+    wechatPlatformSubscribeSetConfig(miniprogram_id, { value: e }).then(res => {
+      console.log(res, 'wechatPlatformKeywordGetConfigSet');
+      ElMessage.success(res.message)
+    }).catch(e => {
+      isEnableReply.value = Number(!isEnableReply.value)
+    })
+  }
+}
 const openType = (type: string, data: object | null) => {
   current.value = data;
   switch (type) {
@@ -97,7 +132,9 @@ const openType = (type: string, data: object | null) => {
       if (current.value?.id) {
         createTitle.value = '编辑'
         if (current.value.single) {
-          createTitle.value = current.value.name
+          createTitle.value = current.value.currentwechat.nick_name
+        } else if (current.value.look) {
+          createTitle.value = "查看"
         }
       } else {
         createTitle.value = '新增'
@@ -108,12 +145,19 @@ const openType = (type: string, data: object | null) => {
       break;
   }
 }
+const resetQuery = () => {
+  query.value = Object.assign({ page: query.value.page, limit: query.value.limit, miniprogram_id: query.value.miniprogram_id });
+  search()
+}
 
 const closeType = (type: string) => {
   switch (type) {
     case 'createVisible':
       createVisible.value = false
       break;
+    case 'configPublicVisible':
+      configPublicVisible.value = false
+      break;
   }
   search()
 }
@@ -129,23 +173,50 @@ const deleteChange = (row: object) => {
     }
   )
     .then(() => {
+      wechatPlatformSubscribeDel({ ids: row.id }).then(res => {
+        console.log(res);
+        ElMessage.success(res.message)
+        search()
+      })
       // tableData.value.splice(index, 1)
     })
     .catch(() => {
 
     })
 }
-const init = () => {
-  manageMiniprogramCompanylist().then(res => {
-    messageType.value = res.data
-  })
-  manageMiniprogramTypelist().then(res => {
-    officialAccountsList.value = res.data
-  })
+const remoteMethod = (query: string, type: string,) => {
+  console.log(query, 'queryquery', type);
+  switch (type) {
+    case 'wechatPlatformOfficialAccountList':
+      initRemoteOption('wechatPlatformOfficialAccountList', { nick_name: query })
+      break;
+  }
+}
+
+const initRemoteOption = (type: string, params?: object) => {
+  switch (type) {
+    case 'wechatPlatformOfficialAccountList':
+      wechatPlatformOfficialAccountList({ limit: 99, ...params }).then(res => {
+        officialAccountsList.value = res.data
+      })
+      break;
+  }
+}
+const initGetConfig = () => {
+  if (!checkPermission('wechatPlatform.WechatSubscribe.getConfig')) return;
+  if (JSON.parse(Cache.get('nav_data'))?.app.id) {
+    const miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+    query.value.miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+    wechatPlatformSubscribeGetConfig(miniprogram_id).then(res => {
+      console.log(res, 'wechatPlatformKeywordGetConfig');
+      isEnableReply.value = res.data.data
+    })
+  }
 }
 
 onMounted(() => {
-  init()
+  initGetConfig()
+  initRemoteOption('wechatPlatformOfficialAccountList')
   search();
 });
 </script>

+ 29 - 7
src/views/officialAccount/components/configPublic.vue

@@ -1,13 +1,13 @@
 <template>
   <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
-    <el-form-item label="配置公众号" prop="wx_auth_ids" :rules="[{ required: true, message: '公众号必须选择' }]" label-width="120px">
+    <el-form-item label="配置公众号" prop="wx_auth_ids" :rules="[{ required: false, message: '公众号必须选择' }]" label-width="120px">
       <div class="wrapper">
         <input class="filter-input" v-model="filterPlublic" clearable placeholder="请输入公众号名称"
           @change="filterExistPlublic" />
         <div class="wrapper-inner">
           <el-checkbox v-model="selectAllValue" @change="handleCheckAllChange">全选</el-checkbox>
           <el-checkbox-group v-model="formCallback.wx_auth_ids" @change="handleCheckedChange" class="flex flex-col">
-            <el-checkbox :key="sIndex" :label="item.id" v-for="(item, sIndex) in filteredCities"
+            <el-checkbox :key="sIndex" :label="item" v-for="(item, sIndex) in filteredCities"
               :checked="Boolean(item.is_auth)">
               {{ item.nick_name }}
             </el-checkbox>
@@ -26,10 +26,16 @@
 <script lang="ts" setup>
 import { Plus, InfoFilled } from '@element-plus/icons-vue';
 import { wechatPlatformKeywordAuthList, wechatPlatformKeywordAllocation } from '@/api/officialAccount/keywordReply/index'
+import { wechatPlatformSubscribeAuthList, wechatPlatformSubscribeAllocation } from '@/api/officialAccount/attentionReply/index'
+import { wechatPlatformMenuAuthList, wechatPlatformMenuAllocation } from '@/api/officialAccount/publicCustomMenu/index'
 import { FormInstance } from 'element-plus';
 const props = defineProps({
   primary: Object | null,
+  typeName: String
 });
+let submitFormapi = wechatPlatformKeywordAllocation;
+let authListapi = wechatPlatformKeywordAuthList;
+
 const form = ref<FormInstance>()
 const emit = defineEmits(['close']);
 const loading = ref(false)
@@ -39,7 +45,7 @@ const filterPlublic = ref('')
 let goalOptions = ref([])
 
 const filteredCities = computed(() => {
-  return goalOptions.value.filter(city => city.nick_name.includes(filterPlublic.value));
+  return goalOptions.value.filter(item => item.nick_name.includes(filterPlublic.value));
 });
 
 const selectAllValue = computed(() => {
@@ -67,8 +73,8 @@ const submitForm = (formEl: FormInstance | undefined) => {
     .validate(valid => {
       if (valid) {
         console.log(formCallback.value, 'formCallback.valueformCallback.value');
-        const wx_auth_ids = formCallback.value.wx_auth_ids.join(',')
-        wechatPlatformKeywordAllocation(props.primary.id, { wx_auth_ids }).then(res => {
+        const wx_auth_ids = formCallback.value.wx_auth_ids.map(el => el.id).join(',')
+        submitFormapi(props.primary.id, { wx_auth_ids }).then(res => {
           console.log(res, 'wx_auth_ids');
           ElMessage.success(res.message)
           emit('close')
@@ -83,10 +89,26 @@ const submitForm = (formEl: FormInstance | undefined) => {
 
 
 if (props.primary) {
-  wechatPlatformKeywordAuthList(props.primary.id).then(res => {
+  const type = props.typeName
+  console.log(type, props.typeName, 'props.primary.apiName');
+  switch (type) {
+    case '关键字回复':
+      submitFormapi = wechatPlatformKeywordAllocation
+      authListapi = wechatPlatformKeywordAuthList
+      break;
+    case '关注回复':
+      submitFormapi = wechatPlatformSubscribeAllocation
+      authListapi = wechatPlatformSubscribeAuthList
+      break;
+    case '公众号菜单':
+      submitFormapi = wechatPlatformMenuAllocation
+      authListapi = wechatPlatformMenuAuthList
+      break;
+  }
+  authListapi(props.primary.id).then(res => {
     goalOptions.value = res.data
     console.log(goalOptions.value, 'goalOptionsgoalOptions');
-    const wx_auth_ids = goalOptions.value?.filter(el => el.is_auth).map(el => el.id)
+    const wx_auth_ids = goalOptions.value?.filter(el => el.is_auth)
     if (wx_auth_ids.length > 0) {
       formCallback.value.wx_auth_ids = wx_auth_ids
     }

+ 116 - 0
src/views/officialAccount/components/configPublicPlus.vue

@@ -0,0 +1,116 @@
+<template>
+  <div class="flex flex-col w-full">
+    <div class="wrapper">
+      <input class="filter-input" v-model="filterPlublic" clearable placeholder="请输入公众号名称" @change="filterExistPlublic" />
+      <div class="wrapper-inner">
+        <el-checkbox v-model="selectAllValue" @change="handleCheckAllChange">全选</el-checkbox>
+        <el-checkbox-group v-model="formCallback.wx_auth_ids" @change="handleCheckedChange" class="flex flex-col">
+          <el-checkbox :key="sIndex" :label="item.id" v-for="(item, sIndex) in filteredCities">
+            {{ item.nick_name }}
+          </el-checkbox>
+        </el-checkbox-group>
+      </div>
+    </div>
+    <div class="flex justify-end w-full" v-if="props.isShowButton">
+      <el-button type="primary" @click="submitForm">{{
+        $t('system.confirm')
+      }}</el-button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { wechatPlatformOfficialAccountList } from '@/api/officialAccount/officialList/index'
+import { FormInstance } from 'element-plus';
+interface Props {
+  primary?: object | null,
+  isShowButton?: boolean,
+  required?: boolean
+}
+// 接收父组件传递过来的参数
+const props = withDefaults(defineProps<Props>(), {
+  isShowButton: true,
+  required: false,
+});
+const emit = defineEmits(['close', 'success', 'change']);
+const formCallback = ref({ wx_auth_ids: [] })
+const filterPlublic = ref('')
+// const isIndeterminate = ref(false)
+let goalOptions = ref([])
+
+const filteredCities = computed(() => {
+  return goalOptions.value.filter(item => item.nick_name.includes(filterPlublic.value));
+});
+
+watch(() => formCallback.value.wx_auth_ids, (newVal, oldVal) => {
+  emit('change', formCallback.value.wx_auth_ids)
+}, {
+  immediate: true,
+  deep: true
+})
+
+const selectAllValue = computed(() => {
+  return formCallback.value?.wx_auth_ids?.length > 0 && formCallback.value?.wx_auth_ids?.length == goalOptions.value.length
+});
+
+const filterExistPlublic = () => {
+  console.log('filterExistPlublicfilterExistPlublic');
+}
+const handleCheckAllChange = (val: boolean) => {
+  formCallback.value.wx_auth_ids = val ? filteredCities.value?.map(el => el.id) : []
+  console.log(formCallback.value.wx_auth_ids, val, 'formCallback.value.wx_auth_ids');
+  // isIndeterminate.value = false
+}
+const handleCheckedChange = (value: string[]) => {
+  // isIndeterminate.value = checkedCount > 0 && checkedCount < goalOptions.value.length
+}
+
+const submitForm = () => {
+  const wx_auth_ids = formCallback.value.wx_auth_ids
+  if (props.required) {
+    if (wx_auth_ids.length <= 0) return ElMessage.error('请选择公众号')
+  }
+  console.log(wx_auth_ids, 'formCallback.valueformCallback.value');
+  emit('success', wx_auth_ids)
+  // emit('close')
+}
+
+const init = () => {
+  wechatPlatformOfficialAccountList({ is_export: true }).then(res => {
+    goalOptions.value = res.data
+  })
+}
+
+if (props.primary) {
+  formCallback.value.wx_auth_ids = props.primary?.gzh_ids_arr
+  console.log(props.primary, formCallback.value, 'props.primaryprops.primary');
+}
+onMounted(() => {
+  init()
+});
+</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>

+ 48 - 25
src/views/officialAccount/keywordReply/form/create.vue

@@ -1,20 +1,23 @@
 <template>
+  <div class="mb-3" v-if="!props.primary?.look">
+    <el-alert title="注:如果一个公众号出现关键字重复配置(比如关键字相同,回复内容相同或不同的情况),以最新一次配置为准,原配置将被覆盖" type="error" :closable="false" />
+  </div>
   <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
-    <el-form-item label="关键词" prop="keyword" :rules="[{ required: true, message: '关键词必须填写' }]" label-width="120px">
+    <el-form-item label="关键字" prop="keyword" :rules="[{ required: true, message: '关键字必须填写' }]" label-width="120px">
       <template #label>
         <div class="flex items-center">
           <el-tooltip placement="top">
             <template #content>
-              <span>关键词</span> <br />
+              <span>不同关键字间以英文逗号分割,或的关系</span> <br />
             </template>
             <el-icon>
               <InfoFilled />
             </el-icon>
           </el-tooltip>
-          <span>关键</span>
+          <span>关键</span>
         </div>
       </template>
-      <el-input :disabled="props.primary?.look" v-model="formCallback.keyword" placeholder="请输入关键" />
+      <el-input :disabled="props.primary?.look" v-model="formCallback.keyword" placeholder="请输入关键" />
     </el-form-item>
     <el-form-item label="消息内容" prop="type" :rules="[{ required: true, message: '消息类型必须填写' }]" label-width="120px">
       <el-radio-group v-model="formCallback.type" :disabled="props.primary?.look">
@@ -37,12 +40,19 @@
           </div>
         </template>
         <div class="insert-content">
-          <div v-for="(item, index) in formCallback.content" :key="index">
-            <el-button type="primary" link v-if="item.url" @click="linkClick({ index, ...item })">
-              {{ item.title }}
-            </el-button>
-            <el-input style="boder:none;" :disabled="props.primary?.look" v-else v-model="item.title"
-              clearable></el-input>
+          <div class="insert-content">
+            <div v-for="(item, index) in formCallback.content" :key="index" class="flex items-center">
+              <span class="mr-5">
+                <el-icon @click="removeItem(index)" class="cursor-pointer">
+                  <Delete />
+                </el-icon>
+              </span>
+              <el-button type="primary" link v-if="item.url" @click="linkClick({ index, ...item })">
+                {{ item.title }}
+              </el-button>
+              <el-input style="boder:none;" type="textarea" autosize :disabled="props.primary?.look" v-else
+                v-model="item.title" clearable></el-input>
+            </div>
           </div>
         </div>
       </el-card>
@@ -59,13 +69,13 @@
       <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>
+          <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary"
+            href="https://docs.qq.com/doc/DUWlNb0RsTXFQelFF" target="_blank">关联小程序</el-link>
           <span>,已关联的小程序可被使用在自定义菜单、模板消息和附近的小程序等场景中</span>
         </div>
         <el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
-          <el-input style="width:300px;" :disabled="props.primary?.look" v-model="formCallbackinsert.title"
-            auto-complete="off" placeholder="请输入标题"></el-input>
+          <el-input style="width:300px;" type="textarea" autosize :disabled="props.primary?.look"
+            v-model="formCallbackinsert.title" auto-complete="off" placeholder="请输入标题"></el-input>
         </el-form-item>
         <el-form-item label="链接" prop="url" :rules="[{ required: true, message: '链接必须填写' }]" label-width="120px">
           <el-input style="width:300px;" :disabled="props.primary?.look" v-model="formCallbackinsert.url"
@@ -75,9 +85,9 @@
         </el-form-item>
       </div>
       <div v-else>
-        <el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
-          <el-input style="width:300px;" :disabled="props.primary?.look" type="textarea"
-            v-model="formCallbackinsert.title" auto-complete="off" placeholder="请输入标题"></el-input>
+        <el-form-item label="内容" prop="title" :rules="[{ required: true, message: '内容必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" :disabled="props.primary?.look" type="textarea" autosize
+            v-model="formCallbackinsert.title" auto-complete="off" placeholder="请输入内容"></el-input>
         </el-form-item>
       </div>
       <div class="flex justify-end" v-if="!props.primary?.look">
@@ -87,17 +97,18 @@
   </Dialog>
 
   <Dialog v-model="linkVisible" title="选择链接" destroy-on-close>
-    <generateLink @close="linkClose" />
+    <generateLink @success="linkSuccess" />
   </Dialog>
 </template>
 
 <script lang="ts" setup>
-import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { Plus, InfoFilled, Delete } from '@element-plus/icons-vue';
 import { FormInstance } from 'element-plus';
 import { useRouter, useRoute } from 'vue-router'
-import { wechatPlatformKeywordAdd, wechatPlatformKeywordEdit } from '@/api/officialAccount/keywordReply/index'
+import { wechatPlatformKeywordAdd, wechatPlatformKeywordEdit, wechatPlatformKeywordDetail } from '@/api/officialAccount/keywordReply/index'
 import generateLink from './generateLink.vue'
 import Cache from '@/support/cache';
+import { checkPermission } from '@/directives/permission';
 const router = useRouter()
 const route = useRoute()
 const insertform = ref()
@@ -126,7 +137,6 @@ const linkClick = (row: object) => {
 }
 const insertSubmitChange = (formEl: FormInstance | undefined) => {
   if (!formEl) return;
-  if (!formEl) return;
   formEl
     .validate(valid => {
       if (valid) {
@@ -137,14 +147,19 @@ const insertSubmitChange = (formEl: FormInstance | undefined) => {
         }
         insertVisible.value = false
         formCallbackinsert.value = {}
+        console.log('formCallbackinsert.valueformCallbackinsert.value');
       } else {
       }
     })
     .then(() => { });
 }
-
-const linkClose = (e: any) => {
+const removeItem = (idx: number) => {
+  formCallback.value.content.splice(idx, 1);
+}
+const linkSuccess = (e: any) => {
+  linkVisible.value = false;
   console.log(e);
+  formCallbackinsert.value.url = e.data.url
 }
 // 提交回传配置
 const submitForm = (formEl: FormInstance | undefined) => {
@@ -183,11 +198,19 @@ const submitForm = (formEl: FormInstance | undefined) => {
     })
     .then(() => { });
 }
-
+const detailInit = (id: number | string) => {
+  wechatPlatformKeywordDetail(id).then(res => {
+    formCallback.value = res.data
+  })
+}
 
 if (props.primary) {
   console.log(props.primary, 'props.primary');
-  formCallback.value = JSON.parse(JSON.stringify(props.primary))
+  if (checkPermission('wechatPlatform.WechatKeywords.detail')) {
+    detailInit(props.primary.id)
+  } else {
+    formCallback.value = JSON.parse(JSON.stringify(props.primary))
+  }
 }
 onMounted(() => {
   if (JSON.parse(Cache.get('nav_data'))?.app.id) {

+ 19 - 7
src/views/officialAccount/keywordReply/form/generateLink.vue

@@ -2,15 +2,17 @@
   <el-tabs>
     <el-tab-pane label="视频链接">
       <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
-        <el-form-item label="短剧" prop="video_id">
+        <el-form-item label="短剧" prop="video_id" :rules="[{ required: true, message: '请选择短剧' }]">
           <el-select v-model="formCallback.video_id" class="w-full" clearable filterable remote
             :remote-method="remoteMethod" placeholder="请输入短剧">
             <el-option v-for="item in videoList" :key="item.id" :label="item.name" :value="item.id" />
           </el-select>
         </el-form-item>
-        <el-form-item label="剧集" prop="sequence" v-if="formCallback.video_id">
+        <el-form-item label="剧集" prop="sequence" v-if="formCallback.video_id"
+          :rules="[{ required: true, message: '请选择剧集' }]">
           <el-select v-model="formCallback.sequence" class="w-full" clearable filterable placeholder="请选择剧集">
-            <el-option v-for="item in videoEpisodeList" :key="item.id" :label="item.name" :value="item.id" />
+            <el-option v-for="item in videoEpisodeList" :key="item.id" :label="item.series_name"
+              :value="item.series_sequence" />
           </el-select>
         </el-form-item>
         <div class="flex justify-end">
@@ -27,10 +29,12 @@
 import { Plus, InfoFilled } from '@element-plus/icons-vue';
 import { FormInstance } from 'element-plus';
 import { videoStockVideoList, videoStockEpisodeList } from '@/api/video/index'
+import { wechatPlatformCommonGetPlayUrl } from '@/api/officialAccount/keywordReply/index'
 const props = defineProps({
   primary: Object | null,
 });
-const emit = defineEmits(['close']);
+const emit = defineEmits(['close', 'success']);
+const form = ref<FormInstance>()
 const loading = ref(false)
 const formCallback = ref({})
 const videoList = ref([])
@@ -42,9 +46,12 @@ const initVideoList = (params?: object) => {
     videoList.value = res.data
   })
 }
+watch(() => formCallback.value.video_id, (newValue, oldValue) => {
+  initVideoStockEpisodeList({ video_id: newValue })
+})
 
 const initVideoStockEpisodeList = (params: object) => {
-  videoStockEpisodeList(params).then(res => {
+  videoStockEpisodeList({ is_fee: 1, ...params }).then(res => {
     console.log(res, 'videoEpisodeList');
     videoEpisodeList.value = res.data;
   })
@@ -65,8 +72,13 @@ const submitForm = (formEl: FormInstance | undefined) => {
   formEl
     .validate(valid => {
       if (valid) {
-
-        loading.value = false;
+        wechatPlatformCommonGetPlayUrl(formCallback.value).then(res => {
+          console.log(res, 'wechatPlatformCommonGetPlayUrl');
+          emit('success', res)
+          loading.value = false;
+        }).catch(e => {
+          loading.value = false;
+        })
       } else {
         loading.value = false;
       }

+ 76 - 38
src/views/officialAccount/keywordReply/index.vue

@@ -1,39 +1,44 @@
 <template>
-  <div class="mb-3">
+  <div class="mb-3" v-action="'wechatPlatform.WechatKeywords.setConfig'">
     <el-card shadow="never">
-      <el-form-item label="是否启用关回复">
-        <el-switch v-model="isEnableReply" />
+      <el-form-item label="是否启用关键字回复">
+        <el-switch v-model="isEnableReply" @change="isEnableReplyChange" :active-value="1" :inactive-value="0" />
       </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="flex flex-col justify-between w-full sm:flex-row" v-if="isEnableReply">
     <div class="w-full">
-      <Search :search="search" :reset="reset">
+      <Search :search="search" :reset="resetQuery">
         <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>
+            <span class="mr-5 text-lg font-bold text-dark-600">自定义关键字</span>
           </div>
           <el-form-item label="关键字">
-            <el-input placeholder="请输入关键字" v-model="query.key" clearable></el-input>
+            <el-input placeholder="请输入关键字" v-model="query.keyword" 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 v-model="query.wechat_authorization_info_id" filterable remote clearable
+              :remote-method="(query) => { remoteMethod(query, 'wechatPlatformOfficialAccountList') }"
+              placeholder="选择公众号">
+              <el-option v-for="item in officialAccountsList" :key="item.id" :label="item.nick_name" :value="item.id" />
             </el-select>
           </el-form-item>
         </template>
         <template v-slot:content>
           <div class="table-default">
+            <div class="mt-3">
+              <el-alert title="注:如果一个公众号出现关键字重复配置(比如关键字相同,回复内容相同或不同的情况),以最新一次配置为准,原配置将被覆盖" type="warning" show-icon
+                :closable="false" />
+            </div>
             <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>
+              <el-button type="primary" v-action="'wechatPlatform.WechatKeywords.add'" size="default"
+                @click="openType('createVisible', null)">新增</el-button>
+              <el-button size="default" v-action="'wechatPlatform.WechatKeywords.del'" @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>
@@ -58,8 +63,8 @@
               <el-table-column prop="nick_name" label="配置公众号" min-width="200">
                 <template #default="scope">
                   <div class="flex flex-col">
-                    <!-- text-blue-400 cursor-pointer -->
-                    <div class="text-lg font-bold " v-for="item in scope.row.wechat_accounts" :key="item.id">
+                    <!-- text-lg font-bold text-blue-400 cursor-pointer -->
+                    <div v-for="item in scope.row.wechat_accounts" :key="item.id">
                       <!--  @click="openType('createVisible', { single: true, currentwechat: item, ...scope.row })" -->
                       {{ item.nick_name }}
                     </div>
@@ -68,14 +73,16 @@
               </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>
+                  <el-button link type="primary" v-action="'wechatPlatform.WechatKeywords.edit'" size="small"
+                    @click="openType('createVisible', scope.row)">编辑</el-button>
                   <br />
-                  <el-button link type="primary" size="small"
+                  <el-button link type="primary" v-action="'wechatPlatform.WechatKeywords.detail'" 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>
+                  <el-button link type="primary" size="small" v-action="'wechatPlatform.WechatKeywords.del'"
+                    @click="deleteChange(scope.row)">删除</el-button>
                   <br />
-                  <el-button link type="primary" size="small"
+                  <el-button link type="primary" size="small" v-action="'wechatPlatform.WechatKeywords.allocation'"
                     @click="openType('configPublicVisible', scope.row)">配置公众号</el-button>
                   <br />
                 </template>
@@ -89,7 +96,7 @@
         <Create @close="closeType('createVisible')" :primary="current" />
       </Dialog>
       <Dialog v-model="configPublicVisible" title="配置公众号" destroy-on-close>
-        <configPublic @close="closeType('configPublicVisible')" :primary="current" />
+        <configPublic @close="closeType('configPublicVisible')" typeName="关键字回复" :primary="current" />
       </Dialog>
     </div>
   </div>
@@ -97,22 +104,22 @@
 
 <script lang="ts" setup>
 import { InfoFilled } from '@element-plus/icons-vue';
+import { checkPermission } from '@/directives/permission';
 import Create from './form/create.vue';
 import configPublic from '@/views/officialAccount/components/configPublic.vue'
 import { useGetList } from '@/hook/curd/useGetList';
-import { manageMiniprogramCompanylist, manageMiniprogramTypelist } from '@/api/applet/index'
 import {
   wechatPlatformKeywordGetConfig,
-  wechatPlatformKeywordGetConfigSet,
+  wechatPlatformKeywordSetConfig,
   wechatPlatformKeywordDel
-} from '@/api/officialAccount/keywordReply'
+} from '@/api/officialAccount/keywordReply/index'
+import { wechatPlatformOfficialAccountList } from '@/api/officialAccount/officialList/index'
 const api = 'wechatPlatform/keyword/list';
 import Cache from '@/support/cache';
 const createVisible = ref(false)
 const configPublicVisible = ref(false)
-const isEnableReply = ref(true)
+const isEnableReply = ref(0)
 const current = ref<object | null>({})
-const messageType = ref([])
 const officialAccountsList = ref([])
 const createTitle = ref('新增')
 const { data, query, search, reset, loading } = useGetList(api);
@@ -120,6 +127,20 @@ const rolesIdentify = inject('rolesIdentify')
 const tableData = computed(() => data.value?.data);
 
 const multipleSelection = ref([])
+
+const isEnableReplyChange = (e: any) => {
+  console.log(e);
+  if (!checkPermission('wechatPlatform.WechatKeywords.setConfig')) return;
+  if (JSON.parse(Cache.get('nav_data'))?.app.id) {
+    const miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+    wechatPlatformKeywordSetConfig(miniprogram_id, { value: e }).then(res => {
+      console.log(res, 'wechatPlatformKeywordGetConfigSet');
+      ElMessage.success(res.message)
+    }).catch(e => {
+      isEnableReply.value = Number(!isEnableReply.value)
+    })
+  }
+}
 // 全选
 const handleSelectionChange = (val) => {
   multipleSelection.value = val;
@@ -145,7 +166,10 @@ const openType = (type: string, data: object | null) => {
       break;
   }
 }
-
+const resetQuery = () => {
+  query.value = Object.assign({ page: query.value.page, limit: query.value.limit, miniprogram_id: query.value.miniprogram_id });
+  search()
+}
 // 批量删除
 const mulSet = () => {
   if (multipleSelection.value.length <= 0) {
@@ -203,30 +227,44 @@ const deleteChange = (row: object) => {
         ElMessage.success(res.message)
         search()
       })
-      // tableData.value.splice(index, 1)
     })
     .catch(() => {
 
     })
 }
-const init = () => {
-  manageMiniprogramCompanylist().then(res => {
-    messageType.value = res.data
-  })
-  manageMiniprogramTypelist().then(res => {
-    officialAccountsList.value = res.data
-  })
+const remoteMethod = (query: string, type: string,) => {
+  console.log(query, 'queryquery', type);
+  switch (type) {
+    case 'wechatPlatformOfficialAccountList':
+      initRemoteOption('wechatPlatformOfficialAccountList', { nick_name: query })
+      break;
+  }
 }
 
-onMounted(() => {
+const initRemoteOption = (type: string, params?: object) => {
+  switch (type) {
+    case 'wechatPlatformOfficialAccountList':
+      wechatPlatformOfficialAccountList({ limit: 99, ...params }).then(res => {
+        officialAccountsList.value = res.data
+      })
+      break;
+  }
+}
+
+const initGetConfig = () => {
+  if (!checkPermission('wechatPlatform.WechatKeywords.getConfig')) return;
   if (JSON.parse(Cache.get('nav_data'))?.app.id) {
     const miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+    query.value.miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
     wechatPlatformKeywordGetConfig(miniprogram_id).then(res => {
       console.log(res, 'wechatPlatformKeywordGetConfig');
+      isEnableReply.value = res.data.data
     })
   }
-
-  init()
+}
+onMounted(() => {
+  initGetConfig()
+  initRemoteOption('wechatPlatformOfficialAccountList')
   search();
 });
 </script>

+ 100 - 66
src/views/officialAccount/newsService/form/create.vue

@@ -1,16 +1,16 @@
 <template>
   <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
-    <el-form-item label="活动名称" prop="is_roi" :rules="[{ required: true, message: '活动名称必须填写' }]" label-width="120px">
-      <el-input v-model="formCallback.keyword" placeholder="请输入活动名称" />
+    <el-form-item label="活动名称" prop="name" :rules="[{ required: true, message: '活动名称必须填写' }]" label-width="120px">
+      <el-input v-model="formCallback.name" :disabled="props.primary?.look" placeholder="请输入活动名称" />
     </el-form-item>
-    <el-form-item label="消息内容" prop="type" :rules="[{ required: true, message: '消息类型必须填写' }]" label-width="120px">
-      <el-radio-group v-model="formCallback.type">
-        <el-radio label="txt">文本消息</el-radio>
+    <el-form-item label="消息内容" prop="message_type" :rules="[{ required: true, message: '消息类型必须选择' }]" label-width="120px">
+      <el-radio-group v-model="formCallback.message_type" :disabled="props.primary?.look">
+        <el-radio :label="1">文本消息</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>
+        <template #header v-if="!props.primary?.look">
           <div class="card-header">
             <el-popover placement="right" :visible="popoverVisible" trigger="click">
               <template #reference>
@@ -24,41 +24,47 @@
           </div>
         </template>
         <div class="insert-content">
-          <div v-for="(item, index) in formCallback.content" :key="index">
-            <el-button type="primary" link v-if="item.link" @click="linkClick({ index, ...item })">
-              {{ item.title }}
+          <div v-for="(item, index) in formCallback.message_content" :key="index" class="flex items-center">
+            <span class="mr-5">
+              <el-icon @click="removeItem(index)" class="cursor-pointer">
+                <Delete />
+              </el-icon>
+            </span>
+            <el-button type="primary" link v-if="item._url" @click="linkClick({ index, ...item })">
+              {{ item._title }}
             </el-button>
-            <el-input style="boder:none;" v-else v-model="item.title" clearable></el-input>
+            <el-input :disabled="props.primary?.look" autosize type="textarea" style="boder:none;" v-else
+              v-model="item.text" clearable></el-input>
           </div>
         </div>
       </el-card>
     </el-form-item>
-    <el-form-item label="发送用户" prop="is_roi" :rules="[{ required: true, message: '活动名称必须填写' }]" label-width="120px">
-      <el-radio-group v-model="formCallback.user">
-        <el-radio :label="3">全部粉丝</el-radio>
-        <el-radio :label="6">标签用户</el-radio>
+    <el-form-item label="发送用户" prop="u_type" :rules="[{ required: true, message: '发送用户必须选择' }]" label-width="120px">
+      <el-radio-group v-model="formCallback.u_type" :disabled="props.primary?.look">
+        <el-radio :label="1">全部粉丝</el-radio>
+        <el-radio :label="2">标签用户</el-radio>
       </el-radio-group>
     </el-form-item>
-    <el-form-item label="人群包" prop="callback_config_id"
+    <el-form-item label="人群包" v-if="formCallback.u_type == 2" prop="ug_id"
       :rules="[{ required: true, message: '请选择人群包', trigger: 'change' }]">
-      <el-select v-model="formCallback.callback_config_id" filterable remote clearable :remote-method="remoteMethod"
-        placeholder="请选择人群包">
+      <el-select v-model="formCallback.ug_id" :disabled="props.primary?.look" filterable remote clearable
+        :remote-method="remoteMethod" placeholder="请选择人群包">
         <el-option v-for="(item, index) in crowdPackageList" :key="index" :label="item.name" :value="item.id" />
       </el-select>
-      <el-button type="primary" link size="default" @click="addCrowdPackage">新增人群包</el-button>
+      <el-button v-if="!props.primary?.look" type="primary" link size="default" @click="addCrowdPackage">新增人群包</el-button>
     </el-form-item>
-    <el-form-item label="发送时间" prop="callback_config_id"
-      :rules="[{ required: true, message: '请选择发送时间', trigger: 'change' }]">
+    <el-form-item label="发送时间" prop="send_at" :rules="[{ required: true, message: '请选择发送时间', trigger: 'change' }]">
       <div>
-        <el-date-picker unlink-panels clearable @change="sendTimeChange" format="YYYY/MM/DD hh:mm:ss"
-          value-format="YYYY-MM-DD h:m:s" v-model="formCallback.sendTime" type="datetime" placeholder="选择日期时间" />
+        <el-date-picker :disabled="props.primary?.look" unlink-panels clearable @change="sendTimeChange"
+          format="YYYY/MM/DD hh:mm:ss" value-format="YYYY-MM-DD h:m:s" v-model="formCallback.send_at" type="datetime"
+          placeholder="选择日期时间" />
       </div>
-      <div>
+      <div v-if="!props.primary?.look">
         <el-button type="primary" v-for="(time, index) in timeArr" :key="index" link size="default"
           @click="setSendTime(time)">{{ time.name }}</el-button>
       </div>
     </el-form-item>
-    <div class="flex justify-end">
+    <div class="flex justify-end" v-if="!props.primary?.look">
       <el-button type="primary" @click="submitForm(form)">确定</el-button>
     </div>
   </el-form>
@@ -68,44 +74,47 @@
       <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>
+          <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary"
+            href="https://docs.qq.com/doc/DUWlNb0RsTXFQelFF" 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 label="标题" prop="_title" :rules="[{ required: true, message: '标题必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" type="textarea" autosize :disabled="props.primary?.look"
+            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 label="链接" prop="_url" :rules="[{ required: true, message: '链接必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" :disabled="props.primary?.look" v-model="formCallbackinsert._url"
+            auto-complete="off" placeholder="请输入链接"></el-input>
+          <el-button type="primary" v-if="!props.primary?.look" 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 label="内容" prop="_title" :rules="[{ required: true, message: '内容必须填写' }]" label-width="120px">
+          <el-input style="width:300px;" :disabled="props.primary?.look" type="textarea" autosize
+            v-model="formCallbackinsert._title" auto-complete="off" placeholder="请输入内容"></el-input>
         </el-form-item>
       </div>
-      <div class="flex justify-end">
+      <div class="flex justify-end" v-if="!props.primary?.look">
         <el-button type="primary" @click="insertSubmitChange(insertform)">确定</el-button>
       </div>
     </el-form>
   </Dialog>
 
   <Dialog v-model="linkVisible" title="选择链接" destroy-on-close>
-    <generateLink @close="linkClose" />
+    <generateLink @success="linkSuccess" />
   </Dialog>
 </template>
 
 <script lang="ts" setup>
-import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { Plus, InfoFilled, Delete } from '@element-plus/icons-vue';
 import { FormInstance } from 'element-plus';
 import { useRouter, useRoute } from 'vue-router'
-import { wechatPlatformKeywordAdd, wechatPlatformKeywordEdit } from '@/api/officialAccount/keywordReply/index'
+import { wechatPlatformKfMessageAdd, wechatPlatformKfMessageUpdateContent } from '@/api/officialAccount/newsService/index'
 import generateLink from './generateLink.vue'
+import Cache from '@/support/cache';
 import moment from 'moment';
+import { audienceManageUserGroupList } from '@/api/customer/segment/index'
 const router = useRouter()
 const route = useRoute()
 const insertform = ref()
@@ -124,7 +133,7 @@ const loading = ref(false)
 const linkVisible = ref(false)
 const popoverVisible = ref(false)
 const insertVisible = ref(false)
-const formCallback = ref({ type: 'txt', content: [] })
+const formCallback = ref({ message_type: 1, message_content: [], u_type: 1 })
 const formCallbackinsert = ref({})
 const insertType = ref()
 const insertTitle = computed(() => insertType.value == 'link' ? '插入小程序链接' : '插入纯文本')
@@ -133,6 +142,9 @@ const insertChange = (type: string) => {
   insertVisible.value = true;
   popoverVisible.value = false;
 }
+const removeItem = (idx: number) => {
+  formCallback.value.message_content.splice(idx, 1);
+}
 const linkClick = (row: object) => {
   formCallbackinsert.value = row
   insertVisible.value = true
@@ -141,42 +153,54 @@ const linkClick = (row: object) => {
 
 const setSendTime = (item: object) => {
   console.log(item, 'setSendTime', moment().add(item.value, item.format).format('YYYY-MM-DD HH:mm:ss'));
-  formCallback.value.sendTime = moment().add(item.value, item.format).format('YYYY-MM-DD HH:mm:ss')
+  formCallback.value.send_at = moment().add(item.value, item.format).format('YYYY-MM-DD HH:mm:ss')
 }
 const sendTimeChange = (e: object) => {
   console.log(e, 'timeChangetimeChangetimeChange');
 }
 
 const addCrowdPackage = () => {
-  router.push({ path: '', query: {} })
+  router.push({ path: '/audience/userGroup', query: {} })
 }
 
 const initRemoteOption = (type: string, params?: object) => {
   switch (type) {
-    case '':
+    case 'crowdPackage':
+      audienceManageUserGroupList({ limit: 999, ...params }).then(res => {
+        crowdPackageList.value = res.data;
+      })
       break;
   }
 }
 
 const remoteMethod = (query: string) => {
   if (query) {
-    initRemoteOption("callbackJuliangAccountList", { name: query })
+    initRemoteOption("crowdPackage", { name: query })
   } else {
-    initRemoteOption("callbackJuliangAccountList")
+    initRemoteOption("crowdPackage")
   }
 }
 
 const insertSubmitChange = (formEl: FormInstance | undefined) => {
   if (!formEl) return;
-  if (!formEl) return;
   formEl
     .validate(valid => {
       if (valid) {
+        if (insertType.value == 'link') {
+          if (JSON.parse(Cache.get('nav_data'))?.app.appid) {
+            let appid = JSON.parse(Cache.get('nav_data'))?.app.appid
+            formCallbackinsert.value.text = `<a href="" data-miniprogram-appid="${appid}" data-miniprogram-path="${formCallbackinsert.value._url}">${formCallbackinsert.value._title}</a>`
+          }
+        } else {
+          formCallbackinsert.value.text = formCallbackinsert.value._title
+        }
         if (formCallbackinsert.value.index != undefined) {
-          formCallback.value.content[formCallbackinsert.value?.index] = formCallbackinsert.value
+          formCallback.value.message_content[formCallbackinsert.value?.index] = formCallbackinsert.value
+
         } else {
-          formCallback.value.content.push(formCallbackinsert.value)
+          formCallback.value.message_content.push(formCallbackinsert.value)
         }
+        console.log(formCallbackinsert.value, 'formCallbackinsert.valueformCallbackinsert.value');
         insertVisible.value = false
         formCallbackinsert.value = {}
       } else {
@@ -185,10 +209,12 @@ const insertSubmitChange = (formEl: FormInstance | undefined) => {
     .then(() => { });
 }
 
-const linkClose = (e: any) => {
+const linkSuccess = (e: any) => {
+  linkVisible.value = false;
   console.log(e);
+  formCallbackinsert.value._url = e.data.url
 }
-// 提交回传配置
+
 const submitForm = (formEl: FormInstance | undefined) => {
   if (!formEl) return;
   loading.value = true;
@@ -196,22 +222,29 @@ const submitForm = (formEl: FormInstance | undefined) => {
   formEl
     .validate(valid => {
       if (valid) {
-        if (props.primary?.id) {
-          wechatPlatformKeywordEdit(props.primary?.id, formCallback.value).then(res => {
-            ElMessage.success(res.message)
-            emit('close')
-          }).catch(e => {
-            loading.value = false;
-          })
+        let api;
+        let params = {
+          "name": formCallback.value?.name,
+          "send_at": formCallback.value?.send_at,
+          "message_type": formCallback.value?.message_type,
+          "u_type": formCallback.value?.u_type,
+          "ug_id": formCallback.value?.ug_id,
+          "message_content": formCallback.value?.message_content,
+        }
+        if (props.primary) {
+          api = wechatPlatformKfMessageUpdateContent
+          params.id = formCallback.value.id
         } else {
-          wechatPlatformKeywordAdd(formCallback.value).then(res => {
-            ElMessage.success(res.message)
-            emit('close')
-          }).catch(e => {
-            loading.value = false;
-          })
+          api = wechatPlatformKfMessageAdd
         }
-        loading.value = false;
+        api(params).then(res => {
+          console.log(res);
+          ElMessage.success(res.message)
+          loading.value = false;
+          emit('close')
+        }).catch(e => {
+          loading.value = false;
+        })
       } else {
         loading.value = false;
       }
@@ -223,9 +256,10 @@ const submitForm = (formEl: FormInstance | undefined) => {
 if (props.primary) {
   console.log(props.primary, 'props.primary');
   formCallback.value = JSON.parse(JSON.stringify(props.primary))
-  formCallback.value.content = [{ title: 'dsds' }, { title: 'sdds', link: 'dsdsds' }]
+  formCallback.value.message_content = formCallback.value.message_content_arr
 }
 onMounted(() => {
+  initRemoteOption("crowdPackage")
 });
 </script>
 

+ 46 - 28
src/views/officialAccount/newsService/form/generateLink.vue

@@ -1,41 +1,44 @@
 <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>
+  <el-tabs>
+    <el-tab-pane label="视频链接">
+      <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+        <el-form-item label="短剧" prop="video_id" :rules="[{ required: true, message: '请选择短剧' }]">
+          <el-select v-model="formCallback.video_id" class="w-full" clearable filterable remote
+            :remote-method="remoteMethod" placeholder="请输入短剧">
+            <el-option v-for="item in videoList" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="剧集" prop="sequence" v-if="formCallback.video_id"
+          :rules="[{ required: true, message: '请选择剧集' }]">
+          <el-select v-model="formCallback.sequence" class="w-full" clearable filterable placeholder="请选择剧集">
+            <el-option v-for="item in videoEpisodeList" :key="item.id" :label="item.series_name"
+              :value="item.series_sequence" />
+          </el-select>
+        </el-form-item>
+        <div class="flex justify-end">
+          <el-button type="primary" @click="submitForm(form)">{{
+            $t('system.confirm')
+          }}</el-button>
+        </div>
+      </el-form>
+    </el-tab-pane>
+  </el-tabs>
 </template>
 
 <script lang="ts" setup>
 import { Plus, InfoFilled } from '@element-plus/icons-vue';
 import { FormInstance } from 'element-plus';
-import { videoStockVideoList } from '@/api/video/index'
+import { videoStockVideoList, videoStockEpisodeList } from '@/api/video/index'
+import { wechatPlatformCommonGetPlayUrl } from '@/api/officialAccount/keywordReply/index'
 const props = defineProps({
   primary: Object | null,
 });
-const emit = defineEmits(['close']);
+const emit = defineEmits(['close', 'success']);
+const form = ref<FormInstance>()
 const loading = ref(false)
 const formCallback = ref({})
 const videoList = ref([])
+const videoEpisodeList = ref([])
 
 const initVideoList = (params?: object) => {
   videoStockVideoList({ limit: 999, ...params }).then(res => {
@@ -43,6 +46,16 @@ const initVideoList = (params?: object) => {
     videoList.value = res.data
   })
 }
+watch(() => formCallback.value.video_id, (newValue, oldValue) => {
+  initVideoStockEpisodeList({ video_id: newValue })
+})
+
+const initVideoStockEpisodeList = (params: object) => {
+  videoStockEpisodeList({ is_fee: 1, ...params }).then(res => {
+    console.log(res, 'videoEpisodeList');
+    videoEpisodeList.value = res.data;
+  })
+}
 const remoteMethod = (query: string) => {
   if (query) {
     initVideoList({ videoName: query })
@@ -59,8 +72,13 @@ const submitForm = (formEl: FormInstance | undefined) => {
   formEl
     .validate(valid => {
       if (valid) {
-
-        loading.value = false;
+        wechatPlatformCommonGetPlayUrl(formCallback.value).then(res => {
+          console.log(res, 'wechatPlatformCommonGetPlayUrl');
+          emit('success', res)
+          loading.value = false;
+        }).catch(e => {
+          loading.value = false;
+        })
       } else {
         loading.value = false;
       }

+ 89 - 0
src/views/officialAccount/newsService/form/testSend.vue

@@ -0,0 +1,89 @@
+<template>
+  <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+    <el-form-item label="测试ID" prop="openid" :rules="[{ required: true, message: '测试ID必须填写' }]">
+      <el-input placeholder="请输入测试ID" v-model="formCallback.openid" clearable></el-input>
+    </el-form-item>
+    <el-form-item label="公众号" prop="gzh_id" :rules="[{ required: true, message: '公众号必须选择' }]">
+      <el-select v-model="formCallback.gzh_id" filterable remote clearable
+        :remote-method="(query) => { remoteMethod(query, 'wechatPlatformOfficialAccountList') }">
+        <el-option v-for="item in officialAccountsList" :key="item.id" :label="item.nick_name" :value="item.id" />
+      </el-select>
+    </el-form-item>
+    <div class="flex justify-end">
+      <el-button type="primary" @click="submitForm(form)">确定</el-button>
+    </div>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+import { InfoFilled } from '@element-plus/icons-vue';
+import { wechatPlatformOfficialAccountList } from '@/api/officialAccount/officialList/index'
+import { wechatPlatformKfMessageTestSend } from '@/api/officialAccount/newsService/index'
+import { FormInstance } from 'element-plus';
+const emit = defineEmits(['close']);
+const messageTypeList = ref([])
+const formCallback = ref([])
+const officialAccountsList = ref([])
+const rolesIdentify = inject('rolesIdentify')
+const form = ref<FormInstance>()
+const props = defineProps({
+  primary: Object | null,
+});
+const remoteMethod = (query: string, type: string,) => {
+  console.log(query, 'queryquery', type);
+  switch (type) {
+    case 'wechatPlatformOfficialAccountList':
+      initRemoteOption('wechatPlatformOfficialAccountList', { nick_name: query })
+      break;
+  }
+}
+
+const initRemoteOption = (type: string, params?: object) => {
+  switch (type) {
+    case 'wechatPlatformOfficialAccountList':
+      wechatPlatformOfficialAccountList({ limit: 999, ...params }).then(res => {
+        officialAccountsList.value = res.data
+      })
+      break;
+  }
+}
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  formEl
+    .validate(valid => {
+      if (valid) {
+        console.log(formCallback.value);
+        let params = {
+          message_id: formCallback.value?.message_id,
+          gzh_id: formCallback.value?.gzh_id,
+          openid: formCallback.value?.openid,
+        }
+        wechatPlatformKfMessageTestSend(params).then(res => {
+          console.log(res);
+          ElMessage.success(res.message)
+          emit('close')
+        }).catch(e => {
+        })
+      } else {
+      }
+    })
+    .then(() => { });
+}
+
+if (props.primary) {
+  console.log(props.primary, 'props.primary');
+  formCallback.value.message_id = props.primary.id
+}
+
+onMounted(() => {
+  initRemoteOption('wechatPlatformOfficialAccountList')
+});
+</script>
+
+<style lang="scss" scoped>
+.withdraw-popup-warn {
+  font-size: 14px;
+  font-weight: 600;
+  color: #666;
+}
+</style>

+ 184 - 52
src/views/officialAccount/newsService/index.vue

@@ -3,16 +3,30 @@
     <div class="w-full">
       <Search :search="search" :reset="reset">
         <template v-slot:body>
-
-          <el-form-item label="关键字">
-            <el-input placeholder="请输入关键字" v-model="query.key" clearable></el-input>
+          <el-form-item label="活动名称">
+            <el-input placeholder="请输入活动名称" v-model="query.name" clearable></el-input>
+          </el-form-item>
+          <el-form-item label="消息类型">
+            <el-select v-model="query.message_type" clearable filterable placeholder="请选择消息类型">
+              <el-option v-for="(item, index) in messageTypeList" :key="index" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="发送状态">
+            <el-select v-model="query.status" clearable filterable placeholder="请选择发送状态">
+              <el-option v-for="(item, index) in sendStateList" :key="index" :label="item.label" :value="item.value" />
+            </el-select>
           </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 v-model="query.gzh_id" filterable remote clearable
+              :remote-method="(query) => { remoteMethod(query, 'wechatPlatformOfficialAccountList') }">
+              <el-option v-for="item in officialAccountsList" :key="item.id" :label="item.nick_name" :value="item.id" />
             </el-select>
           </el-form-item>
+          <el-form-item label="发送时间">
+            <el-date-picker unlink-panels clearable @change="sendTimeChange" format="YYYY/MM/DD hh:mm:ss"
+              value-format="YYYY-MM-DD h:m:s" v-model="query.sendTime" type="datetimerange" :shortcuts="shortcuts"
+              range-separator="To" start-placeholder="开始时间" end-placeholder="结束时间" />
+          </el-form-item>
         </template>
         <template v-slot:content>
           <div class="mt-3">
@@ -20,51 +34,64 @@
           </div>
           <div class="table-default">
             <div class="pt-5 pl-2">
-              <el-button type="primary" size="default" @click="openType('createVisible', null)">新增</el-button>
-              <el-button size="default" @click="mulSet">批量删除</el-button>
+              <el-button type="primary" v-action="'wechatPlatform.KFMessage.add'" size="default"
+                @click="openType('createVisible', null)">新增</el-button>
+              <el-button size="default" v-action="'wechatPlatform.KFMessage.delete'" @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>
+              <el-table-column prop="id" label="ID" min-width="80"></el-table-column>
+              <el-table-column prop="name" label="活动名称" min-width="200"></el-table-column>
+              <el-table-column prop="send_at" label="发送时间" min-width="200">
+                <template #default="scope">
+                  <span>{{ scope.row.send_at }}</span>
                 </template>
+              </el-table-column>
+              <el-table-column prop="message_type_str" label="消息类型" min-width="150"></el-table-column>
+              <el-table-column prop="status_str" label="发送状态" min-width="150">
+                <template #default="scope">
+                  <el-text class="mx-1" :type="colorType(scope.row)">{{ scope.row.status_str }}</el-text>
+                </template>
+              </el-table-column>
+              <el-table-column prop="name" label="接收粉丝" min-width="200">
                 <template #default="scope">
-                  <span>{{ scope.row.updated_at }}</span>
+                  <span v-if="scope.row.u_type == 1">{{ scope.row.u_type == 1 && '全部粉丝' }}</span>
+                  <span v-else>{{ scope.row.ug_str }}</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>
+                      {{ scope.row.gzh_names }}
                     </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 />
+                  <div v-if="![4, 2].includes(scope.row.status)" v-action="'wechatPlatform.KFMessage.updateContent'">
+                    <el-button link type="primary" size="small"
+                      @click="openType('createVisible', scope.row)">编辑</el-button>
+                  </div>
+                  <div v-if="scope.row.status == 1" v-action="'wechatPlatform.KFMessage.stop'">
+                    <el-button link type="primary" size="small" @click="stopChange(scope.row)">停止</el-button>
+                  </div>
+                  <div>
+                    <el-button link type="primary" size="small" v-action="'wechatPlatform.KFMessage.detail'"
+                      @click="openType('createVisible', { look: true, ...scope.row })">查看</el-button>
+                  </div>
+                  <div v-if="scope.row.status != 2" v-action="'wechatPlatform.KFMessage.delete'">
+                    <el-button link type="primary" size="small" @click="deleteChange(scope.row)">删除</el-button>
+                  </div>
+                  <div v-action="'wechatPlatform.KFMessage.updateGZH'">
+                    <el-button link type="primary" size="small" v-if="![4, 2].includes(scope.row.status)"
+                      @click="openType('configPublicVisible', scope.row)">配置公众号</el-button>
+                  </div>
+                  <div v-action="'wechatPlatform.KFMessage.testSend'">
+                    <el-button link type="primary" size="small"
+                      @click="openType('testSendVisible', scope.row)">测试发送</el-button>
+                  </div>
                 </template>
               </el-table-column>
             </el-table>
@@ -75,8 +102,15 @@
       <Dialog v-model="createVisible" :title="createTitle" destroy-on-close>
         <Create @close="closeType('createVisible')" :primary="current" />
       </Dialog>
+      <Dialog v-model="testSendVisible" title="测试发送" destroy-on-close>
+        <testSend @close="closeType('testSendVisible')" :primary="current" />
+      </Dialog>
       <Dialog v-model="configPublicVisible" title="配置公众号" destroy-on-close>
-        <configPublic @close="closeType('createVisible')" :primary="current" />
+        <el-form-item label="配置公众号" prop="wx_auth_ids" :rules="[{ required: false, message: '公众号必须选择' }]"
+          label-width="120px">
+          <configPublicPlus :required="false" @close="closeType('configPublicVisible')" @success="configPubliSuccess"
+            :primary="current" />
+        </el-form-item>
       </Dialog>
     </div>
   </div>
@@ -84,16 +118,21 @@
 
 <script lang="ts" setup>
 import { InfoFilled } from '@element-plus/icons-vue';
+import { shortcuts } from '@/utils/shortcuts'
+import { optionsCommonParams } from '@/api/common/index'
+import testSend from './form/testSend.vue';
 import Create from './form/create.vue';
-import configPublic from '@/views/officialAccount/components/configPublic.vue'
+import configPublicPlus from '@/views/officialAccount/components/configPublicPlus.vue'
 import { useGetList } from '@/hook/curd/useGetList';
-import { manageMiniprogramCompanylist, manageMiniprogramTypelist } from '@/api/applet/index'
-const api = 'wechatPlatform/keyword/list';
+import { wechatPlatformOfficialAccountList } from '@/api/officialAccount/officialList/index'
+import { wechatPlatformKfMessageStop, wechatPlatformKfMessageDelete, wechatPlatformKfMessageUpdateGZH } from '@/api/officialAccount/newsService/index'
+const api = 'wechatPlatform/kfMessage/list';
 const createVisible = ref(false)
 const configPublicVisible = ref(false)
-const isEnableReply = ref(true)
+const testSendVisible = ref(false)
 const current = ref<object | null>({})
-const messageType = ref([])
+const messageTypeList = ref([])
+const sendStateList = ref([])
 const officialAccountsList = ref([])
 const createTitle = ref('新增')
 const { data, query, search, reset, loading } = useGetList(api);
@@ -101,6 +140,40 @@ const rolesIdentify = inject('rolesIdentify')
 const tableData = computed(() => data.value?.data);
 
 const multipleSelection = ref([])
+
+const colorType = (data: object) => {
+  const type = data.status
+  switch (type) {
+    case 3:
+      return 'success'
+    case 1:
+      return 'warning'
+    case 2:
+      return 'primary'
+    case 4:
+      return 'danger'
+  }
+}
+const configPubliSuccess = (e: any) => {
+  console.log(e);
+  wechatPlatformKfMessageUpdateGZH({ id: current.value?.id, gzh_ids: e }).then(res => {
+    console.log(res);
+    ElMessage.success(res.message)
+    configPublicVisible.value = false
+    search()
+  })
+}
+const sendTimeChange = (e: object) => {
+  console.log(e, 'timeChangetimeChangetimeChange');
+  if (query.value.sendTime) {
+    const timeArr = toRaw(e);
+    query.value.send_at_start = timeArr[0]
+    query.value.send_at_end = timeArr[1]
+  } else {
+    delete query.value.send_at_start
+    delete query.value.send_at_end
+  }
+}
 // 全选
 const handleSelectionChange = (val) => {
   multipleSelection.value = val;
@@ -114,6 +187,8 @@ const openType = (type: string, data: object | null) => {
         createTitle.value = '编辑'
         if (current.value.single) {
           createTitle.value = current.value.name
+        } else if (current.value.look) {
+          createTitle.value = "查看"
         }
       } else {
         createTitle.value = '新增'
@@ -122,9 +197,19 @@ const openType = (type: string, data: object | null) => {
     case 'configPublicVisible':
       configPublicVisible.value = true
       break;
+    case 'testSendVisible':
+      testSendVisible.value = true
+      break;
   }
 }
 
+const initCommonParams = () => {
+  optionsCommonParams().then(res => {
+    console.log(res, 'optionsCommonParams');
+    messageTypeList.value = res?.data?.wechatPlatform?.kfMessageType
+    sendStateList.value = res?.data?.wechatPlatform?.kfMessageStatus
+  })
+}
 // 批量删除
 const mulSet = () => {
   if (multipleSelection.value.length <= 0) {
@@ -141,7 +226,12 @@ const mulSet = () => {
       }
     )
       .then(() => {
-
+        const ids = multipleSelection.value.map(el => el.id)
+        wechatPlatformKfMessageDelete({ ids }).then(res => {
+          console.log(res);
+          ElMessage.success(res.message)
+          search()
+        })
       })
       .catch(() => {
 
@@ -154,13 +244,40 @@ const closeType = (type: string) => {
     case 'createVisible':
       createVisible.value = false
       break;
+    case 'configPublicVisible':
+      configPublicVisible.value = false
+      break;
+    case 'testSendVisible':
+      testSendVisible.value = false
+      break;
   }
   search()
 }
 
+const stopChange = (row: object) => {
+  ElMessageBox.confirm(
+    `确定要停止活动: ${row.name} 吗?`,
+    '提示',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      wechatPlatformKfMessageStop({ id: row.id }).then(res => {
+        console.log(res);
+        ElMessage.success(res.message)
+        search()
+      })
+    })
+    .catch(() => {
+
+    })
+}
 const deleteChange = (row: object) => {
   ElMessageBox.confirm(
-    '确定要删除吗?',
+    `确定要删除活动:${row.name} 吗?`,
     '提示',
     {
       confirmButtonText: '确定',
@@ -169,23 +286,38 @@ const deleteChange = (row: object) => {
     }
   )
     .then(() => {
-      // tableData.value.splice(index, 1)
+      wechatPlatformKfMessageDelete({ ids: [row.id] }).then(res => {
+        console.log(res);
+        ElMessage.success(res.message)
+        search()
+      })
     })
     .catch(() => {
 
     })
 }
-const init = () => {
-  manageMiniprogramCompanylist().then(res => {
-    messageType.value = res.data
-  })
-  manageMiniprogramTypelist().then(res => {
-    officialAccountsList.value = res.data
-  })
+const remoteMethod = (query: string, type: string,) => {
+  console.log(query, 'queryquery', type);
+  switch (type) {
+    case 'wechatPlatformOfficialAccountList':
+      initRemoteOption('wechatPlatformOfficialAccountList', { nick_name: query })
+      break;
+  }
+}
+
+const initRemoteOption = (type: string, params?: object) => {
+  switch (type) {
+    case 'wechatPlatformOfficialAccountList':
+      wechatPlatformOfficialAccountList({ limit: 99, ...params }).then(res => {
+        officialAccountsList.value = res.data
+      })
+      break;
+  }
 }
 
 onMounted(() => {
-  init()
+  initCommonParams()
+  initRemoteOption('wechatPlatformOfficialAccountList')
   search();
 });
 </script>

+ 51 - 0
src/views/officialAccount/publicCustomMenu/form/create.vue

@@ -0,0 +1,51 @@
+<template>
+  <div class="wrapper">
+    <el-tabs v-model="activeName" @tab-click="handleClick" v-if="props.primary?.id">
+      <el-tab-pane label="IOS系统设置" name="ios" v-if="props.primary.type == 'ios'">
+        <customMenu @close="close" type="ios" v-show="activeName == 'ios'" :primary="props.primary"></customMenu>
+      </el-tab-pane>
+      <el-tab-pane label="Android系统设置" name="android" v-if="props.primary.type == 'android'">
+        <customMenu @close="close" type="android" v-show="activeName == 'android'" :primary="props.primary"></customMenu>
+      </el-tab-pane>
+    </el-tabs>
+    <el-tabs v-model="activeName" @tab-click="handleClick" v-else>
+      <el-tab-pane label="IOS系统设置" name="ios">
+        <customMenu @close="close" type="ios" v-show="activeName == 'ios'" :primary="props.primary"></customMenu>
+      </el-tab-pane>
+      <el-tab-pane label="Android系统设置" name="android">
+        <customMenu @close="close" type="android" v-show="activeName == 'android'" :primary="props.primary"></customMenu>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+<script lang="ts" setup>
+import { ref } from 'vue'
+import type { TabsPaneContext } from 'element-plus'
+import customMenu from './customMenu.vue';
+const emit = defineEmits(['close']);
+const props = defineProps({
+  primary: Object | null,
+});
+const activeName = ref('ios')
+const close = () => {
+  emit('close')
+}
+
+if (props.primary) {
+  console.log(props.primary, 'props.primary');
+  activeName.value = props.primary.type
+}
+const handleClick = (tab: TabsPaneContext, event: Event) => {
+  console.log(tab, event)
+}
+</script>
+<style>
+.wrapper {
+  border-radius: 6px;
+  padding: 16px;
+  width: 100%;
+  background-color: #FFFFFF;
+  display: flex;
+  flex-direction: column;
+}
+</style>

+ 643 - 0
src/views/officialAccount/publicCustomMenu/form/customMenu.vue

@@ -0,0 +1,643 @@
+<template>
+  <div class="wrapper">
+    <div class="custom-menu">
+      <el-form-item label="模板名称" class="m-5" style="width:300px;" prop="title"
+        :rules="[{ required: false, message: '模板名称必须填写' }]">
+        <el-input v-model="formCallback.title" :disabled="props.primary?.look" name="title" clearable />
+      </el-form-item>
+      <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 :disabled="props.primary?.look" :list="menus.buttons" classString="flex-warpper"
+                    @end="onEnd">
+                    <template #default="{ item, index }">
+                      <div class="flex items-center justify-start wechat-menu-item" :key="index">
+                        <p :class="['menu-name', { 'cur-menu': (index) === menus.curActiveIndex ?? 0 }]"
+                          @click="addMenu(index)">
+                          <el-icon v-if="item.sub_button.length > 0">
+                            <Icon name="bars-3" class="text-sm" />
+                          </el-icon>
+                          <span> {{ item.name }}</span>
+                        </p>
+                        <div class="children-menu-items" v-if="(index) === menus.curActiveIndex ?? 0">
+                          <Draggable :disabled="props.primary?.look" :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 && !props.primary?.look" 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 v-if="!props.primary?.look" class="mt-5">可直接拖动菜单排序</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 v-if="!props.primary?.look" 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]"
+                    :disabled="props.primary?.look" label-width="100px" label-position="left">
+                    <el-form-item label="子菜单名称" prop="title" v-if="hasChildComputed">
+                      <el-input @change="nameChange"
+                        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="hasChildComputed">
+                      <el-radio-group @change="typeChange"
+                        v-model="menus.buttons[menus.curActiveIndex].sub_button[menus.curSubActiveIndex].type">
+                        <el-radio class="radio" label="miniprogram">跳转小程序链接</el-radio>
+                        <el-radio class="radio" label="view">跳转网页</el-radio>
+                        <el-radio class="radio" label="click">纯文本</el-radio>
+                      </el-radio-group>
+                    </el-form-item>
+                    <div
+                      v-if="menus?.buttons[menus?.curActiveIndex]?.sub_button[menus?.curSubActiveIndex]?.type == 'miniprogram'">
+                      <div class="withdraw-popup-warn">
+                        <span>需先</span>
+                        <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary"
+                          href="https://docs.qq.com/doc/DUWlNb0RsTXFQelFF" target="_blank">关联小程序</el-link>
+                        <span>,已关联的小程序可被使用在自定义菜单、模板消息和附近的小程序等场景中</span>
+                      </div>
+                      <el-form-item label="链接">
+                        <el-input style="width:300px;"
+                          v-model="menus.buttons[menus.curActiveIndex].sub_button[menus.curSubActiveIndex].url"
+                          auto-complete="off" placeholder="请输入链接"></el-input>
+                        <el-button type="primary" link :icon="Plus" @click="linkInsertClick('sub_button')"
+                          class="mr-6">插入链接</el-button>
+                      </el-form-item>
+                    </div>
+                    <div
+                      v-if="menus?.buttons[menus?.curActiveIndex]?.sub_button[menus?.curSubActiveIndex]?.type == 'view'">
+                      <el-form-item label="网页链接">
+                        <el-input style="width:300px;"
+                          v-model="menus.buttons[menus.curActiveIndex].sub_button[menus.curSubActiveIndex].url"
+                          auto-complete="off" placeholder="请输入公众号文章链接"></el-input>
+                      </el-form-item>
+                    </div>
+                    <div
+                      v-if="menus?.buttons[menus?.curActiveIndex]?.sub_button[menus?.curSubActiveIndex]?.type == 'click'">
+                      <el-form-item label="文本内容">
+                        <el-input style="width:300px;" type="textarea"
+                          v-model="menus.buttons[menus.curActiveIndex].sub_button[menus.curSubActiveIndex].content"
+                          auto-complete="off" placeholder="请输入文本内容"></el-input>
+                      </el-form-item>
+                    </div>
+                  </el-form>
+                </div>
+                <div v-else>
+                  <el-form :disabled="props.primary?.look" :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" @change="nameChange"
+                        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 @change="typeChange" v-model="menus.buttons[menus.curActiveIndex].type">
+                          <el-radio class="radio" label="miniprogram">跳转小程序链接</el-radio>
+                          <el-radio class="radio" label="view">跳转网页</el-radio>
+                          <el-radio class="radio" label="click">纯文本</el-radio>
+                        </el-radio-group>
+                      </el-form-item>
+                      <div v-if="menus?.buttons[menus?.curActiveIndex]?.type == 'miniprogram'">
+                        <div class="withdraw-popup-warn">
+                          <span>需先</span>
+                          <el-link class="text-lg font-bold text-blue-400 cursor-pointer" type="primary"
+                            href="https://docs.qq.com/doc/DUWlNb0RsTXFQelFF" target="_blank">关联小程序</el-link>
+                          <span>,已关联的小程序可被使用在自定义菜单、模板消息和附近的小程序等场景中</span>
+                        </div>
+                        <el-form-item label="链接">
+                          <el-input style="width:300px;" v-model="menus.buttons[menus.curActiveIndex].url"
+                            auto-complete="off" placeholder="请输入链接"></el-input>
+                          <el-button type="primary" link :icon="Plus" @click="linkInsertClick('button')"
+                            class="mr-6">插入链接</el-button>
+                        </el-form-item>
+                      </div>
+                      <div v-if="menus?.buttons[menus?.curActiveIndex]?.type == 'view'">
+                        <el-form-item label="网页链接">
+                          <el-input style="width:300px;" v-model="menus.buttons[menus.curActiveIndex].url"
+                            auto-complete="off" placeholder="请输入公众号文章链接"></el-input>
+                        </el-form-item>
+                      </div>
+                      <div v-if="menus?.buttons[menus?.curActiveIndex]?.type == 'click'">
+                        <el-form-item label="文本内容">
+                          <el-input style="width:300px;" type="textarea"
+                            v-model="menus.buttons[menus.curActiveIndex].content" auto-complete="off"
+                            placeholder="请输入文本内容"></el-input>
+                        </el-form-item>
+                      </div>
+                    </div>
+                  </el-form>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div v-else>
+            <p class="no-init">点击左侧菜单进行编辑操作</p>
+          </div>
+        </div>
+      </div>
+      <div class="flex justify-start m-9">
+        <el-button size="default" @click="cancel">取消</el-button>
+        <el-button type="primary" @click="submitFormChange" v-if="!props.primary?.look">确认</el-button>
+      </div>
+    </div>
+  </div>
+  <Dialog v-model="linkVisible" title="选择链接" destroy-on-close>
+    <generateLink @success="linkSuccess" />
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { Plus, ArrowLeft, User } from '@element-plus/icons-vue';
+import generateLink from './generateLink.vue'
+import { wechatPlatformMenuDetail, wechatPlatformMenuAdd, wechatPlatformMenuEdit } from '@/api/officialAccount/publicCustomMenu/index'
+import { validation } from './validation'
+import Cache from '@/support/cache';
+const props = defineProps({
+  primary: Object | null,
+  type: String
+});
+const limitButtons = ref(3)
+const linkVisible = ref(false)
+const formCallback = ref({})
+const menus = ref<object>({
+  curActiveIndex: null,
+  curSubActiveIndex: null,
+  clickSubMenu: false,
+  init: false,
+  buttons: [],
+})
+const emit = defineEmits(['close', 'success']);
+const isShowChild = (item: object) => {
+  return item.showChildMenu
+}
+const linkInsertClick = (e: any) => {
+  linkVisible.value = true
+}
+const nameChange = (e: any) => {
+  if (menus.value.clickSubMenu) {
+    menus.value.buttons[menus.value.curActiveIndex].sub_button[menus.value.curSubActiveIndex].key = e
+  } else {
+    menus.value.buttons[menus.value.curActiveIndex].url = e
+  }
+}
+const typeChange = (e: any) => {
+  if (menus.value.clickSubMenu) {
+    menus.value.buttons[menus.value.curActiveIndex].sub_button[menus.value.curSubActiveIndex].url = ''
+  } else {
+    menus.value.buttons[menus.value.curActiveIndex].url = ''
+  }
+}
+const linkSuccess = (e: any) => {
+  linkVisible.value = false;
+  if (menus.value.clickSubMenu) {
+    menus.value.buttons[menus.value.curActiveIndex].sub_button[menus.value.curSubActiveIndex].url = e.data.url
+  } else {
+    menus.value.buttons[menus.value.curActiveIndex].url = e.data.url
+  }
+  console.log(e, menus.value, ' menus.value.');
+}
+const cancel = () => {
+  emit('close')
+}
+const submitFormChange = () => {
+  console.log(menus.value, 'menusmenus', validation(menus.value.buttons));
+  if (!formCallback.value?.title) return ElMessage.error('模板名称不能为空')
+  if (!validation(menus.value.buttons)) return
+  let params = {
+    title: formCallback.value?.title,
+    type: props.type,
+    miniprogram_id: formCallback.value?.miniprogram_id,
+    content: menus.value.buttons?.map((item) => {
+      const { showChildMenu, hasChild, ...rest } = item;
+      return rest;
+    })
+  }
+  if (props.primary.id) {
+    wechatPlatformMenuEdit(props.primary.id, params).then(res => {
+      console.log(res);
+      ElMessage.success(res.message)
+      emit('close')
+    })
+    emit('success', params)
+  } else {
+    wechatPlatformMenuAdd(params).then(res => {
+      console.log(res);
+      ElMessage.success(res.message)
+      emit('close')
+    })
+    emit('success', params)
+  }
+  console.log(params, 'paramsparams');
+
+}
+
+const hasChildComputed = 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: "miniprogram",
+    url: "",
+    key: "菜单名称",
+    content: "",
+    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: "",
+    key: "子菜单名称",
+    content: "",
+    type: "miniprogram",
+  };
+  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;
+}
+
+const detailInit = (id: number | string) => {
+  wechatPlatformMenuDetail(id).then(res => {
+    formCallback.value = res.data
+    menus.value.buttons = res.data.content
+    console.log(res.data, 'menus.value', menus.value);
+  })
+}
+
+if (props.primary) {
+  console.log(props.primary, 'props.primary');
+  detailInit(props.primary.id)
+}
+
+onMounted(() => {
+  if (JSON.parse(Cache.get('nav_data'))?.app.id) {
+    formCallback.value.miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+  }
+  menus.value = {
+    curActiveIndex: 0,
+    curSubActiveIndex: null,
+    clickSubMenu: false,
+    init: true,
+    buttons:
+      [
+        { name: "首页", hasChild: false, showChildMenu: true, content: '', type: "miniprogram", url: "", key: "首页", sub_button: [], },
+        {
+          name: "我的", hasChild: false, showChildMenu: false, content: '', type: "miniprogram", url: "", key: "我的", sub_button: [],
+        },
+        {
+          name: "菜单名称3", hasChild: true, showChildMenu: false, content: '', type: "miniprogram", url: "", key: "菜单名称3",
+          sub_button:
+            [
+              { key: "子菜单名称", type: "miniprogram", name: "子菜单名称", content: '', url: "" }
+            ]
+        }
+      ]
+  }
+}
+)
+</script>
+
+<style lang="scss" scoped>
+.withdraw-popup-warn {
+  font-size: 14px;
+  font-weight: 600;
+  color: #666;
+  margin-bottom: 12px;
+}
+
+.wrapper {
+  width: 100%;
+  height: 100%;
+  background-color: #FFFFFF;
+  display: flex;
+  flex-direction: column;
+}
+
+.menu-container {
+  display: flex;
+  justify-content: flex-start;
+  align-items: flex-start;
+}
+
+.device-wrap {
+  width: 350px;
+  margin-left: 20px;
+  margin-top: 20px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  img {
+    width: 100%;
+    height: 100%;
+    border-bottom-left-radius: 40px;
+    border-bottom-right-radius: 40px;
+  }
+}
+
+.edit-wrap {
+  min-width: 668px;
+  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;
+    }
+  }
+}
+
+.menu-name {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.children-menu-items {
+  font-size: 12px;
+  position: absolute;
+  background: #f8f8f8;
+  border-radius: 3px;
+  left: 0%;
+  bottom: 120%;
+  width: 100%;
+  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>

+ 119 - 0
src/views/officialAccount/publicCustomMenu/form/generateLink.vue

@@ -0,0 +1,119 @@
+<template>
+  <el-tabs>
+    <el-tab-pane label="视频链接">
+      <el-form :model="formCallback" label-width="120px" ref="form" v-loading="loading" class="pr-4">
+        <el-form-item label="短剧" prop="video_id" :rules="[{ required: true, message: '请选择短剧' }]">
+          <el-select v-model="formCallback.video_id" class="w-full" clearable filterable remote
+            :remote-method="remoteMethod" placeholder="请输入短剧">
+            <el-option v-for="item in videoList" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="剧集" prop="sequence" v-if="formCallback.video_id"
+          :rules="[{ required: true, message: '请选择剧集' }]">
+          <el-select v-model="formCallback.sequence" class="w-full" clearable filterable placeholder="请选择剧集">
+            <el-option v-for="item in videoEpisodeList" :key="item.id" :label="item.series_name"
+              :value="item.series_sequence" />
+          </el-select>
+        </el-form-item>
+        <div class="flex justify-end">
+          <el-button type="primary" @click="submitForm(form)">{{
+            $t('system.confirm')
+          }}</el-button>
+        </div>
+      </el-form>
+    </el-tab-pane>
+  </el-tabs>
+</template>
+
+<script lang="ts" setup>
+import { Plus, InfoFilled } from '@element-plus/icons-vue';
+import { FormInstance } from 'element-plus';
+import { videoStockVideoList, videoStockEpisodeList } from '@/api/video/index'
+import { wechatPlatformCommonGetPlayUrl } from '@/api/officialAccount/keywordReply/index'
+const props = defineProps({
+  primary: Object | null,
+});
+const emit = defineEmits(['close', 'success']);
+const form = ref<FormInstance>()
+const loading = ref(false)
+const formCallback = ref({})
+const videoList = ref([])
+const videoEpisodeList = ref([])
+
+const initVideoList = (params?: object) => {
+  videoStockVideoList({ limit: 999, ...params }).then(res => {
+    console.log(res);
+    videoList.value = res.data
+  })
+}
+watch(() => formCallback.value.video_id, (newValue, oldValue) => {
+  initVideoStockEpisodeList({ video_id: newValue })
+})
+
+const initVideoStockEpisodeList = (params: object) => {
+  videoStockEpisodeList({ is_fee: 1, ...params }).then(res => {
+    console.log(res, 'videoEpisodeList');
+    videoEpisodeList.value = res.data;
+  })
+}
+const remoteMethod = (query: string) => {
+  if (query) {
+    initVideoList({ videoName: query })
+  } else {
+    initVideoList()
+  }
+}
+
+// 提交回传配置
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  loading.value = true;
+  formEl
+    .validate(valid => {
+      if (valid) {
+        wechatPlatformCommonGetPlayUrl(formCallback.value).then(res => {
+          // console.log(res, 'wechatPlatformCommonGetPlayUrl');
+          emit('success', res)
+          loading.value = false;
+        }).catch(e => {
+          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>

+ 37 - 0
src/views/officialAccount/publicCustomMenu/form/validation.ts

@@ -0,0 +1,37 @@
+export const validation = (buttons: Array<object>) => {
+  try {
+    buttons.forEach(button => {
+      if (button.sub_button.length <= 0 && !button.name) {
+        throw new Error('菜单名称不能为空');
+      }
+      if (button.sub_button.length <= 0 && !button.url) {
+        throw new Error('链接不能为空');
+      }
+      if (
+        button.sub_button.length <= 0 &&
+        button.type == 'click' &&
+        !button.content
+      ) {
+        throw new Error('文本内容不能为空');
+      }
+      if (button.sub_button && button.sub_button.length > 0) {
+        button.sub_button.forEach(subButton => {
+          if (!subButton.name) {
+            throw new Error('子菜单名称不能为空');
+          }
+          if (!subButton.url) {
+            throw new Error('子菜单链接不能为空');
+          }
+          if (subButton.type == 'click' && !subButton.content) {
+            throw new Error('子菜单文本内容不能为空');
+          }
+        });
+      }
+    });
+    return true; // 如果没有抛出异常,表示验证通过
+  } catch (error) {
+    ElMessage.error(error.message);
+    console.error(error.message);
+    return false;
+  }
+};

+ 160 - 428
src/views/officialAccount/publicCustomMenu/index.vue

@@ -1,459 +1,191 @@
 <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 class="flex flex-col justify-between w-full sm:flex-row">
+    <div class="w-full" v-if="!createVisible">
+      <Search :search="search" :reset="resetQuery">
+        <template v-slot:body>
+          <el-form-item label="模板名称">
+            <el-input placeholder="请输入模板名称" v-model="query.keyword" clearable></el-input>
+          </el-form-item>
+          <el-form-item label="公众号">
+            <el-select v-model="query.wechat_authorization_info_id" filterable remote clearable
+              :remote-method="(query) => { remoteMethod(query, 'wechatPlatformOfficialAccountList') }"
+              placeholder="选择公众号">
+              <el-option v-for="item in officialAccountsList" :key="item.id" :label="item.nick_name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </template>
+        <template v-slot:content>
+          <div class="table-default">
+            <div class="mt-3">
+              <el-alert title="注:一个公众号对应一套菜单配置,如果重复配置,原内容将被覆盖,以最新配置为准" type="warning" show-icon :closable="false" />
             </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 class="pt-5 pl-2">
+              <el-button type="primary" v-action="'wechatPlatform.WechatKeywords.add'" size="default"
+                @click="openType('createVisible', null)">新增</el-button>
             </div>
+            <el-table :data="tableData" class="mt-3" v-loading="loading">
+              <el-table-column prop="title" label="模板名称" min-width="200"></el-table-column>
+              <el-table-column prop="nick_name" label="配置公众号" min-width="200">
+                <template #default="scope">
+                  <div class="flex flex-col">
+                    <div v-for="item in scope.row.wechat_accounts" :key="item.id">
+                      {{ item.nick_name }}
+                    </div>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column prop="created_at" label="创建时间" min-width="200"></el-table-column>
+              <el-table-column prop="type" label="系统类型" min-width="200">
+                <template #default="scope">
+                  <span>{{ scope.row.type }}</span>
+                </template>
+              </el-table-column>
+
+              <el-table-column label="操作" width="200" fixed="right">
+                <template #default="scope">
+                  <el-button link type="primary" v-action="'wechatPlatform.WechatKeywords.edit'" size="small"
+                    @click="openType('createVisible', scope.row)">编辑</el-button>
+                  <br />
+                  <el-button link type="primary" v-action="'wechatPlatform.WechatKeywords.detail'" size="small"
+                    @click="openType('createVisible', { look: true, ...scope.row })">查看</el-button>
+                  <br />
+                  <el-button link type="primary" size="small" v-action="'wechatPlatform.WechatKeywords.del'"
+                    @click="deleteChange(scope.row)">删除</el-button>
+                  <br />
+                  <el-button link type="primary" size="small" v-action="'wechatPlatform.WechatKeywords.allocation'"
+                    @click="openType('configPublicVisible', scope.row)">配置公众号</el-button>
+                  <br />
+                </template>
+              </el-table-column>
+            </el-table>
+            <Paginate />
           </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>
+        </template>
+      </Search>
+      <Dialog v-model="configPublicVisible" title="配置公众号" destroy-on-close>
+        <configPublic @close="closeType('configPublicVisible')" typeName="公众号菜单" :primary="current" />
+      </Dialog>
     </div>
+    <Create v-else @close="closeType('createVisible')" :primary="current" />
   </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;
+import { InfoFilled } from '@element-plus/icons-vue';
+import { checkPermission } from '@/directives/permission';
+import Create from './form/create.vue';
+import configPublic from '@/views/officialAccount/components/configPublic.vue'
+import { useGetList } from '@/hook/curd/useGetList';
+import {
+  wechatPlatformKeywordGetConfig,
+  wechatPlatformKeywordSetConfig,
+  wechatPlatformKeywordDel
+} from '@/api/officialAccount/keywordReply/index'
+import { wechatPlatformOfficialAccountList } from '@/api/officialAccount/officialList/index'
+const api = 'wechatPlatform/menu/list';
+import Cache from '@/support/cache';
+const createVisible = ref(false)
+const configPublicVisible = ref(false)
+const current = ref<object | null>({})
+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.currentwechat.nick_name
+        } else if (current.value.look) {
+          createTitle.value = "查看"
+        }
+      } else {
+        createTitle.value = '新增'
+      }
+      break;
+    case 'configPublicVisible':
+      configPublicVisible.value = true
+      break;
   }
-  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 resetQuery = () => {
+  query.value = Object.assign({ page: query.value.page, limit: query.value.limit, miniprogram_id: query.value.miniprogram_id });
+  search()
 }
-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 closeType = (type: string) => {
+  switch (type) {
+    case 'createVisible':
+      createVisible.value = false
+      break;
+    case 'configPublicVisible':
+      configPublicVisible.value = false
+      break;
+  }
+  search()
 }
-const delMenus = (isSubMenu: boolean) => {
-  // * 删除菜单或者子菜单内容
-  let curActiveIndex = menus.value.curActiveIndex;
-  let curSubActiveIndex = menus.value.curSubActiveIndex;
+
+const deleteChange = (row: object) => {
   ElMessageBox.confirm(
-    isSubMenu
-      ? "删除后“子菜单名称”菜单下设置的内容将被删除"
-      : "删除后“菜单名称”菜单下设置的内容将被删除",
-    "温馨提示",
+    '确定要删除吗?',
+    '提示',
     {
-      confirmButtonText: "确定",
-      cancelButtonText: "取消",
-      type: "warning",
+      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();
-      }
+      wechatPlatformKeywordDel({ ids: row.id }).then(res => {
+        console.log(res);
+        ElMessage.success(res.message)
+        search()
+      })
     })
     .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;
-    }
+const remoteMethod = (query: string, type: string,) => {
+  console.log(query, 'queryquery', type);
+  switch (type) {
+    case 'wechatPlatformOfficialAccountList':
+      initRemoteOption('wechatPlatformOfficialAccountList', { nick_name: query })
+      break;
   }
 }
 
-.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;
-      }
-    }
+const initRemoteOption = (type: string, params?: object) => {
+  switch (type) {
+    case 'wechatPlatformOfficialAccountList':
+      wechatPlatformOfficialAccountList({ limit: 99, ...params }).then(res => {
+        officialAccountsList.value = res.data
+      })
+      break;
   }
 }
-
-.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;
+onMounted(() => {
+  if (JSON.parse(Cache.get('nav_data'))?.app.id) {
+    const miniprogram_id = JSON.parse(Cache.get('nav_data'))?.app.id
+    query.value.miniprogram_id = miniprogram_id
   }
-}
-
-.menu-save {
-  text-align: center;
-  margin-top: 20px;
-  padding-bottom: 20px;
-}
-
-.choose-mode {
-  position: absolute;
-  right: 20px;
-  top: 50%;
-  transform: translateY(-50%);
+  initRemoteOption('wechatPlatformOfficialAccountList')
+  search();
+});
+</script>
 
-  span {
-    color: #39a4ff;
-  }
+<style lang="scss" scoped>
+.withdraw-popup-warn {
+  font-size: 14px;
+  font-weight: 600;
+  color: #666;
 }
 </style>

+ 1 - 1
vite.config.js

@@ -76,7 +76,7 @@ export default defineConfig(({ command, mode }) => {
             enabledCollections: ['ep'] //@iconify-json/ep 是 Element Plus 的图标库
           }),
           // 自动导入 Element Plus 组件//dev环境会特别慢加上,build的时候可以放开
-          // ElementPlusResolver()
+          ElementPlusResolver()
         ],
         dirs: ['src/components/', 'src/layout/'],