Selaa lähdekoodia

个微相关提醒

吴树波 3 viikkoa sitten
vanhempi
commit
2287140bc0

+ 34 - 0
src/api/wechat.js

@@ -16,3 +16,37 @@ export function checkWechatBindStatus(sceneId) {
     params: { sceneId }
   });
 }
+
+// 生成公众号订阅通知二维码
+export function generateSubscribeQrcode(userId, tenantCode) {
+  return request({
+    url: '/wxmp/subscribe/' + tenantCode + '/qrcode/' + userId,
+    method: 'get'
+  });
+}
+
+// 查询员工订阅状态
+export function getSubscribeStatus(userId, tenantCode) {
+  return request({
+    url: '/wxmp/subscribe/' + tenantCode + '/status/' + userId,
+    method: 'get'
+  });
+}
+
+// 确认订阅通知
+export function confirmSubscribe(userId, tenantCode) {
+  return request({
+    url: '/wxmp/subscribe/' + tenantCode + '/confirm',
+    method: 'get',
+    params: { userId }
+  });
+}
+
+// 推送公众号订阅消息
+export function pushSubscribeMessage(data, tenantCode) {
+  return request({
+    url: '/wxmp/subscribe/' + tenantCode + '/push',
+    method: 'post',
+    data: data
+  });
+}

+ 103 - 2
src/views/company/companyConfig/index.vue

@@ -17,7 +17,6 @@
                   placeholder="请输入生成条数"
                 ></el-input-number>
               </el-form-item>
-
               <el-form-item label="开始位置" prop="startIndex">
                 <el-input-number
                   v-model="cidConfig.startIndex"
@@ -305,6 +304,48 @@
             </div>
           </el-form>
         </el-tab-pane>
+        <el-tab-pane label="订阅配置" name="wxMpSubscribeConfig">
+          <el-form ref="wxMpSubscribeConfig" :model="wxMpSubscribeConfig" label-width="120px">
+            <el-form-item label="公众号AppID">
+              <el-input v-model="wxMpSubscribeConfig.appId" style="width:400px" placeholder="请输入公众号AppID"></el-input>
+            </el-form-item>
+            <el-form-item label="公众号Secret">
+              <el-input v-model="wxMpSubscribeConfig.secret" style="width:400px" placeholder="请输入公众号Secret"></el-input>
+            </el-form-item>
+            <el-form-item label="Token">
+              <el-input v-model="wxMpSubscribeConfig.token" style="width:400px" placeholder="请输入Token"></el-input>
+            </el-form-item>
+            <el-form-item label="EncodingAESKey">
+              <el-input v-model="wxMpSubscribeConfig.aesKey" style="width:400px" placeholder="请输入EncodingAESKey"></el-input>
+            </el-form-item>
+            <el-divider content-position="left">订阅消息模板</el-divider>
+            <div style="margin-bottom: 12px;">
+              <el-button type="primary" size="small" icon="el-icon-plus" @click="addSubscribeTemplateId">新增模板ID</el-button>
+              <span class="tip-text" style="margin-left: 10px; color: #909399; font-size: 12px;">在公众号后台「订阅消息」中添加模板后,将模板ID填写于此</span>
+            </div>
+            <div v-for="(item, index) in wxMpSubscribeConfig.subscribeTemplateIds" :key="index" style="display: flex; align-items: center; margin-bottom: 10px;">
+              <el-input v-model="wxMpSubscribeConfig.subscribeTemplateIds[index]" style="width:400px" placeholder="请输入模板ID,如:xxxxxxxxxxxxxxx"></el-input>
+              <el-button type="text" icon="el-icon-delete" style="color: #F56C6C; margin-left: 10px;" @click="removeSubscribeTemplateId(index)"></el-button>
+            </div>
+            <el-divider content-position="left">推送消息模板(用于发送)</el-divider>
+            <div style="margin-bottom: 12px;">
+              <el-button type="primary" size="small" icon="el-icon-plus" @click="addPushTemplate">新增推送模板</el-button>
+              <span class="tip-text" style="margin-left: 10px; color: #909399; font-size: 12px;">配置推送消息使用的模板,类型决定推送时使用哪个模板</span>
+            </div>
+            <div v-for="(item, index) in wxMpSubscribeConfig.pushTemplates" :key="'push-'+index" style="display: flex; align-items: center; margin-bottom: 10px;">
+              <el-input v-model="item.templateId" style="width:240px" placeholder="模板ID"></el-input>
+              <el-select v-model="item.type" style="width:150px; margin-left: 10px;" placeholder="类型">
+                <el-option label="个微AI转人工" value="个微AI转人工"></el-option>
+              </el-select>
+              <el-input v-model="item.title" style="width:150px; margin-left: 10px;" placeholder="模板标题"></el-input>
+              <el-button type="text" icon="el-icon-delete" style="color: #F56C6C; margin-left: 10px;" @click="removePushTemplate(index)"></el-button>
+            </div>
+            <div class="line"></div>
+            <div style="float:right;margin-right:20px">
+              <el-button type="primary" @click="onSubmitWxMpSubscribe">提交</el-button>
+            </div>
+          </el-form>
+        </el-tab-pane>
         <el-tab-pane label="配置销售会员审核" name="companyUserConfig">
           <el-form ref="companyUserConfig" label-width="140px">
             <el-form-item label="会员是否小黑屋"><!--会员是否默认黑名单-->
@@ -442,7 +483,16 @@ export default {
       redPacketConfig:{},
 
       redPacketConfigForm:{},
-      cidConfigForm:{}
+      cidConfigForm:{},
+      wxMpSubscribeConfig: {
+        appId: '',
+        secret: '',
+        token: '',
+        aesKey: '',
+        subscribeTemplateIds: [],
+        pushTemplates: []
+      },
+      wxMpSubscribeConfigForm:{}
     };
   },
   created() {
@@ -455,6 +505,7 @@ export default {
     this.getConfigKey("company:admin:show");
     this.getConfigKey("redPacket:config");
     this.getConfigKey("cId.config");
+    this.getConfigKey("wx:mp:subscribe:config");
     this.getDicts("sys_company_status").then((response) => {
       this.statusOptions = response.data;
     });
@@ -615,6 +666,21 @@ export default {
                   ...parsed
                 };
               }
+            }else if(key == "wx:mp:subscribe:config"){
+              this.wxMpSubscribeConfigForm = response.data;
+              if(response.data.configValue != null){
+                const parsed = JSON.parse(response.data.configValue);
+                this.wxMpSubscribeConfig = {
+                  appId: '',
+                  secret: '',
+                  token: '',
+                  aesKey: '',
+                  subscribeTemplateIds: [],
+                  pushTemplates: [],
+                  ...parsed,
+                  pushTemplates: parsed.pushTemplates || []
+                };
+              }
             }
         });
     },
@@ -760,6 +826,41 @@ export default {
       const map = { 30: '30分钟', 60: '1小时', 120: '2小时', 360: '6小时', 1440: '天' };
       return map[minutes] || (minutes + '分钟');
     },
+    onSubmitWxMpSubscribe() {
+      if (!this.wxMpSubscribeConfig.appId) {
+        this.msgError('请输入公众号AppID');
+        return;
+      }
+      if (!this.wxMpSubscribeConfig.secret) {
+        this.msgError('请输入公众号Secret');
+        return;
+      }
+      this.wxMpSubscribeConfigForm.configValue = JSON.stringify(this.wxMpSubscribeConfig);
+      updateConfig(this.wxMpSubscribeConfigForm).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+          this.getConfigKey("wx:mp:subscribe:config");
+        }
+      });
+    },
+    addSubscribeTemplateId() {
+      if (!this.wxMpSubscribeConfig.subscribeTemplateIds) {
+        this.$set(this.wxMpSubscribeConfig, 'subscribeTemplateIds', []);
+      }
+      this.wxMpSubscribeConfig.subscribeTemplateIds.push('');
+    },
+    removeSubscribeTemplateId(index) {
+      this.wxMpSubscribeConfig.subscribeTemplateIds.splice(index, 1);
+    },
+    addPushTemplate() {
+      if (!this.wxMpSubscribeConfig.pushTemplates) {
+        this.$set(this.wxMpSubscribeConfig, 'pushTemplates', []);
+      }
+      this.wxMpSubscribeConfig.pushTemplates.push({ templateId: '', type: '个微AI转人工', title: '' });
+    },
+    removePushTemplate(index) {
+      this.wxMpSubscribeConfig.pushTemplates.splice(index, 1);
+    },
   }
 };
 </script>

+ 194 - 0
src/views/company/companyUser/index.vue

@@ -315,6 +315,8 @@
               <el-button v-if="scope.row.userType !== '00'" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['company:user:edit']">修改</el-button>
               <el-button v-if="scope.row.userType !== '00'" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['company:user:remove']">删除</el-button>
               <el-button size="mini" type="text" icon="el-icon-key" @click="handleResetPwd(scope.row)" v-hasPermi="['company:user:resetPwd']">重置密码</el-button>
+              <el-button size="mini" type="text" icon="el-icon-bell" @click="handleWxMpSubscribe(scope.row)">通知订阅</el-button>
+              <el-button size="mini" type="text" icon="el-icon-s-promotion" @click="handleWxMpPush(scope.row)">推送消息</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -856,6 +858,50 @@
         </div>
       </div>
     </el-drawer>
+
+    <!-- 通知订阅二维码弹窗 -->
+    <el-dialog title="微信通知订阅" :visible.sync="subscribeDialogVisible" width="400px" :before-close="handleSubscribeDialogClose" center>
+      <div v-if="subscribeLoading" style="text-align: center; padding: 20px;">
+        <i class="el-icon-loading" style="font-size: 32px;"></i>
+        <p>正在生成二维码...</p>
+      </div>
+      <div v-else-if="subscribeQrCodeUrl" style="text-align: center;">
+        <p style="margin-bottom: 10px; color: #606266;">请员工使用微信扫描以下二维码</p>
+        <p style="margin-bottom: 10px; font-size: 12px; color: #909399;">扫描后关注公众号,即可绑定通知服务</p>
+        <img :src="subscribeQrCodeUrl" style="width: 260px; height: 260px; border: 1px solid #ebeef5; border-radius: 4px;" />
+        <div style="margin-top: 15px;">
+          <el-tag v-if="subscribeStatus && subscribeStatus.bound" type="success">已绑定公众号</el-tag>
+          <el-tag v-else-if="subscribeStatus && !subscribeStatus.bound" type="info">等待扫码关注</el-tag>
+          <el-tag v-if="subscribeStatus && subscribeStatus.mpSubscribed === 1" type="success" style="margin-left: 5px;">已订阅通知</el-tag>
+        </div>
+      </div>
+      <div v-else style="text-align: center; padding: 20px; color: #f56c6c;">
+        <p>二维码生成失败,请重试</p>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button size="small" @click="handleSubscribeDialogClose">关 闭</el-button>
+        <el-button size="small" type="primary" @click="refreshSubscribeStatus" :loading="subscribeStatusLoading">刷新状态</el-button>
+      </span>
+    </el-dialog>
+
+    <!-- 推送订阅消息对话框 -->
+    <el-dialog title="推送订阅消息" :visible.sync="pushDialogVisible" width="400px" center>
+      <el-form ref="pushForm" :model="pushForm" :rules="pushRules" label-width="80px">
+        <el-form-item label="员工">
+          <span>{{ pushForm.nickName }}</span>
+        </el-form-item>
+        <el-form-item label="消息类型">
+          <el-tag size="small">个微AI转人工</el-tag>
+        </el-form-item>
+        <el-form-item label="客户姓名" prop="customerName">
+          <el-input v-model="pushForm.customerName" placeholder="请输入客户姓名" maxlength="20"></el-input>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button size="small" @click="pushDialogVisible = false">取 消</el-button>
+        <el-button size="small" type="primary" @click="submitPushMessage" :loading="pushLoading">推 送</el-button>
+      </span>
+    </el-dialog>
   </div>
 </template>
 
@@ -882,6 +928,8 @@ import {
   analyseCompanyUserInfo
 } from "@/api/company/companyUser";
 import { getToken } from "@/utils/auth";
+import { generateSubscribeQrcode, getSubscribeStatus, pushSubscribeMessage } from "@/api/wechat";
+import { getTenantCode } from "@/utils/auth";
 import { treeselect } from "@/api/company/companyDept";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
@@ -902,6 +950,19 @@ export default {
   components: {selectDoctor, Treeselect ,selectUser, AiSipCallUser, VoiceCollectDialog},
   data() {
     return {
+      // 微信通知订阅相关
+      subscribeDialogVisible: false,
+      subscribeLoading: false,
+      subscribeStatusLoading: false,
+      subscribeQrCodeUrl: null,
+      subscribeCurrentUserId: null,
+      subscribeStatus: null,
+      subscribeTimer: null,
+      // 推送订阅消息相关
+      pushDialogVisible: false,
+      pushLoading: false,
+      pushForm: { userId: null, nickName: '', customerName: '', type: '个微AI转人工' },
+      pushRules: { customerName: [{ required: true, message: '请输入客户姓名', trigger: 'blur' }] },
       doctor: {
         open: false,
         title: '绑定医生'
@@ -1505,6 +1566,139 @@ export default {
         .catch(() => {
         });
     },
+    /** 微信通知订阅 */
+    handleWxMpSubscribe(row) {
+      this.subscribeCurrentUserId = row.userId;
+      this.subscribeDialogVisible = true;
+      this.subscribeLoading = true;
+      this.subscribeQrCodeUrl = null;
+      this.subscribeStatus = null;
+      var tenantCode = getTenantCode();
+      // 先查询当前订阅状态
+      getSubscribeStatus(row.userId, tenantCode).then(res => {
+        if (res.code === 200) {
+          this.subscribeStatus = res.data;
+          if (res.data && res.data.bound && res.data.mpSubscribed === 1) {
+            this.subscribeLoading = false;
+            this.subscribeQrCodeUrl = null;
+            this.$message.success('该员工已订阅通知');
+            return;
+          }
+        }
+        // 未绑定或未订阅,生成二维码
+        this.loadSubscribeQrCode(row.userId, tenantCode);
+      }).catch(() => {
+        this.loadSubscribeQrCode(row.userId, tenantCode);
+      });
+    },
+    loadSubscribeQrCode(userId, tenantCode) {
+      this.subscribeLoading = true;
+      generateSubscribeQrcode(userId, tenantCode).then(res => {
+        if (res.code === 200) {
+          this.subscribeQrCodeUrl = res.data;
+          this.subscribeLoading = false;
+          // 开始轮询绑定状态
+          this.startSubscribePolling(userId, tenantCode);
+        } else {
+          this.$message.error('生成二维码失败:' + res.msg);
+          this.subscribeLoading = false;
+        }
+      }).catch(err => {
+        this.$message.error('生成二维码失败');
+        this.subscribeLoading = false;
+      });
+    },
+    startSubscribePolling(userId, tenantCode) {
+      // 清除旧的轮询
+      if (this.subscribeTimer) {
+        clearInterval(this.subscribeTimer);
+      }
+      // 每5秒轮询一次绑定状态
+      this.subscribeTimer = setInterval(() => {
+        getSubscribeStatus(userId, tenantCode).then(res => {
+          if (res.code === 200) {
+            this.subscribeStatus = res.data;
+            // 如果已绑定且已订阅,停止轮询
+            if (res.data && res.data.bound && res.data.mpSubscribed === 1) {
+              this.stopSubscribePolling();
+              this.$message.success('员工已成功订阅通知');
+            }
+          }
+        }).catch(() => {});
+      }, 5000);
+    },
+    stopSubscribePolling() {
+      if (this.subscribeTimer) {
+        clearInterval(this.subscribeTimer);
+        this.subscribeTimer = null;
+      }
+    },
+    refreshSubscribeStatus() {
+      if (!this.subscribeCurrentUserId) return;
+      this.subscribeStatusLoading = true;
+      var tenantCode = getTenantCode();
+      getSubscribeStatus(this.subscribeCurrentUserId, tenantCode).then(res => {
+        if (res.code === 200) {
+          this.subscribeStatus = res.data;
+          if (res.data && res.data.bound && res.data.mpSubscribed === 1) {
+            this.$message.success('员工已成功订阅通知');
+            this.stopSubscribePolling();
+          } else {
+            this.$message.info('当前状态:' + (res.data.bound ? '已绑定公众号' : '未绑定'));
+          }
+        }
+      }).catch(() => {
+        this.$message.error('查询状态失败');
+      }).finally(() => {
+        this.subscribeStatusLoading = false;
+      });
+    },
+    handleSubscribeDialogClose() {
+      this.stopSubscribePolling();
+      this.subscribeDialogVisible = false;
+      this.subscribeQrCodeUrl = null;
+      this.subscribeCurrentUserId = null;
+      this.subscribeStatus = null;
+    },
+    /** 推送订阅消息 */
+    handleWxMpPush(row) {
+      this.pushForm = {
+        userId: row.userId,
+        nickName: row.nickName || row.userName,
+        customerName: '',
+        type: '个微AI转人工'
+      };
+      this.pushDialogVisible = true;
+      this.$nextTick(() => {
+        if (this.$refs.pushForm) {
+          this.$refs.pushForm.clearValidate();
+        }
+      });
+    },
+    /** 提交推送消息 */
+    submitPushMessage() {
+      this.$refs.pushForm.validate(valid => {
+        if (!valid) return;
+        this.pushLoading = true;
+        var tenantCode = getTenantCode();
+        pushSubscribeMessage({
+          userId: this.pushForm.userId,
+          customerName: this.pushForm.customerName,
+          type: this.pushForm.type
+        }, tenantCode).then(res => {
+          if (res.code === 200) {
+            this.$message.success(res.msg || '推送成功');
+            this.pushDialogVisible = false;
+          } else {
+            this.$message.error(res.msg || '推送失败');
+          }
+        }).catch(() => {
+          this.$message.error('推送请求失败');
+        }).finally(() => {
+          this.pushLoading = false;
+        });
+      });
+    },
     /** 提交按钮 */
     submitForm: function() {