@dongkboy 2 лет назад
Родитель
Сommit
9b70fe16e1

BIN
src/assets/customerService/Frame.png


BIN
src/assets/customerService/Frame1.png


BIN
src/assets/customerService/Frame2.png


BIN
src/assets/customerService/customerService.png


BIN
src/assets/customerService/customerService1.png


+ 83 - 0
src/components/customerService/Loading.vue

@@ -0,0 +1,83 @@
+<template>
+  <div>
+    <div class="loader">
+      <li class="ball"></li>
+      <li class="ball"></li>
+      <li class="ball"></li>
+    </div>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script>
+export default {
+  // 数据存放区域
+  data() {
+    return {
+    }
+  },
+  // 生命周期函数
+  created() {
+  },
+  // 方法函数
+  methods: {
+  },
+  // 计算属性
+  computed: {
+  }
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+/* From Uiverse.io by JkHuger */
+/* From Uiverse.io by G4b413l */
+/* From Uiverse.io by ashish-yadv */
+.loader {
+  width: 60px;
+  display: flex;
+  justify-content: center;
+}
+
+.ball {
+  list-style: none;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background-color: #fff;
+  margin-top:10px;
+  margin:0 4px;
+}
+
+.ball:nth-child(1) {
+  animation: bounce-1 1.5s ease-in-out infinite;
+}
+
+@keyframes bounce-1 {
+  50% {
+    transform: translateY(-10px);
+    background-color: #0052FF;
+  }
+}
+
+.ball:nth-child(2) {
+  animation: bounce-3 1.5s ease-in-out 0.3s infinite;
+}
+
+@keyframes bounce-2 {
+  50% {
+    transform: translateY(-10px);
+    background-color: #0052FF;
+  }
+}
+
+.ball:nth-child(3) {
+  animation: bounce-3 1.5s ease-in-out 0.6s infinite;
+}
+
+@keyframes bounce-3 {
+  50% {
+    transform: translateY(-10px);
+    background-color: #0052FF;
+  }
+}
+</style>

+ 178 - 0
src/components/customerService/floatingBar.vue

@@ -0,0 +1,178 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <!-- 模板内容 -->
+    <div class="floatingBar flex f-c  a-c j-s">
+      <div class="aiSupport flex f-c a-c " @click="aiSupportClick">
+        <img src="../../assets/customerService/customerService.png" alt="">
+        <span>客服</span>
+      </div>
+      <el-popover placement="right-end" width="30" trigger="hover">
+        <div class="flex f-c a-c">
+          <div class="qrCode flex j-c a-c">
+            <div id="qrcode" ref="qrcode"></div>
+          </div>
+          <span style="font-size: 14px;color: #333;margin-top: 5px;">微信扫码咨询</span>
+        </div>
+        <div class="humanAgent flex f-c j-c a-c" slot="reference" @click="humanAgentClick">
+          <img src="../../assets/customerService/Frame.png" alt="">
+          <span>掌柜</span>
+          <span>客服</span>
+        </div>
+      </el-popover>
+    </div>
+    <modalDialog ref="modalDialog"></modalDialog>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script>
+import QRCode from "qrcodejs2";
+import modalDialog from "@/components/customerService/modalDialog.vue";
+
+export default {
+  // 数据存放区域
+  components: {
+    modalDialog,
+  },
+  data() {
+    return {
+      qrcode: null,
+      qrcodeWidth: "130",
+      qrcodeHeight: "130",
+    }
+  },
+  // 生命周期函数
+  created() {
+    this.wechat();
+  },
+  // 方法函数
+  methods: {
+
+    aiSupportClick() {
+      this.$refs.modalDialog.setBackupVisible(true);
+    },
+    wechat() {
+      this.$api.aiSupport.wechatpicture().then((res) => {
+        if (res.code == '200') {
+          this.$nextTick(() => {
+            this.creatQrCode(res.data.wechatPictureUrl);
+          });
+        } else {
+          this.$nextTick(() => {
+            this.creatQrCode("https://www.baidu.com/s?ie=UTF-8&wd=baoidu");
+          });
+        }
+      });
+    },
+    //二维码生成插件
+    creatQrCode(text) {
+      // 查找并删除旧的二维码DOM元素
+      let element = document.getElementById("qrcode");
+      while (element.firstChild) {
+        element.removeChild(element.firstChild);
+      }
+      // 创建新的QRCode实例
+      this.qrcode = new QRCode("qrcode", {
+        text: text,
+        width: this.qrcodeWidth,
+        height: this.qrcodeHeight,
+        colorDark: "#000000",
+        colorLight: "#ffffff",
+        correctLevel: QRCode.CorrectLevel.H,
+      });
+    },
+  },
+  // 计算属性
+  computed: {
+  }
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+/deep/ {
+  .el-popover {
+    padding: 5px;
+  }
+}
+#qrCode {
+  display: inline-block;
+  margin: 0 auto;
+  /*水平居中*/
+  position: relative;
+  top: 15%;
+}
+
+#qrCode img {
+  width: 130px;
+  height: 130px;
+  background-color: #fff;
+  padding: 6px;
+}
+.floatingBar {
+  width: auto;
+  position: fixed;
+  bottom: 10%;
+  right: 0;
+  z-index: 99;
+  background: #FFFFFF;
+  box-shadow: 6px 6px 12px 0px rgba(0, 70, 128, 0.08);
+  border-radius: 4px 0px 0px 4px;
+  border: 1px solid #F1F3F6;
+  padding: 8px 6px;
+  box-sizing: border-box;
+
+  .aiSupport {
+    cursor: pointer;
+    background: #FFFFFF;
+    border-radius: 4px 4px 4px 4px;
+    padding: 4px 2px;
+    margin-bottom: 10px;
+
+    span {
+      color: #202D45;
+      font-size: 12px;
+      line-height: 14px;
+      margin-top: 4px;
+    }
+
+    img {
+      width: 32px;
+      height: 44px;
+    }
+  }
+
+  .aiSupport:hover {
+    background: #F0F6FF;
+  }
+
+  .humanAgent {
+    cursor: pointer;
+    background: #fff;
+    border-radius: 4px 4px 4px 4px;
+    padding: 4px 2px;
+
+    span {
+      color: #202D45;
+      font-size: 12px;
+      line-height: 14px;
+      margin-top: 4px;
+    }
+
+    img {
+      width: 32px;
+      height: 26px;
+    }
+  }
+
+  .humanAgent:hover {
+    background: #F0F6FF;
+  }
+}
+
+.qrCode {
+  width: 140px;
+  height: 140px;
+  border: 1px solid #EAEDF1;
+}
+</style>

+ 278 - 0
src/components/customerService/modalDialog.vue

@@ -0,0 +1,278 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <!-- 模板内容 -->
+    <el-drawer title="掌柜在线客服" :visible.sync="drawer" :direction="direction" :show-close="true" :modal="false" :size="440"
+      :wrapperClosable="false">
+      <div class="drawer">
+        <div class="scroll-view" ref="scrollView">
+          <div id="okk">
+            <div class="flex f-c j-c" v-for="(item, index) in msgList" :key="index">
+              <div class="flex f-c " v-if="!item.my">
+                <div class="flex j-c a-c">
+                  <span class="initTime flex j-c a-c">{{ item.initTime }}</span>
+                </div>
+                <div style="max-width: 350px;" class="flex">
+                  <div class="conversationText flex j-s a-c">
+                    <img v-if="index == 0" src="../../assets/customerService/customerService1.png" alt="">
+                    <span :style="{ marginLeft: index === 0 ? '60px' : '10px' }">{{ item.msg }}</span>
+                  </div>
+                </div>
+              </div>
+              <div class="flex f-c a-end" v-if="item.my">
+                <div class="conversationText1 flex j-s a-c">
+                  <span>{{ item.msg }}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="flex f-c " v-if="msgLoad" style="width:65px;">
+            <div class="conversationText1loding flex j-c a-end">
+              <isLoading />
+            </div>
+          </div>
+        </div>
+        <div class="outerLayer">
+          <div class="textInput flex j-s a-c">
+            <el-input v-model="msg" class="Input" placeholder="请描述您的问题" @focus="Inputfocus" @blur="InputBlur"
+              @change="InputChange"></el-input>
+            <img v-if="InputfocusShow" src="../../assets/customerService/Frame1.png" alt="">
+            <img v-else src="../../assets/customerService/Frame2.png" alt="">
+          </div>
+        </div>
+      </div>
+    </el-drawer>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script>
+import isLoading from "@/components/customerService/Loading.vue";
+
+export default {
+  components: {
+    isLoading,
+  },
+  // 数据存放区域
+  data() {
+    return {
+      InputfocusShow: false,
+      drawer: false,
+      direction: 'rtl',
+      msgLoad: false,
+      msg: "",
+      msgList: [{
+        my: false,
+        msg: "您好,掌柜智能客服助理为您服务!",
+        initTime: this.getFormattedTime(),
+      },
+      ],
+    }
+  },
+  // 生命周期函数
+  created() {
+
+  },
+  // 方法函数
+  methods: {
+    InputChange() {
+      if (this.msg == "") {
+        return 0;
+      }
+      this.msgList.push({
+        "msg": this.msg,
+        "my": true
+      })
+
+      // 保证消息可见
+      this.msgGo()
+      // 回答问题
+      this.msgKf(this.msg)
+      // 清除消息
+      this.msg = ""
+    },
+    msgGo() {
+      // 选择消息体元素
+      // const scrollView = document.querySelector('.scroll-view'); // 获取第一个类名为 'arr' 的元素
+      const content = document.getElementById('okk');
+      if (!content) return; // 确保元素存在
+      // 获取当前视口高度
+      // 这里直接获取消息体高度
+      this.$nextTick(() => {
+          const scrollView = this.$refs.scrollView;
+          scrollView.scrollTop = scrollView.scrollHeight;
+      });
+    },
+    msgKf(msg) {
+      // loading
+      this.msgLoad = true
+      // 这里连接服务器获取答案
+      this.$api.aiSupport.AIsendMessage({
+        message: msg
+      }).then((res) => {
+        if (res.code == 200) {
+          setTimeout(() => {
+            // 取消loading
+            this.msgLoad = false
+            this.msgList.push({
+              my: false,
+              msg: res.data.message,
+              initTime: res.data.date
+            })
+            this.msgGo()
+          }, 1000)
+        }
+      });
+    },
+    Inputfocus() {
+      this.InputfocusShow = true;
+    },
+    InputBlur() {
+      this.InputfocusShow = false;
+
+    },
+    getFormattedTime() {
+      const date = new Date();
+      const year = date.getFullYear();
+      const month = (date.getMonth() + 1).toString().padStart(2, '0');
+      const day = date.getDate().toString().padStart(2, '0');
+      const hours = date.getHours().toString().padStart(2, '0');
+      const minutes = date.getMinutes().toString().padStart(2, '0');
+
+      return `${year}-${month}-${day} ${hours}:${minutes}`;
+    },
+    setBackupVisible: function (backupVisible) {
+      this.drawer = backupVisible
+    },
+  },
+  // 计算属性
+  computed: {
+  }
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+/deep/ {
+  .el-drawer {
+    height: 600px;
+    border-radius: 8px;
+    position: fixed;
+    bottom: 8%;
+    right: 5%;
+    top: initial;
+    background: linear-gradient(90deg, #FFFFFF 0%, #EDF3FE 100%);
+  }
+
+  .el-drawer__header {
+    color: #333;
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 16px;
+  }
+
+  .el-drawer__close-btn {
+    color: #979AA0;
+  }
+
+  .el-input__inner {
+    height: 48px;
+    border: none;
+  }
+}
+
+.drawer {
+
+  .scroll-view {
+    padding: 20px;
+    overflow-y: auto;
+    /* 允许垂直滚动 */
+    height: calc(600px - 113px);
+    box-sizing: border-box;
+    padding-bottom: 50px;
+    /* 适应消息区和底部导航栏 */
+    cursor: pointer;
+
+    /* 防止内容溢出 */
+    .initTime {
+      background: rgba(207, 207, 207, 0.2);
+      border-radius: 20px 20px 20px 20px;
+      padding: 4px 16px;
+      font-size: 12px;
+      color: rgba(0, 0, 0, 0.85);
+      margin-bottom: 15px;
+    }
+
+    .conversationText {
+      background: #FFFFFF;
+      box-shadow: 0px 4px 10px 0px #DAE3F4;
+      border-radius: 0px 4px 4px 4px;
+      margin-bottom: 18px;
+      position: relative;
+      padding: 8px;
+
+      span {
+
+        font-size: 14px;
+        color: #000000;
+        word-break: break-all;
+      }
+
+      img {
+        width: 58px;
+        height: 58px;
+        position: absolute;
+        bottom: 0;
+        left: 0;
+      }
+    }
+
+    .conversationText1 {
+      background: #0052FF;
+      box-shadow: 0px 4px 10px 0px #DAE3F4;
+      border-radius: 4px;
+      padding: 8px;
+      margin-bottom: 18px;
+
+      span {
+        color: #FFFFFF;
+        font-size: 14px;
+      }
+    }
+
+    .conversationText1loding {
+      box-shadow: 0px 4px 10px 0px #DAE3F4;
+      border-radius: 4px;
+      height: 36px;
+      padding: 4px 8px;
+      box-sizing: border-box;
+
+    }
+  }
+
+  .outerLayer {
+    width: 100%;
+    height: auto;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    background: linear-gradient(90deg, #FFFFFF 0%, #EDF3FE 100%);
+    padding: 10px 20px;
+    box-sizing: border-box;
+
+    .textInput {
+      width: 100%;
+      height: 48px;
+      background: #FFFFFF;
+      box-shadow: 6px 6px 12px 0px rgba(83, 98, 153, 0.08);
+      border-radius: 4px 4px 4px 4px;
+
+      img {
+        width: 28px;
+        height: 28px;
+        margin-right: 20px;
+      }
+    }
+  }
+
+}
+</style>

+ 3 - 0
src/http/api.js

@@ -50,6 +50,8 @@ import * as expense from './moudules/expense.js'
 import * as report from './moudules/report.js'
 import * as one from './moudules/service/one'
 import * as sys from './moudules/sys.js'
+//客服
+import * as aiSupport from './moudules/service/aiSupport'
 
 import * as bindUser from './moudules/bindUser.js'
 
@@ -62,6 +64,7 @@ export default {
     menu,
     dict,
     log,
+    aiSupport,
     settlement,
     /////////////////////////////
     esmTeam,

+ 18 - 0
src/http/moudules/service/aiSupport.js

@@ -0,0 +1,18 @@
+import axios from '../../axios'
+
+// AI客服发送消息
+export const AIsendMessage= (data) => {
+    return axios({
+        url: "/customer/ai/sendMessage",
+        method: "post",
+        data
+    })
+}
+// 获取企业微信二维码
+export const wechatpicture= () => {
+  return axios({
+      url: "/sys/qy/wechat/find/picture",
+      method: "get",
+
+  })
+}

+ 4 - 0
src/views/Task/daikeComponents/KtLetter.vue

@@ -3171,6 +3171,7 @@
         <el-button size="small" type="primary" @click="uploadinformation">上传资料</el-button>
       </span>
     </el-dialog>
+    <floatingBar/>
   </div>
 </template>
 <script>
@@ -3192,6 +3193,8 @@ import quotationDialog from "@/views/Core/components/quotationDialog.vue";
 import quotationDialogPro from "@/views/Core/components/quotationDialogPro.vue";
 import detailsDialog from "@/views/Core/components/detailsDialog.vue";
 import imageDialog from "@/views/Core/components/imageDialog.vue";
+import floatingBar from '@/components/customerService/floatingBar.vue';//表单组件
+
 import { mapState } from "vuex";
 export default {
   inject: ["reload"],
@@ -4769,6 +4772,7 @@ export default {
     quotationDialog,
     quotationDialogPro,
     imageDialog,
+    floatingBar,
     "el-image-viewer": () =>
       import("element-ui/packages/image/src/image-viewer"),
   },