10.10综合交易所原始源码_移动端
1
admin
2026-01-06 03043192ddf00f9a36b7454799a9152cd1b50a0b
src/views/customerService/index.vue
@@ -1,63 +1,94 @@
<template>
   <div class="service-box flex flex-col pb-40">
      <iframe height="100%" src="https://cdn.chat20gm.cfd/chat_online/index?channelId=1958101585091772416"></iframe>
  <div class="service-box flex flex-col" :class="homesStore.kefu_url ? '' : 'pb-40'">
    <div>
      <van-nav-bar ref="navEl" :title="$t('onLineService') + ' Jason David'" left-arrow @click-left="onClickLeft"
        fixed />
      <div class="px-3.5 py-5" v-if="state == 0">
        <div class="white">{{ $t('OrdersWill') }} <span style="color: #1194F7">{{ dayjs.duration(remainingTime,
          'seconds').format('mm:ss') }}</span> {{
              $t('afterCancel')
          }}</div>
        <div class="mt-3">
          <span class="mr-1" style="color: #8A919E">{{ $t('lumpSum') }}</span>
          <span class="white">{{ payInfo.currency }} {{ payInfo.amount }}</span>
        </div>
        <div class="mt-5">
          <van-button class="w-full" type="primary" @click="router.back()">{{ $t('toPay') }}</van-button>
        </div>
      </div>
    </div>
    <div class="flex-1" v-if="homesStore.kefu_url">
      <iframe :src="generateExtranetLink()" width="100%" height="100%" frameborder="0"></iframe>
    </div>
    <div class="localKefu flex-1 flex" v-else>
      <div class="flex flex-col px-10 box-border" ref="boxScrollEl" style="overflow:auto;">
        <div class="w-full py-4 text-grey text-center pt-30 text-30" @click="onMore"
          :style="{ 'display': finished ? 'none' : 'block' }">
          {{ $t('historyMessage') }}
        </div>
        <ul class="flex flex-col pt-3">
          <li v-for="(item, index) in list" :key="index" class="flex flex-col my-3">
            <!-- <p class="text-center py-2 text-grey text-30" v-if="showTime(index)">{{
              item.createtime &&
              item.createtime.split(' ')[0]
            }}</p> -->
            <template v-if="item.delete_status != -1">
              <p class="text-center pb-3 text-grey text-30">{{
                item.createtime
                }}</p>
              <div class="flex" :class="item.send_receive === 'send' ? 'justify-end' : ''">
                <template v-if="item.send_receive === 'receive'">
                  <img src="@/assets/image/service/responser.png" class="w-20 h-20 mr-5" />
                  <div class="responser px-12 py-8 text-30 left-chatBg">
                    <p class="break-word textColor  text-30" style="max-width: 200px;"
                      v-if="item.content_type === 'text' || item.type === 'text'">
                      {{ item.content }}</p>
                    <img v-else :src="item.content" class="w-200 h-200" @click="onPreview(item.content)" />
                  </div>
                </template>
                <div class="py-8 px-12 rounded-md flex flex-col right-chatBg" v-else>
                  <img :src="`${item.content}`" class="w-200 h-200"
                    v-if="item.content_type === 'img' || item.type === 'img'" @click="onPreview(item.content)" />
                  <p class="break-word text-white  text-30" v-else style="max-width: 200px;">{{ item.content }}</p>
                </div>
              </div>
            </template>
          </li>
        </ul>
      </div>
      <div
        class="bottom bottomBox flex justify-between items-center w-full fixed bottom-0 borderTop px-4 box-border bgBottom">
        <van-uploader :max-size="10000 * 1024" @oversize="onOversize" :after-read="afterRead">
          <img src="@/assets/image/service/photo2.png" class="w-12 h-12" />
        </van-uploader>
        <input type="text" v-model="message" :placeholder="$t('entryYouMessage')"
          class="flex-1 mx-3 h-full border-none textColor chatBg" maxlength="500" />
        <img src="@/assets/image/service/send2.png" class="w-12 h-12" @click="throttleSend(message)" />
      </div>
    </div>
   </div>
</template>
<script setup>
   import {
      Uploader,
      showImagePreview
   } from 'vant'
   import {
      _getMsg,
      _getUnreadMsg,
      _sendMsg
   } from '@/service/im.api'
   import {
      _uploadImage
   } from '@/service/upload.api'
   import {
      ref,
      onMounted,
      onUnmounted,
      onBeforeMount
   } from "vue";
   import {
      useI18n
   } from "vue-i18n";
   import {
      throttle
   } from '@/utils/index'
   import {
      closeToast,
      showToast,
      showLoadingToast
   } from "vant";
   import {
      useRoute,
      useRouter
   } from 'vue-router';
   import {
      getc2cOrder
   } from "@/service/recharge.api.js";
   import {
      useHomesStore
   } from "@/store/homes.store";
   import {
      SET_KEFU
   } from "@/store/types.store";
   import {
      useUserStore
   } from '@/store/user';
import { Uploader, showImagePreview } from 'vant'
import { _getMsg, _getUnreadMsg, _sendMsg } from '@/service/im.api'
import { _uploadImage } from '@/service/upload.api'
import { ref, onMounted, onUnmounted, onBeforeMount, nextTick } from "vue";
import { useI18n } from "vue-i18n";
import { throttle } from '@/utils/index'
import { closeToast, showToast, showLoadingToast } from "vant";
import { useRoute, useRouter } from 'vue-router';
import { getc2cOrder } from "@/service/recharge.api.js";
import { useHomesStore } from "@/store/homes.store";
import { SET_KEFU } from "@/store/types.store";
import { useUserStore } from '@/store/user';
   import dayjs from 'dayjs';
   import duration from 'dayjs/plugin/duration';
   dayjs.extend(duration);
   const userStore = useUserStore()
   const {
      t
   } = useI18n()
const { t } = useI18n()
   const homesStore = useHomesStore();
   const router = useRouter()
   const route = useRoute()
@@ -79,39 +110,49 @@
   let state = ref(null)
   let orderNo = ""
   let partyId = ""
   onMounted(() => {
      // 美洽客服
      // const _ll="&id="+_id.value+"&name="+_name.value
// onMounted(() => {
//   // 美洽客服
//   // const _ll="&id="+_id.value+"&name="+_name.value
      // const _ll=""
      // console.log(_ll)
//   // console.log(_ll)
      // const _uull='https://cdn.chat20gm.cfd/chat_online/index?channelId=1958101585091772416'+_ll;
      // window.location.href=_uull
//   // window.location.href=_uull
//   // 绑定滚动事件监听器,使用 nextTick 确保 DOM 渲染完成
//   nextTick(() => {
//     if (boxScrollEl.value) {
//       boxScrollEl.value.addEventListener('scroll', handleScroll)
//       // 初始化时检查是否在底部
//       setTimeout(() => {
//         handleScroll()
//       }, 100)
//     }
//   })
// })
onMounted(() => {
  orderNo = ""
  partyId = ""
  navHeight.value = navEl.value.$el.getBoundingClientRect().height
  if (route.query.order_no) {
    getc2cOrderDetails(route.query.order_no, (data) => {
      console.log("getc2cOrderDetails = " + JSON.stringify(data))
      orderNo = data.orderNo;
      partyId = data.partyId;
      fetchList()
   })
   // onMounted_bak(() => {
   //   orderNo = ""
   //   partyId = ""
   //   navHeight.value = navEl.value.$el.getBoundingClientRect().height
   //   if (route.query.order_no) {
   //     getc2cOrderDetails(route.query.order_no, (data) => {
   //       console.log("getc2cOrderDetails = " + JSON.stringify(data))
   //       orderNo = data.orderNo;
   //       partyId = data.partyId;
   //       fetchList()
   //     })
   //   } else {
   //     if (!homesStore.kefu_url) {
   //       fetchList()
   //     }
   //   }
   //   setInterval(() => {
   //     getCountdown()
   //   }, 1000)
   //   const model = navigator.userAgent;
   //   // 判断是否是安卓手机,是则是true
   //   androidAttrs.value = model.indexOf('Android') > -1 || model.indexOf('Linux') > -1
   //   window.addEventListener('scroll', handleScroll, true)
   // })
  } else {
    if (!homesStore.kefu_url) {
      fetchList()
    }
  }
  setInterval(() => {
    getCountdown()
  }, 1000)
  const model = navigator.userAgent;
  // 判断是否是安卓手机,是则是true
  androidAttrs.value = model.indexOf('Android') > -1 || model.indexOf('Linux') > -1
  window.addEventListener('scroll', handleScroll, true)
})
   onBeforeMount(() => {
      homesStore[SET_KEFU]()
@@ -119,15 +160,11 @@
   //获取订单详情
   const getc2cOrderDetails = (orderNo, call) => {
      getc2cOrder({
         order_no: orderNo
      }).then((res) => {
  getc2cOrder({ order_no: orderNo }).then((res) => {
         payInfo.value = res
         state.value = payInfo.value.state
         remainingTime.value = res.autoCancelTimeRemain
         if (call) {
            call(res)
         }
    if (call) { call(res) }
      })
   }
@@ -135,10 +172,7 @@
   const generateExtranetLink = () => {
      let extranetLink = ''
      if (userStore.userInfo && userStore.userInfo.usercode) {
         const userData = encodeURIComponent(JSON.stringify({
            name: userStore.userInfo.usercode,
            comment: ''
         }))
    const userData = encodeURIComponent(JSON.stringify({ name: userStore.userInfo.usercode, comment: '' }))
         let params = `&clientid=${userStore.userInfo.usercode}&metadata=${userData}`;
         extranetLink = homesStore.kefu_url + params;
      } else {
@@ -172,9 +206,7 @@
      }
   }
   const afterRead = (file) => { // 文件上传
      showLoadingToast({
         duration: 0
      })
  showLoadingToast({ duration: 0 })
      _uploadImage(file, (percent) => {
         console.log(percent)
      }).then(data => {
@@ -187,9 +219,15 @@
   const fetchList = async (message_id = '') => { // 获取消息列表
      console.log("orderNo = " + orderNo);
      console.log("partyId = " + partyId);
      _getMsg({
         message_id
      }, orderNo, partyId).then(data => { //
  // 保存更新前的滚动位置和滚动高度
  let previousScrollTop = 0
  let previousScrollHeight = 0
  if (boxScrollEl.value && !message_id) {
    previousScrollTop = boxScrollEl.value.scrollTop
    previousScrollHeight = boxScrollEl.value.scrollHeight
  }
  _getMsg({ message_id }, orderNo, partyId).then(data => { //
         if (!lastMsgId.value) {
            lastMsgId.value = data.length && data[data.length - 1]['id']
         }
@@ -214,11 +252,29 @@
            }
      // 使用 nextTick 确保 DOM 更新完成后再操作滚动
      setTimeout(() => {
        if (boxScrollEl.value) {
          if (message_id) {
            // 加载更多时,保持滚动位置(通过计算新增内容的高度)
            const newScrollHeight = boxScrollEl.value.scrollHeight
            const heightDiff = newScrollHeight - previousScrollHeight
            boxScrollEl.value.scrollTop = previousScrollTop + heightDiff
          } else {
            // 轮询新消息时,只有用户在底部才滚动到底部
            if (isScrollBottom.value) {
               boxScrollEl.value.scrollTop = boxScrollEl.value.scrollHeight - boxScrollEl.value
                  .offsetHeight
              boxScrollEl.value.scrollTop = boxScrollEl.value.scrollHeight - boxScrollEl.value.offsetHeight
            } else {
              // 如果用户不在底部,保持当前滚动位置(通过计算新增内容的高度)
              const newScrollHeight = boxScrollEl.value.scrollHeight
              const heightDiff = newScrollHeight - previousScrollHeight
              boxScrollEl.value.scrollTop = previousScrollTop + heightDiff
            }
            currentScrollTop.value = boxScrollEl.value.scrollTop;
          }
          currentScrollTop.value = boxScrollEl.value.scrollTop
        }
      }, 0)
            if (data.length < 10) {
               finished.value = true
            }
@@ -234,11 +290,11 @@
   const handleScroll = () => {
      if (boxScrollEl.value) {
         if (boxScrollEl.value.scrollTop === currentScrollTop.value) {
            isScrollBottom.value = true
         } else {
            isScrollBottom.value = false
         }
    const { scrollTop, scrollHeight, offsetHeight } = boxScrollEl.value
    // 判断是否在底部(允许5px的误差)
    const isAtBottom = scrollHeight - scrollTop - offsetHeight <= 5
    isScrollBottom.value = isAtBottom
    currentScrollTop.value = scrollTop
      }
   }
@@ -268,7 +324,7 @@
         message.value = ''
         isScrollBottom.value = true
         // document.getElementById('bottom').click()
         // await fetchList()
    await fetchList()
      })
   }
   const getCountdown = () => {
@@ -281,7 +337,12 @@
   onUnmounted(() => {
      clearIntervalTimer()
  // 移除滚动事件监听器
  if (boxScrollEl.value) {
    boxScrollEl.value.removeEventListener('scroll', handleScroll)
  }
   })
</script>
<style lang="scss" scoped>
   .service-box {