发送按钮样式优化和代码联调

master
wujingling0908 2 weeks ago
parent f22713668a
commit 3f88043322
  1. 312
      src/App.vue
  2. 1
      src/assets/images/chat/todo-icon.svg
  3. 8
      src/assets/style/common.css
  4. 641
      src/components/abstract.vue
  5. 379
      src/components/business-handle.vue
  6. 4777
      src/components/chat-content.vue
  7. 2
      src/components/chat-main.vue
  8. 1201
      src/components/chat-new.vue
  9. 11
      src/components/hot-question-new.vue
  10. 914
      src/components/text-content.vue
  11. 1145
      src/components/text-folding.vue
  12. 12
      src/components/welcomes.vue
  13. 71
      src/router/index.js
  14. 11
      src/store/app.js
  15. 1376
      src/utils/index.js
  16. 930
      src/utils/socket.js
  17. 2020
      src/utils/utils.js
  18. 1257
      src/views/chat.vue
  19. 10
      src/views/word.vue
  20. 8
      vite.config.js

@ -1,154 +1,162 @@
<script setup> <script setup>
import { RouterView, useRoute } from 'vue-router' import { RouterView, useRoute } from 'vue-router'
import { onMounted, onUnmounted, provide } from 'vue' import { onMounted, onUnmounted, provide } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useAppStore } from '@/store/app.js' import { useAppStore } from '@/store/app.js'
// import { leaveProject } from '@/utils/api.js' // import { leaveProject } from '@/utils/api.js'
import wx from 'weixin-js-sdk' import wx from 'weixin-js-sdk'
const route = useRoute() const route = useRoute()
import { isIOS, isAndroid, isPureHarmonyOS, handleFontSize, detectFontSize } from '@/utils/common' import { isIOS, isAndroid, isPureHarmonyOS, handleFontSize, detectFontSize } from '@/utils/common'
console.log('navigator.userAgent', navigator.userAgent, navigator.userAgentData) console.log('navigator.userAgent', navigator.userAgent, navigator.userAgentData)
window.wx = wx window.wx = wx
// 全局 provide 接线:子组件统一通过 inject('appStore') / inject('xxx') 使用 store // 全局 provide 接线:子组件统一通过 inject('appStore') / inject('xxx') 使用 store
const appStore = useAppStore() const appStore = useAppStore()
const appStoreRefs = storeToRefs(appStore) const appStoreRefs = storeToRefs(appStore)
provide('appStore', appStore) provide('appStore', appStore)
Object.keys(appStoreRefs).forEach((key) => { // Object.keys(appStoreRefs).forEach((key) => {
provide(key, appStoreRefs[key]) // provide(key, appStoreRefs[key])
}) // })
const handleGlobalBrowserExit = (reason, extra = {}) => { const handleGlobalBrowserExit = (reason, extra = {}) => {
const payload = { const payload = {
reason, reason,
href: window.location.href, href: window.location.href,
route: route.fullPath, route: route.fullPath,
timestamp: Date.now(), timestamp: Date.now(),
...extra, ...extra,
} }
// leaveProject() // leaveProject()
// alert('关闭页面') // alert('关闭页面')
console.log('📴 全局检测到浏览器关闭/返回动作', payload) console.log('📴 全局检测到浏览器关闭/返回动作', payload)
// 在这里统一处理关闭浏览器、手势返回、刷新离开等动作。 // 在这里统一处理关闭浏览器、手势返回、刷新离开等动作。
// 如果后续需要接业务方法,直接在这里调用即可。 // 如果后续需要接业务方法,直接在这里调用即可。
// example: // example:
// someBusinessMethod(payload) // someBusinessMethod(payload)
} }
const handleBrowserPopState = () => { const handleBrowserPopState = () => {
handleGlobalBrowserExit('popstate') handleGlobalBrowserExit('popstate')
} }
const handleBrowserPageHide = (event) => { const handleBrowserPageHide = (event) => {
handleGlobalBrowserExit('pagehide', { handleGlobalBrowserExit('pagehide', {
persisted: Boolean(event?.persisted), persisted: Boolean(event?.persisted),
}) })
} }
const handleBrowserBeforeUnload = () => { const handleBrowserBeforeUnload = () => {
handleGlobalBrowserExit('beforeunload') handleGlobalBrowserExit('beforeunload')
} }
onMounted(() => { onMounted(() => {
var html = document.getElementsByTagName('html')[0] var html = document.getElementsByTagName('html')[0]
// 初始化 // 初始化
function init() { function init() {
console.log('navigator.userAgent', navigator.userAgent, navigator.userAgentData) console.log('navigator.userAgent', navigator.userAgent, navigator.userAgentData)
console.log('handleFontSize called with:', localStorage.getItem('bigFont')) console.log('handleFontSize called with:', localStorage.getItem('bigFont'))
if (localStorage.getItem('bigFont') === 'true') if (localStorage.getItem('bigFont') === 'true')
document.getElementsByTagName('html')[0].style.fontSize = '0.79333275px' document.getElementsByTagName('html')[0].style.fontSize = '0.79333275px'
if (localStorage.getItem('localfontSize')) if (localStorage.getItem('localfontSize'))
html.style.fontSize = localStorage.getItem('localfontSize') html.style.fontSize = localStorage.getItem('localfontSize')
// 监听WeixinJSBridge // 监听WeixinJSBridge
if (typeof WeixinJSBridge == 'object' && typeof WeixinJSBridge.invoke == 'function') { if (typeof WeixinJSBridge == 'object' && typeof WeixinJSBridge.invoke == 'function') {
detectFontSize() detectFontSize()
handleFontSize() handleFontSize()
} else { } else {
if (document.addEventListener) { if (document.addEventListener) {
document.addEventListener( document.addEventListener(
'WeixinJSBridgeReady', 'WeixinJSBridgeReady',
function () { function () {
detectFontSize() detectFontSize()
handleFontSize() handleFontSize()
}, },
false false
) )
} else if (document.attachEvent) { } else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', function () { document.attachEvent('WeixinJSBridgeReady', function () {
detectFontSize() detectFontSize()
handleFontSize() handleFontSize()
}) })
} }
} }
} }
init() init()
console.log(isPureHarmonyOS(), 'isPureHarmonyOS') console.log(isPureHarmonyOS(), 'isPureHarmonyOS')
window.onresize = function () {
window.onresize = function () { if (!isIOS() && !isAndroid() && !isPureHarmonyOS()) init()
if (!isIOS() && !isAndroid() && !isPureHarmonyOS()) init() }
}
window.addEventListener('popstate', handleBrowserPopState)
window.addEventListener('popstate', handleBrowserPopState) window.addEventListener('pagehide', handleBrowserPageHide)
window.addEventListener('pagehide', handleBrowserPageHide) window.addEventListener('beforeunload', handleBrowserBeforeUnload)
window.addEventListener('beforeunload', handleBrowserBeforeUnload) })
})
onUnmounted(() => {
onUnmounted(() => { window.removeEventListener('popstate', handleBrowserPopState)
window.removeEventListener('popstate', handleBrowserPopState) window.removeEventListener('pagehide', handleBrowserPageHide)
window.removeEventListener('pagehide', handleBrowserPageHide) window.removeEventListener('beforeunload', handleBrowserBeforeUnload)
window.removeEventListener('beforeunload', handleBrowserBeforeUnload) })
}) </script>
</script>
<template>
<template> <div class="app-wrapper opacity">
<div id="app" class="app-wrapper opacity"> <RouterView v-slot="{ Component, route }">
<RouterView v-slot="{ Component }"> <keep-alive>
<keep-alive include="chat"> <component
<component :is="Component" /> :is="Component"
v-if="Component && route.meta.keepAlive"
:key="route.name"
/>
</keep-alive> </keep-alive>
<component
:is="Component"
v-if="Component && !route.meta.keepAlive"
:key="route.name"
/>
</RouterView> </RouterView>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.header-box { .header-box {
width: 100%; width: 100%;
height: 88rem; height: 88rem;
display: flex; display: flex;
align-items: center; align-items: center;
background: #e8f2ff; background: #e8f2ff;
padding: 0 24rem 0 54rem; padding: 0 24rem 0 54rem;
} }
.view { .view {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
} }
.left-box { .left-box {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.back-icon { .back-icon {
display: block; display: block;
width: 18rem; width: 18rem;
height: 32rem; height: 32rem;
} }
.back-icon img { .back-icon img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
</style> </style>
<style scoped lang="scss"> <style scoped lang="scss">
#app { #app {
.con { .con {
color: red; color: red;
} }
} }
</style> </style>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.4 KiB

@ -117,14 +117,6 @@ body .van-toast--text {
color: #0074e1; color: #0074e1;
} }
body .van-popup--bottom {
max-width: 9.98rem;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
}
/* .van-picker__toolbar{ /* .van-picker__toolbar{
position: relative; position: relative;
} }

@ -1,301 +1,340 @@
<script setup> <script setup>
import { ref, onMounted, computed, inject } from 'vue'; import { ref, onMounted, onActivated, computed, inject } from 'vue';
import { Post } from '@/utils/https' import { Post } from '@/utils/https'
import { messageSend } from '@/utils/api' import { messageSend } from '@/utils/api'
import { getHiddenProp, getVisibilityState, decryptByVersionNo1, isSit, Encrypt, isDev, isProd } from "@/utils"; import { getHiddenProp, getVisibilityState, decryptByVersionNo1, isSit, Encrypt, isDev, isProd } from "@/utils";
const store = inject('appStore', null) const store = inject('appStore', null)
const businessHandleInfo = ref({}) const businessHandleInfo = ref({})
const orderCount = ref(0) const orderCount = ref(0)
const abstractList = computed(() => [ const emit = defineEmits(['ready', 'businessClick'])
{ const abstractList = computed(() => [
title: '保障状态', {
num: orderCount.value, title: '保障状态',
text: '保单保障中', num: orderCount.value,
unit: '张', text: '保单保障中',
name: '查保单', unit: '张',
btnText: '查保单', name: '查保单',
btnBg: 'linear-gradient(90deg, #025cea 0%, #0595f6 100%)', btnText: '查保单',
}, btnBg: 'linear-gradient(90deg, #025cea 0%, #0595f6 100%)',
{ },
title: '待办任务', {
num: businessHandleInfo.value?.contents?.length || 0, title: '待办任务',
text: '待办需处理', num: businessHandleInfo.value?.contents?.length || 0,
unit: '个', text: '待办需处理',
name: '去处理', unit: '个',
btnText: '去处理', name: '去处理',
btnBg: 'linear-gradient(111deg, #ff752a 44%, #feb52d 136%)', btnText: '去处理',
}, btnBg: 'linear-gradient(111deg, #ff752a 44%, #feb52d 136%)',
{ },
title: '权益领取', {
num: 0, title: '权益领取',
text: '生存金权益', num: 0,
unit: '项', text: '生存金权益',
name: '去领取', unit: '项',
btnText: '去领取', name: '去领取',
btnBg: 'linear-gradient(261deg, #2ed0c6 3%, #08b7ac 65%)', btnText: '去领取',
}, btnBg: 'linear-gradient(261deg, #2ed0c6 3%, #08b7ac 65%)',
].filter(item => item.num > 0)) },
const businessHandle = () => { ].filter(item => item.num > 0))
if ( const businessHandle = (userId) => {
store.source == "95270000" || if (
store.source == "95320000" || store.source == "95270000" ||
store.source == "95350000" || store.source == "95320000" ||
store.source == "96060000" store.source == "95350000" ||
) { store.source == "96060000"
Post("notify/info", { userInfoId: store.userId }) ) {
.then((res) => { return Post("notify/info", { userInfoId: userId })
if (res.data?.contents.length > 0) { .then((res) => {
businessHandleInfo.value = res.data; if (res.data?.contents?.length > 0) {
} businessHandleInfo.value = res.data
}) } else {
.catch((error) => { businessHandleInfo.value = {}
console.log(error); }
}); store?.setBusinessHandleInfo?.(businessHandleInfo.value)
} })
} .catch((error) => {
const isIdentityNo = (value) => /^\d{15}$|^\d{17}[\dXx]$/.test(String(value || '').trim()) console.log(error);
});
// 与 liics 一致:明文身份证来自解密 userInfo.pid(encryptPid 是给外部系统用的密文,不能直接解) }
const getIdentityCardno = () => { return Promise.resolve()
const versionNo = store.userInfo?.versionNo1 || '' }
const pidCipher = store.userInfo?.pid || ''
if (!versionNo || !pidCipher) return '' const businessClick = () => {
emit('businessClick')
const identityCardno = decryptByVersionNo1(versionNo, pidCipher) }
console.log(decryptByVersionNo1(versionNo, '0FLzZwhD08nkUE4BjRGOSEri4KWO'),id); const isIdentityNo = (value) => /^\d{15}$|^\d{17}[\dXx]$/.test(String(value || '').trim())
return isIdentityNo(identityCardno) ? identityCardno.trim() : ''
} // 与 liics 一致:明文身份证来自解密 userInfo.pid(encryptPid 是给外部系统用的密文,不能直接解)
const getIdentityCardno = () => {
const getOrderCount = () => { const versionNo = store.userInfo?.versionNo1 || ''
try { const pidCipher = store.userInfo?.pid || ''
const identityCardno = getIdentityCardno() if (!versionNo || !pidCipher) return ''
if (!identityCardno) {
console.warn('证件号解密失败', { const identityCardno = decryptByVersionNo1(versionNo, pidCipher)
hasPid: Boolean(store.userInfo?.pid), console.log('decrypt pid =>', JSON.stringify(identityCardno))
hasVersionNo1: Boolean(store.userInfo?.versionNo1), return isIdentityNo(identityCardno) ? identityCardno.trim() : ''
}) }
return
} const getOrderCount = () => {
try {
Post('policy/queryCount', { const identityCardno = getIdentityCardno()
dentityCardno: identityCardno, if (!identityCardno) {
userId: store.userId console.warn('证件号解密失败', {
}).then(res => { hasPid: Boolean(store.userInfo?.pid),
orderCount.value = res?.data?.policyCount || 0 hasVersionNo1: Boolean(store.userInfo?.versionNo1),
}) })
} catch (error) { return Promise.resolve()
console.log(error) }
}
} return Post('policy/queryCount', {
const toDetail = (record) => { dentityCardno: identityCardno,
const text = record?.name userId: store.userId
console.log('text', text); }).then(res => {
if (!text) return orderCount.value = res?.data?.policyCount || 0
messageSend(text).catch((err) => { }).catch((error) => {
console.log(err) console.log(error)
}) })
} } catch (error) {
onMounted(async () => { console.log(error)
businessHandle() return Promise.resolve()
getOrderCount() }
}) }
</script> const toDetail = (record) => {
<template> if (record.title === '待办任务') {
<div class="abstract-con" v-if="abstractList.length"> businessClick()
<div class="title"> }
<span>今日皮卡丘摘要</span><img src="@/assets/images/chat/star.svg" alt="" srcset="" /> if (record.title === '保障状态') {
</div> // 只把问题写入对话气泡,不走对话接口
<div class="list"> store.addSendMessageList({ msgText: record.name, reqAndResType: '1', sendLoading: false })
<div :class="`item ${(abstractList || []).length === 1 ? 'singleItem' : ''}`" }
v-for="(item, index) in abstractList || []" :key="index"> }
<div> const refreshAbstractData = async () => {
<div class="title">{{ item.title }}</div> await Promise.all([businessHandle(store?.userId), getOrderCount()])
<div class="content" :style="{ display: (abstractList || []).length > 2 ? 'block' : 'flex' }"> }
<div class="nums">
<div class="num"> const hasLoadedOnce = ref(false)
{{ item.num }}<span>{{ item.unit }}</span>
</div> onMounted(async () => {
</div> try {
<div class="text" :style="{ marginTop: (abstractList || []).length > 2 ? '8rem' : '0' }"> await refreshAbstractData()
{{ item.text }} } finally {
</div> hasLoadedOnce.value = true
</div> emit('ready')
</div> }
<div class="btn" :style="{ background: item.btnBg }" @click="toDetail(item)">{{ item.btnText }}</div> })
</div>
</div> // keep-alive 返回 chat 时刷新待办和保单数量
</div> onActivated(async () => {
</template> if (!hasLoadedOnce.value) return
<style lang="scss" scoped> await refreshAbstractData()
.abstract-con { })
position: relative; </script>
z-index: 1; <template>
flex-direction: column; <div class="abstract-con" v-if="abstractList.length">
justify-content: center; <div class="title">
padding: 10rem 12rem; <span>今日皮卡丘摘要</span><img src="@/assets/images/chat/star.svg" alt="" srcset="" />
gap: 12rem; </div>
align-self: stretch; <div class="list">
margin-top: 10rem; <div
background: linear-gradient(77deg, #fcfdfd80 -1%, #f5f7fee6 99%); :class="`item ${(abstractList || []).length === 1 ? 'singleItem' : ''}`"
v-for="(item, index) in abstractList || []"
box-sizing: border-box; :key="index"
border: 1rem solid rgba(255, 255, 255, 0.9); >
<div>
backdrop-filter: blur(10rem); <div class="title">{{ item.title }}</div>
border-radius: 12rem; <div
class="content"
.title { :style="{ display: (abstractList || []).length > 2 ? 'block' : 'flex' }"
font-family: PingFang SC; >
font-size: 16rem; <div class="nums">
font-weight: 500; <div class="num">
line-height: normal; {{ item.num }}<span>{{ item.unit }}</span>
letter-spacing: 0rem; </div>
</div>
/* 科个中性/正文-标题 17204D */ <div
/* 样式描述:用于正文标题 */ class="text"
color: #17204d; :style="{ marginTop: (abstractList || []).length > 2 ? '8rem' : '0' }"
>
img { {{ item.text }}
width: 20rem; </div>
margin-left: 4.6rem; </div>
} </div>
} <div class="btn" :style="{ background: item.btnBg }" @click="toDetail(item)">
{{ item.btnText }}
.list { </div>
margin-top: 13rem; </div>
display: flex; </div>
gap: 8rem; </div>
</template>
.item { <style lang="scss" scoped>
display: flex; .abstract-con {
flex-direction: column; position: relative;
gap: 12rem; z-index: 1;
flex: 1; flex-direction: column;
border-radius: 8rem; justify-content: center;
display: flex; padding: 10rem 12rem;
flex-direction: column; gap: 12rem;
padding: 12rem; align-self: stretch;
gap: 12rem; background: linear-gradient(77deg, #fcfdfd80 -1%, #f5f7fee6 99%);
margin-bottom: 16rem;
background: #fcfdfd; box-sizing: border-box;
border: 1rem solid rgba(255, 255, 255, 0.9);
box-shadow: 0rem 4rem 10rem 0rem #e7effd;
backdrop-filter: blur(10rem);
.title { border-radius: 12rem;
margin-left: 18rem;
font-family: PingFang SC; .title {
font-size: 12rem; font-family: PingFang SC;
font-weight: normal; font-size: 16rem;
line-height: 16rem; font-weight: 500;
letter-spacing: 0rem; line-height: normal;
letter-spacing: 0rem;
/* 科个中性/正文-副标题 7E849F */
color: #7e849f; /* 科个中性/正文-标题 17204D */
} /* 样式描述:用于正文标题 */
color: #17204d;
.content {
.nums { img {
font-family: DIN-Medium; width: 20rem;
font-size: 24rem; margin-left: 4.6rem;
font-weight: bold; }
line-height: 24rem; }
letter-spacing: 0rem;
.list {
font-variation-settings: 'opsz' auto; margin-top: 13rem;
color: #17204d; display: flex;
gap: 8rem;
.num {
display: flex; .item {
align-items: flex-end; display: flex;
} flex-direction: column;
gap: 12rem;
span { flex: 1;
font-family: PingFang SC; border-radius: 8rem;
font-size: 12rem; display: flex;
font-weight: normal; flex-direction: column;
line-height: 16rem; padding: 12rem;
letter-spacing: 0rem; gap: 12rem;
margin: 0 3rem 0 3rem;
background: #fcfdfd;
/* 中性/重要文本色 */
/* 样式描述:17204D */ box-shadow: 0rem 4rem 10rem 0rem #e7effd;
color: #17204d;
} .title {
} margin-left: 18rem;
font-family: PingFang SC;
.text { font-size: 12rem;
font-family: PingFang SC; font-weight: normal;
font-size: 12rem; line-height: 16rem;
font-weight: normal; letter-spacing: 0rem;
line-height: 16rem;
letter-spacing: 0rem; /* 科个中性/正文-副标题 7E849F */
color: #7e849f;
/* 中性/次要文本色 */ }
/* 样式描述:7E849F */
color: #7e849f; .content {
align-self: flex-end; .nums {
} font-family: DIN-Medium;
} font-size: 24rem;
font-weight: bold;
.btn { line-height: 24rem;
border-radius: 100rem; letter-spacing: 0rem;
font-family: PingFang SC;
font-size: 14rem; font-variation-settings: 'opsz' auto;
font-weight: 500; color: #17204d;
line-height: 19rem;
text-align: center; .num {
letter-spacing: 0rem; display: flex;
align-items: flex-end;
color: #ffffff; }
padding: 4rem 12rem;
height: 28rem; span {
cursor: pointer; font-family: PingFang SC;
} font-size: 12rem;
} font-weight: normal;
line-height: 16rem;
.singleItem { letter-spacing: 0rem;
display: flex; margin: 0 3rem 0 3rem;
flex-direction: row;
justify-content: space-between; /* 中性/重要文本色 */
align-items: center; /* 样式描述:17204D */
color: #17204d;
>div { }
margin-left: 36rem; }
.title { .text {
font-family: PingFang SC; font-family: PingFang SC;
font-size: 14rem; font-size: 12rem;
font-weight: 500; font-weight: normal;
line-height: normal; line-height: 16rem;
letter-spacing: 0rem; letter-spacing: 0rem;
margin-left: 0;
/* 中性/次要文本色 */
/* 科个中性/正文-标题 17204D */ /* 样式描述:7E849F */
/* 样式描述:用于正文标题 */ color: #7e849f;
color: #17204d; align-self: flex-end;
margin-bottom: 4rem; }
} }
.content { .btn {
.nums { border-radius: 100rem;
font-size: 12rem; font-family: PingFang SC;
font-weight: normal; font-size: 14rem;
line-height: 16rem; font-weight: 500;
letter-spacing: 0rem; line-height: 19rem;
text-align: center;
z-index: 1; letter-spacing: 0rem;
font-family: PingFang SC;
font-weight: 500; color: #ffffff;
color: #17204d; padding: 4rem 12rem;
height: 28rem;
span { cursor: pointer;
color: #7e849f; }
margin-left: 0; }
}
} .singleItem {
} display: flex;
} flex-direction: row;
} justify-content: space-between;
} align-items: center;
}
</style> > div {
margin-left: 36rem;
.title {
font-family: PingFang SC;
font-size: 14rem;
font-weight: 500;
line-height: normal;
letter-spacing: 0rem;
margin-left: 0;
/* 科个中性/正文-标题 17204D */
/* 样式描述:用于正文标题 */
color: #17204d;
margin-bottom: 4rem;
}
.content {
.nums {
font-size: 12rem;
font-weight: normal;
line-height: 16rem;
letter-spacing: 0rem;
z-index: 1;
font-family: PingFang SC;
font-weight: 500;
color: #17204d;
span {
color: #7e849f;
margin-left: 0;
}
}
}
}
}
}
}
</style>

@ -1,168 +1,211 @@
<template> <template>
<div> <div>
<van-swipe style="height:30rem;margin-top:10rem" class="businessBox" vertical :scrollable="false" :autoplay="3000" <!-- <van-swipe style="height:30rem;margin-top:10rem" class="businessBox" vertical :scrollable="false" :autoplay="3000"
:show-indicators="false"> :show-indicators="false">
<van-swipe-item @click="swiperClick" v-for="(item, index) in businessHandleInfo.contents" :key="index"> <van-swipe-item @click="swiperClick" v-for="(item, index) in businessHandleInfo.contents" :key="index">
<div class="item"> <div class="item">
<img :src="getIconUrl(item.icon)" alt="" style="width:30rem;height:30rem;"> <img :src="getIconUrl(item.icon)" alt="" style="width:30rem;height:30rem;">
<div class="item-content"> <div class="item-content">
<div class="line">{{ item.name }}</div> <div class="line">{{ item.name }}</div>
<van-icon name="arrow-down" /> <van-icon name="arrow-down" />
</div> </div>
</div> </div>
</van-swipe-item> </van-swipe-item>
</van-swipe> </van-swipe> -->
<van-popup
<van-popup get-container="#app" v-model:show="Popupshow" round position="bottom" v-model:show="Popupshow"
:style="{ padding: '15rem', boxSizing: ' border-box' }"> teleport="body"
<div class="displayFlex"> round
<div style="font-size: 13rem;color: #878D99;"> position="bottom"
您有{{ businessHandleInfo.contents.length }}个待办{{ businessHandleInfo.tip }}</div> closeable
<van-icon name="close" color="#5A9CFF" @click="Popupshow = false" /> :z-index="3000"
</div> >
<div class="popupContent"> <div class="displayFlex">
<div class="item popupContentItem" v-for="(item, index) in businessHandleInfo.contents" :key="index" <div class="title">您有{{ displayInfo?.contents?.length || 0 }}个待办</div>
@click="richCallAnswer(item)"> <div class="popupContent">
<img :src="getIconUrl(item.icon)" alt="" style="width:30rem;height:30rem;"> <div
<div class="item-content"> class="item popupContentItem"
<div>{{ item.name }}</div> v-for="(item, index) in displayInfo.contents || []"
<van-icon name="arrow" color="#5A9CFF" /> :key="index"
</div> @click="richCallAnswer(item)"
</div> >
</div> <img
</van-popup> src="@/assets/images/chat/todo-icon.svg"
</div> alt=""
</template> style="width: 22rem; height: 22rem"
/>
<script setup> <div class="item-content">
import { ref, inject, computed } from 'vue' <div>{{ item.name }}</div>
import { Icon, Swipe, SwipeItem, Popup } from 'vant' <van-icon name="arrow" color="#5A9CFF" />
</div>
defineProps({ </div>
businessHandleInfo: { type: Object, default: () => ({}) } </div>
}) </div>
</van-popup>
const store = inject('appStore', null) </div>
const userId = inject('userId', ref('')) </template>
const mescrollContent = inject('mescrollContent', ref(null))
const manualService = inject('manualService', ref('1')) <script setup>
import { ref, inject, computed, watch } from 'vue'
const Popupshow = ref(false) import { Icon, Swipe, SwipeItem, Popup } from 'vant'
const isBtnsubmit = ref(true)
const props = defineProps({
function getIconUrl(icon) { businessHandleInfo: { type: Object, default: () => ({}) },
try { autoOpen: { type: Boolean, default: true },
return require(`../assets/images/${icon}.png`) })
} catch (e) {
return '' const store = inject('appStore', null)
} const userId = inject('userId', ref(''))
} const mescrollContent = inject('mescrollContent', ref(null))
const manualService = inject('manualService', ref('1'))
function swiperClick() {
Popupshow.value = true const Popupshow = ref(false)
} const isBtnsubmit = ref(true)
const hasAutoOpened = ref(false)
function richCallAnswer(item) {
const text = item.name const displayInfo = computed(() => {
const serialNumber = item.serialNumber const fromProp = props.businessHandleInfo
const sessionId = item.sessionId if (fromProp && Array.isArray(fromProp.contents) && fromProp.contents.length) return fromProp
return store?.businessHandleInfo || {}
const mdParmas = { })
userInfoId: userId.value,
sessionId, function getIconUrl(icon) {
serialNumber, try {
clickTime: new Date().getTime() return new URL(`../assets/images/${icon}.png`, import.meta.url).href
} } catch (e) {
store?.dispatch?.('updateClickTime', mdParmas).catch(err => { return ''
console.error(err) }
}) }
let sendParmas = { function swiperClick() {
cmd: 'business-handle', Popupshow.value = true
reqAndResType: '1', }
msgText: '&' + text + '&',
intentId: '', function richCallAnswer(item) {
msgSource: manualService.value, const text = item.name
questionType: 6 const serialNumber = item.serialNumber
} const sessionId = item.sessionId
if (isBtnsubmit.value) { const mdParmas = {
isBtnsubmit.value = false userInfoId: userId.value,
Popupshow.value = false sessionId,
scrollTopBody() serialNumber,
store?.dispatch?.('sendingRobot', sendParmas).then(() => { clickTime: new Date().getTime()
isBtnsubmit.value = true }
}).catch(err => { store?.dispatch?.('updateClickTime', mdParmas).catch(err => {
isBtnsubmit.value = true console.error(err)
console.log(err) })
})
} let sendParmas = {
} cmd: 'business-handle',
reqAndResType: '1',
function scrollTopBody() { msgText: '&' + text + '&',
setTimeout(() => { intentId: '',
try { msgSource: manualService.value,
if (mescrollContent.value) { questionType: 6
mescrollContent.value.scrollTop = mescrollContent.value.scrollHeight || 0 }
}
} catch (error) { if (isBtnsubmit.value) {
console.log(error.message) isBtnsubmit.value = false
} Popupshow.value = false
}, 300) scrollTopBody()
} const sending = store?.sendingRobot?.(sendParmas) || store?.dispatch?.('sendingRobot', sendParmas)
</script> Promise.resolve(sending).then(() => {
isBtnsubmit.value = true
<style scoped> }).catch(err => {
.businessBox { isBtnsubmit.value = true
background: #fff; console.log(err)
padding: 10rem; })
border-radius: 16rem 16rem 16rem 2rem; }
} }
.item { function scrollTopBody() {
display: flex; setTimeout(() => {
align-items: center; try {
justify-content: space-around; if (mescrollContent.value) {
} mescrollContent.value.scrollTop = mescrollContent.value.scrollHeight || 0
}
.item>img { } catch (error) {
width: 30rem; console.log(error.message)
height: 30rem; }
margin-right: 10rem; }, 300)
} }
defineExpose({
.item-content { swiperClick
flex: 1; })
display: flex; </script>
align-items: center;
justify-content: space-between; <style scoped lang="scss">
font-size: 15rem; .businessBox {
} background: #fff;
padding: 10rem;
.line { border-radius: 16rem 16rem 16rem 2rem;
font-size: 14rem; }
}
.item {
.displayFlex { display: flex;
display: flex; justify-content: space-around;
align-items: center; }
justify-content: space-between;
} .item > img {
width: 30rem;
.popupContent { height: 30rem;
min-height: 180rem; margin-right: 10rem;
max-height: 290rem; }
overflow: auto;
} .item-content {
flex: 1;
.popupContentItem { display: flex;
height: 50rem; justify-content: space-between;
background: #FFFFFF; font-size: 15rem;
border: 1rem solid #F1F3F6; i{
padding: 13rem; padding-top: 4rem;
box-shadow: 0 2rem 8rem 0 rgba(229, 234, 237, 0.50); }
border-radius: 8rem; }
box-sizing: border-box;
margin: 7rem 5rem; .line {
} font-size: 14rem;
</style> }
.displayFlex {
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 16rem 16rem 0rem 0rem;
display: flex;
flex-direction: column;
align-items: center;
padding: 16rem 16rem 0rem 16rem;
gap: 24rem;
width: 100vw;
background: linear-gradient(180deg, #f0f8ff 0%, #f6f9ff 100%);
.title {
font-family: PingFang SC;
font-size: 16rem;
font-weight: 500;
line-height: normal;
text-align: center;
letter-spacing: 0rem;
font-variation-settings: 'opsz' auto;
}
}
.popupContent {
display: flex;
flex-direction: column;
max-height: 300rem;
overflow: auto;
width: 100%;
gap: 12rem;
padding-bottom: 12rem;
}
.popupContentItem {
background: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.9);
padding: 12px;
gap: 12px;
border-radius: 12px;
}
</style>

File diff suppressed because it is too large Load Diff

@ -96,7 +96,7 @@
<script setup> <script setup>
// 原 Vue2 Options API 组件 chat-main(keep-alive 缓存名沿用 "chat") // 原 Vue2 Options API 组件 chat-main(keep-alive 缓存名沿用 "chat")
defineOptions({ name: 'chat' }) defineOptions({ name: 'chat-main' })
import { ref, computed, watch, onMounted, onUnmounted, onActivated, nextTick } from 'vue' import { ref, computed, watch, onMounted, onUnmounted, onActivated, nextTick } from 'vue'
import { onBeforeRouteLeave } from 'vue-router' import { onBeforeRouteLeave } from 'vue-router'

File diff suppressed because it is too large Load Diff

@ -14,7 +14,7 @@ const initHotQuestion = async () => {
sceneId: store.userInfo.sceneId || "", sceneId: store.userInfo.sceneId || "",
}; };
Post("config/activity", param) return Post("config/activity", param)
.then((res) => { .then((res) => {
if (res.code && res.code != 0) { if (res.code && res.code != 0) {
showToast(res.msg); showToast(res.msg);
@ -72,8 +72,13 @@ const handleAskClick = (record) => {
isItemsubmit.value = true isItemsubmit.value = true
}) })
} }
const emit = defineEmits(['ready'])
onMounted(async () => { onMounted(async () => {
await initHotQuestion() try {
await initHotQuestion()
} finally {
emit('ready')
}
}) })
</script> </script>
<template> <template>
@ -95,7 +100,7 @@ onMounted(async () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10rem; gap: 10rem;
margin-top: 16rem; margin-bottom: 16rem;
position: relative; position: relative;
z-index: 1; z-index: 1;

@ -1,457 +1,457 @@
<template> <template>
<div class="text-template-box" v-if="introduce"> <div class="text-template-box" v-if="introduce">
<div <div
class="question-answer-con" class="question-answer-con"
v-if="introduce" v-if="introduce"
ref="desc" ref="desc"
:class="showTotal || heightMax == 'autoHeight' ? 'total-introduce' : 'detailed-introduce'" :class="showTotal || heightMax == 'autoHeight' ? 'total-introduce' : 'detailed-introduce'"
> >
<p <p
:class="{ connectionArtificial: disabled }" :class="{ connectionArtificial: disabled }"
v-html="replacementRagEmoticon(introduce)" v-html="replacementRagEmoticon(introduce)"
:data-qaid="itemlist && itemlist.qaId ? itemlist.qaId : ''" :data-qaid="itemlist && itemlist.qaId ? itemlist.qaId : ''"
></p> ></p>
</div> </div>
<div class="content-question-answer" v-if="intents && intents.length > 0"> <div class="content-question-answer" v-if="intents && intents.length > 0">
<button <button
class="answer-items" class="answer-items"
v-for="(items, index) in intents" v-for="(items, index) in intents"
:key="index" :key="index"
:disabled="disabled ? true : false" :disabled="disabled ? true : false"
@click.stop="richCallAnswer(items)" @click.stop="richCallAnswer(items)"
> >
{{ items.intent }} {{ items.intent }}
</button> </button>
</div> </div>
<div <div
class="unfold-box" class="unfold-box"
:class="exchangeButton ? 'unfold-bg' : 'unfold-style'" :class="exchangeButton ? 'unfold-bg' : 'unfold-style'"
v-if="(!intents || intents.length == 0) && showExchangeButton" v-if="(!intents || intents.length == 0) && showExchangeButton"
> >
<a href="javascript:;" @click="showTotalIntro" <a href="javascript:;" @click="showTotalIntro"
>{{ exchangeButton ? '展开' : '收起' >{{ exchangeButton ? '展开' : '收起'
}}<em class="look-more-icon" :class="!exchangeButton ? 'active' : ''"></em }}<em class="look-more-icon" :class="!exchangeButton ? 'active' : ''"></em
></a> ></a>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, inject, onMounted, nextTick } from 'vue' import { ref, inject, onMounted, nextTick } from 'vue'
import { showToast, showLoadingToast, closeToast } from 'vant' import { showToast, showLoadingToast, closeToast } from 'vant'
import faceJSON from '@/mock/face.json' import faceJSON from '@/mock/face.json'
import utils from '@/utils/utils' import utils from '@/utils/utils'
import wx from 'weixin-js-sdk' import wx from 'weixin-js-sdk'
const props = defineProps({ const props = defineProps({
introduce: { type: String, default: '' }, introduce: { type: String, default: '' },
intents: { type: Array, default: () => [] }, intents: { type: Array, default: () => [] },
disabled: { type: Boolean, default: false }, disabled: { type: Boolean, default: false },
heightMax: { type: String, default: '' }, heightMax: { type: String, default: '' },
itemlist: { type: Object, default: () => ({}) }, itemlist: { type: Object, default: () => ({}) },
}) })
const store = inject('appStore', null) const store = inject('appStore', null)
const userId = inject('userId', ref('')) const userId = inject('userId', ref(''))
const userInfo = inject('userInfo', ref({})) const userInfo = inject('userInfo', ref({}))
const mescrollContent = inject('mescrollContent', ref(null)) const mescrollContent = inject('mescrollContent', ref(null))
const manualService = inject('manualService', ref('1')) const manualService = inject('manualService', ref('1'))
const sendLoading = inject('sendLoading', ref(false)) const sendLoading = inject('sendLoading', ref(false))
const publicPath = import.meta.env.BASE_URL const publicPath = import.meta.env.BASE_URL
const showTotal = ref(true) const showTotal = ref(true)
const exchangeButton = ref(true) const exchangeButton = ref(true)
const showExchangeButton = ref(false) const showExchangeButton = ref(false)
const expressionProcessing = ref({}) const expressionProcessing = ref({})
const bigExpressionProcessing = ref({}) const bigExpressionProcessing = ref({})
const isBtnsubmit = ref(true) const isBtnsubmit = ref(true)
const desc = ref(null) const desc = ref(null)
onMounted(() => { onMounted(() => {
processedEmoji() processedEmoji()
descHeightChange() descHeightChange()
nextTick(() => { nextTick(() => {
initGlobalCallbacks() initGlobalCallbacks()
}) })
}) })
function initGlobalCallbacks() { function initGlobalCallbacks() {
try { try {
window.getLink = (item) => { window.getLink = (item) => {
utils.AppToHttpHtml(item) utils.AppToHttpHtml(item)
} }
window.jumpOpenLink = (item) => { window.jumpOpenLink = (item) => {
utils.AppToHttpHtml(item) utils.AppToHttpHtml(item)
} }
window.jumpOpenWeb = (type, url, urlType) => { window.jumpOpenWeb = (type, url, urlType) => {
let params = {} let params = {}
if (type === 'YY_0000') { if (type === 'YY_0000') {
params = { params = {
idnum: '1', idnum: '1',
ownerId: userInfo.value?.encryptPid, ownerId: userInfo.value?.encryptPid,
openid: userInfo.value?.sourceUserId, openid: userInfo.value?.sourceUserId,
} }
const completeURL = handleURL(url, params) const completeURL = handleURL(url, params)
store store
?.dispatch?.('post', { ?.dispatch?.('post', {
url: 'semantic/getYdbqTicket', url: 'semantic/getYdbqTicket',
data: { url: completeURL, userId: userId.value }, data: { url: completeURL, userId: userId.value },
}) })
.then((res) => { .then((res) => {
if (res.code === 0) { if (res.code === 0) {
if (!res.data) { if (!res.data) {
utils.openWxPage(url, params) utils.openWxPage(url, params)
} else { } else {
utils.openWxPage(url, { accessTicket: res.data }) utils.openWxPage(url, { accessTicket: res.data })
} }
} else { } else {
showToast('网络异常,请重试') showToast('网络异常,请重试')
} }
}) })
} else if (type === 'YY_ydbq') { } else if (type === 'YY_ydbq') {
params = { params = {
mobile: userInfo.value?.phone, mobile: userInfo.value?.phone,
pid: userInfo.value?.encryptPid, pid: userInfo.value?.encryptPid,
openid: userInfo.value?.sourceUserId, openid: userInfo.value?.sourceUserId,
} }
const completeURL = handleURL(url, params) const completeURL = handleURL(url, params)
store store
?.dispatch?.('post', { ?.dispatch?.('post', {
url: 'semantic/getYdbqTicket', url: 'semantic/getYdbqTicket',
data: { url: completeURL, userId: userId.value }, data: { url: completeURL, userId: userId.value },
}) })
.then((res) => { .then((res) => {
if (res.code === 0) { if (res.code === 0) {
if (!res.data) { if (!res.data) {
utils.openWxPage(url, params) utils.openWxPage(url, params)
} else { } else {
utils.openWxPage(url, { accessTicket: res.data }) utils.openWxPage(url, { accessTicket: res.data })
} }
} else { } else {
showToast('网络异常,请重试') showToast('网络异常,请重试')
} }
}) })
} else if (type === 'YY_APP_H5') { } else if (type === 'YY_APP_H5') {
utils.AppOpenH5(url, { mobile: userInfo.value?.phone, pid: userInfo.value?.encryptPid }) utils.AppOpenH5(url, { mobile: userInfo.value?.phone, pid: userInfo.value?.encryptPid })
} else if (type === 'ydbq_2') { } else if (type === 'ydbq_2') {
utils.openSpecPage({ url, generateType: type, userId: userId.value }) utils.openSpecPage({ url, generateType: type, userId: userId.value })
} else if (type === 'YY_CMD') { } else if (type === 'YY_CMD') {
const newUrl = const newUrl =
url + url +
`&idnum=1&openId=${userInfo.value?.sourceUserId}&entryOpenid=${userInfo.value?.entryOpenid}` `&idnum=1&openId=${userInfo.value?.sourceUserId}&entryOpenid=${userInfo.value?.entryOpenid}`
store store
?.dispatch?.('post', { ?.dispatch?.('post', {
url: 'semantic/getYdbqTicket', url: 'semantic/getYdbqTicket',
data: { url: newUrl, userId: userId.value, isCmd: '1' }, data: { url: newUrl, userId: userId.value, isCmd: '1' },
}) })
.then((res) => { .then((res) => {
if (res.code === 0) { if (res.code === 0) {
if (!res.data) { if (!res.data) {
utils.AppToHttpHtml(newUrl) utils.AppToHttpHtml(newUrl)
} else { } else {
utils.AppToHttpHtml(url + `&accessTicket=${res.data}`) utils.AppToHttpHtml(url + `&accessTicket=${res.data}`)
} }
} else { } else {
showToast('网络异常,请重试') showToast('网络异常,请重试')
} }
}) })
} else if (type === 'YY_hfhz') { } else if (type === 'YY_hfhz') {
store store
?.dispatch?.('post', { ?.dispatch?.('post', {
url: 'semantic/xdhfCreateToken', url: 'semantic/xdhfCreateToken',
data: { certNum: '1', systemSource: 'yykf', userId: userId.value }, data: { certNum: '1', systemSource: 'yykf', userId: userId.value },
}) })
.then((res) => { .then((res) => {
const entryptedId = userInfo.value?.versionHFHZ const entryptedId = userInfo.value?.versionHFHZ
utils.openWxPage(url, { utils.openWxPage(url, {
sessionToken: res.data, sessionToken: res.data,
holderId: encodeURI(entryptedId), holderId: encodeURI(entryptedId),
holderType: 'a', holderType: 'a',
hzhfSystemSource: '0803', hzhfSystemSource: '0803',
ishwFlag: '1', ishwFlag: '1',
isqshzFlag: '1', isqshzFlag: '1',
}) })
}) })
} else if (type === 'YY_1v1') { } else if (type === 'YY_1v1') {
store store
?.dispatch?.('post', { url: 'thirdPartyAuth/getExclusiveServicesToken' }) ?.dispatch?.('post', { url: 'thirdPartyAuth/getExclusiveServicesToken' })
.then((res) => { .then((res) => {
if (res.code === 0 && res.data) { if (res.code === 0 && res.data) {
utils.openWxPage(url, { utils.openWxPage(url, {
sour: 'yykf', sour: 'yykf',
omoSource: userInfo.value?.source, omoSource: userInfo.value?.source,
omoSourceName: encodeURIComponent(userInfo.value?.sourceName), omoSourceName: encodeURIComponent(userInfo.value?.sourceName),
token: res.data, token: res.data,
}) })
} else { } else {
showToast('网络异常,请重试') showToast('网络异常,请重试')
} }
}) })
} else if (type === 'YY_yzsfx') { } else if (type === 'YY_yzsfx') {
store store
?.dispatch?.('post', { ?.dispatch?.('post', {
url: 'semantic/getYzsUrl', url: 'semantic/getYzsUrl',
data: { userId: userId.value, bqType: url, urlType }, data: { userId: userId.value, bqType: url, urlType },
}) })
.then((res) => { .then((res) => {
if (res.code === 0 && res.data) { if (res.code === 0 && res.data) {
utils.openWxPage(res.data, '') utils.openWxPage(res.data, '')
} else { } else {
showToast('网络异常,请重试') showToast('网络异常,请重试')
} }
}) })
} else if (type === 'YY_GWXCX') { } else if (type === 'YY_GWXCX') {
wx.miniProgram.navigateTo({ url: url }) wx.miniProgram.navigateTo({ url: url })
} else if (type === 'YY_GWXCX_HOME') { } else if (type === 'YY_GWXCX_HOME') {
wx.miniProgram.reLaunch({ url: url }) wx.miniProgram.reLaunch({ url: url })
} }
} }
window.openAppPage = (params) => { window.openAppPage = (params) => {
if (params) { if (params) {
params = params.replace(/\'/g, '"') params = params.replace(/\'/g, '"')
utils.AppToOutModules(params) utils.AppToOutModules(params)
} }
} }
window.connectionArtificial = (event) => { window.connectionArtificial = (event) => {
let isClick = false, let isClick = false,
isclassName = false isclassName = false
if (event.parentElement && !event.parentElement.className) { if (event.parentElement && !event.parentElement.className) {
isclassName = false isclassName = false
} else { } else {
isclassName = true isclassName = true
} }
if (isclassName && event.parentElement) { if (isclassName && event.parentElement) {
isClick = utils.hasClass(event.parentElement, 'connectionArtificial') isClick = utils.hasClass(event.parentElement, 'connectionArtificial')
} }
if (!isclassName || !isClick) { if (!isclassName || !isClick) {
connectionArtificial(event) connectionArtificial(event)
} }
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
} }
function handleURL(url, params) { function handleURL(url, params) {
let flag = url.indexOf('?') != -1 let flag = url.indexOf('?') != -1
let urlPar = '' let urlPar = ''
if (params) { if (params) {
Object.getOwnPropertyNames(params).forEach((item, index) => { Object.getOwnPropertyNames(params).forEach((item, index) => {
if (index === 0 && !flag) { if (index === 0 && !flag) {
urlPar += `?${item}=${params[item]}` urlPar += `?${item}=${params[item]}`
} else { } else {
urlPar += `&${item}=${params[item]}` urlPar += `&${item}=${params[item]}`
} }
}) })
} }
return url + urlPar return url + urlPar
} }
function replacementRagEmoticon(str) { function replacementRagEmoticon(str) {
str = str || '' str = str || ''
let res = str.replace(/\\n/gm, '<br/>') let res = str.replace(/\\n/gm, '<br/>')
return emojiToImage(res) return emojiToImage(res)
} }
function processedEmoji() { function processedEmoji() {
const tempExp = {} const tempExp = {}
const tempBigExp = {} const tempBigExp = {}
faceJSON.forEach((item) => { faceJSON.forEach((item) => {
item.faceList.forEach((items) => { item.faceList.forEach((items) => {
tempExp[`[${items.title}]`] = items.src tempExp[`[${items.title}]`] = items.src
}) })
item.bigExpression.forEach((items) => { item.bigExpression.forEach((items) => {
tempBigExp[`[${items.title}]`] = items.src tempBigExp[`[${items.title}]`] = items.src
}) })
}) })
expressionProcessing.value = tempExp expressionProcessing.value = tempExp
bigExpressionProcessing.value = tempBigExp bigExpressionProcessing.value = tempBigExp
} }
function emojiToImage(str) { function emojiToImage(str) {
let srcImg = '' let srcImg = ''
let regex = /\[[^\]]+\]/g let regex = /\[[^\]]+\]/g
srcImg = str.replace(regex, (item) => { srcImg = str.replace(regex, (item) => {
if (expressionProcessing.value[item]) { if (expressionProcessing.value[item]) {
return `<img src="${ return `<img src="${
publicPath + expressionProcessing.value[item] publicPath + expressionProcessing.value[item]
}" alt="small-image" title="${item}" />` }" alt="small-image" title="${item}" />`
} else if (bigExpressionProcessing.value[item]) { } else if (bigExpressionProcessing.value[item]) {
return `<img src="${ return `<img src="${
publicPath + bigExpressionProcessing.value[item] publicPath + bigExpressionProcessing.value[item]
}" alt="big-image" title="${item}"/>` }" alt="big-image" title="${item}"/>`
} }
return item return item
}) })
return srcImg return srcImg
} }
function showTotalIntro() { function showTotalIntro() {
showTotal.value = !showTotal.value showTotal.value = !showTotal.value
exchangeButton.value = !exchangeButton.value exchangeButton.value = !exchangeButton.value
} }
function getRem() { function getRem() {
let winWidth = window.innerWidth let winWidth = window.innerWidth
const defaultRem = document.body.style.fontSize.replace('rem', '') const defaultRem = document.body.style.fontSize.replace('rem', '')
let rem = (winWidth / 375) * defaultRem let rem = (winWidth / 375) * defaultRem
return rem return rem
} }
function descHeightChange() { function descHeightChange() {
nextTick(() => { nextTick(() => {
let rem = parseFloat(getRem()) let rem = parseFloat(getRem())
if (!desc.value) return if (!desc.value) return
let descHeight = window.getComputedStyle(desc.value).height.replace('rem', '') let descHeight = window.getComputedStyle(desc.value).height.replace('rem', '')
descHeight = descHeight || desc.value.offsetHeight descHeight = descHeight || desc.value.offsetHeight
if (descHeight > 2.325 * rem) { if (descHeight > 2.325 * rem) {
showExchangeButton.value = true showExchangeButton.value = true
exchangeButton.value = true exchangeButton.value = true
showTotal.value = false showTotal.value = false
} else { } else {
showExchangeButton.value = false showExchangeButton.value = false
showTotal.value = true showTotal.value = true
} }
}) })
} }
function scrollTopBody() { function scrollTopBody() {
setTimeout(() => { setTimeout(() => {
try { try {
if (mescrollContent.value) if (mescrollContent.value)
mescrollContent.value.scrollTop = mescrollContent.value.scrollHeight || 0 mescrollContent.value.scrollTop = mescrollContent.value.scrollHeight || 0
} catch (e) { } catch (e) {
console.log(e.message) console.log(e.message)
} }
}, 300) }, 300)
} }
function connectionArtificial(event) { function connectionArtificial(event) {
try { try {
let qaId = event.parentNode ? event.parentNode.dataset.qaid : '' let qaId = event.parentNode ? event.parentNode.dataset.qaid : ''
let sendParmas = { reqAndResType: '1', msgText: '&转人工服务&', msgSource: manualService.value } let sendParmas = { reqAndResType: '1', msgText: '&转人工服务&', msgSource: manualService.value }
scrollTopBody() scrollTopBody()
store store
?.dispatch?.('connectionArtificial', sendParmas) ?.dispatch?.('connectionArtificial', sendParmas)
.then(() => { .then(() => {
event.disabled = true event.disabled = true
}) })
.catch((err) => console.log(err)) .catch((err) => console.log(err))
store store
?.dispatch?.('userBehavior', { qaId, eventType: '5', eventMsg: '知识库进入人工服务' }) ?.dispatch?.('userBehavior', { qaId, eventType: '5', eventMsg: '知识库进入人工服务' })
.catch((err) => console.log(err)) .catch((err) => console.log(err))
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
} }
function richCallAnswer(item) { function richCallAnswer(item) {
if (!isBtnsubmit.value) return if (!isBtnsubmit.value) return
isBtnsubmit.value = false isBtnsubmit.value = false
let sendParmas = { let sendParmas = {
reqAndResType: '1', reqAndResType: '1',
msgText: item.intent, msgText: item.intent,
intentId: item.intentId, intentId: item.intentId,
msgSource: manualService.value, msgSource: manualService.value,
} }
scrollTopBody() scrollTopBody()
store store
?.dispatch?.('sendingRobot', sendParmas) ?.dispatch?.('sendingRobot', sendParmas)
.then(() => { .then(() => {
isBtnsubmit.value = true isBtnsubmit.value = true
}) })
.catch((err) => { .catch((err) => {
isBtnsubmit.value = true isBtnsubmit.value = true
console.log(err) console.log(err)
}) })
} }
function manualAnswer(event, item) { function manualAnswer(event, item) {
let sendParmas = { reqAndResType: '1', msgText: '&转人工服务&', msgSource: manualService.value } let sendParmas = { reqAndResType: '1', msgText: '&转人工服务&', msgSource: manualService.value }
const toast = showLoadingToast({ const toast = showLoadingToast({
message: '加载中...', message: '加载中...',
duration: 0, duration: 0,
loadingType: 'spinner', loadingType: 'spinner',
forbidClick: true, forbidClick: true,
}) })
if (!sendLoading.value) { if (!sendLoading.value) {
scrollTopBody() scrollTopBody()
store store
?.dispatch?.('sendingRobot', sendParmas) ?.dispatch?.('sendingRobot', sendParmas)
.then(() => { .then(() => {
closeToast() closeToast()
event.disabled = true event.disabled = true
}) })
.catch((err) => { .catch((err) => {
closeToast() closeToast()
console.log(err) console.log(err)
}) })
} else { } else {
closeToast() closeToast()
} }
} }
</script> </script>
<style scoped> <style scoped>
.text-template-box { .text-template-box {
padding: 0; padding: 0;
} }
.question-answer-con { .question-answer-con {
overflow: hidden; overflow: hidden;
} }
.total-introduce { .total-introduce {
max-height: none; max-height: none;
} }
.detailed-introduce { .detailed-introduce {
max-height: 2.325rem; max-height: 2.325rem;
} }
.content-question-answer { .content-question-answer {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 8rem; gap: 8rem;
margin-top: 10rem; margin-top: 10rem;
} }
.answer-items { .answer-items {
padding: 6rem 12rem; padding: 6rem 12rem;
background: #f0f6ff; background: #f0f6ff;
border: 1rem solid #d6e4ff; border: 1rem solid #d6e4ff;
border-radius: 16rem; border-radius: 16rem;
font-size: 13rem; font-size: 13rem;
color: #025cea; color: #025cea;
cursor: pointer; cursor: pointer;
} }
.answer-items[disabled] { .answer-items[disabled] {
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
} }
.unfold-box { .unfold-box {
text-align: center; text-align: center;
margin-top: 8rem; margin-top: 8rem;
} }
.unfold-box a { .unfold-box a {
font-size: 13rem; font-size: 13rem;
color: #025cea; color: #025cea;
} }
.look-more-icon { .look-more-icon {
display: inline-block; display: inline-block;
width: 12rem; width: 12rem;
height: 12rem; height: 12rem;
margin-left: 4rem; margin-left: 4rem;
transition: transform 0.3s; transition: transform 0.3s;
} }
.look-more-icon.active { .look-more-icon.active {
transform: rotate(180deg); transform: rotate(180deg);
} }
</style> </style>

File diff suppressed because it is too large Load Diff

@ -3,6 +3,7 @@ import { ref, onMounted, inject } from 'vue';
import { Post } from '@/utils/https' import { Post } from '@/utils/https'
const store = inject('appStore') const store = inject('appStore')
const emit = defineEmits(['ready'])
const welcomeWord = ref('') const welcomeWord = ref('')
const welcomeSubWord = ref('我是您的服务管家皮卡丘') const welcomeSubWord = ref('我是您的服务管家皮卡丘')
onMounted(() => { onMounted(() => {
@ -11,8 +12,12 @@ onMounted(() => {
userInfoId: store.userId userInfoId: store.userId
}).then(res => { }).then(res => {
if (res.code === 0 && res.data) { if (res.code === 0 && res.data) {
welcomeWord.value = `${res.data?.greetings || ''}${res.data?.greetings ? ',' : ''}${res.data?.welcomeName || ''}` welcomeWord.value = `${res.data?.greetings || ''}${res.data?.welcomeName ? ',' : ''}${res.data?.welcomeName || ''}`
} }
}).catch((error) => {
console.log(error)
}).finally(() => {
emit('ready')
}) })
}) })
</script> </script>
@ -25,7 +30,7 @@ onMounted(() => {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.welcome-con { .welcome-con {
padding: 33rem 8rem; padding: 38rem 8rem;
position: relative; position: relative;
.welcome-title { .welcome-title {
font-family: PingFang SC; font-family: PingFang SC;
@ -44,7 +49,7 @@ onMounted(() => {
} }
.sub-title { .sub-title {
width: calc(100% - 100rem); max-width: calc(100% - 100rem);
font-family: PingFang SC; font-family: PingFang SC;
font-size: 18rem; font-size: 18rem;
font-weight: 600; font-weight: 600;
@ -56,6 +61,7 @@ onMounted(() => {
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
width: fit-content;
text-fill-color: transparent; text-fill-color: transparent;
} }

@ -1,37 +1,34 @@
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import Utils from "@/utils/utils"; import Utils from "@/utils/utils";
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes: [ routes: [
{ path: '/', redirect: '/chat' }, { path: '/', redirect: '/chat' },
{ path: '/chat', name: 'chat', component: () => import('../views/chat.vue') }, { path: '/chat', name: 'chat', component: () => import('../views/chat.vue'), meta: { keepAlive: true, title: '小蓝管家' } },
{ { path: '/word', name: 'word', component: () => import('../views/word.vue'), meta: { keepAlive: true, title: '小蓝管家' } },
path: '/history', // {
name: 'history', // path: '/history',
component: () => import('../views/history.vue'), // name: 'history',
meta: { title: '洋洋客服-历史记录查询' }, // component: () => import('../views/history.vue'),
}, // meta: { title: '小蓝管家-历史记录查询' },
{ // },
path: '/previewPdf', // {
name: 'previewPdf', // path: '/previewPdf',
component: () => import('../components/previewPDF/index.vue'), // name: 'previewPdf',
meta: { title: '预览PDF' }, // component: () => import('../components/previewPDF/index.vue'),
}, // meta: { title: '预览PDF' },
// { path: '/uploadPage', name: 'uploadPage', component: () => import('../views/uploadPage.vue') }, // },
// { path: '/claimPage', name: 'claimPage', component: () => import('../views/claimPage/index.vue'), }, ],
// { path: '/outView', name: 'outView', component: () => import('../views/outView.vue') }, })
// { path: '/sign', name: 'sign', component: () => import('../components/input/sign.vue') }, const defaultTitle = '小蓝管家'
],
}) // 全局前置路由守卫
const defaultTitle = '洋洋客服' router.beforeEach(async (to, from, next) => {
/* 路由发生变化修改页面title */
// 全局前置路由守卫 var title = to.meta.title ? to.meta.title : defaultTitle;
router.beforeEach(async (to, from, next) => { Utils.setTitle(title);//修改页面title
/* 路由发生变化修改页面title */ next();
var title = to.meta.title ? to.meta.title : defaultTitle; })
Utils.setTitle(title);//修改页面title
next(); export default router
})
export default router

@ -117,6 +117,7 @@ export const useAppStore = defineStore('app', () => {
const isBackstage = ref(false) const isBackstage = ref(false)
const sdkLoaded = ref(false) const sdkLoaded = ref(false)
const chatArr = ref([]) const chatArr = ref([])
const businessHandleInfo = ref({})
const isChatLoading = ref(false) const isChatLoading = ref(false)
const bpErrorInfo = ref([]) const bpErrorInfo = ref([])
const voiceDataList = ref([]) const voiceDataList = ref([])
@ -194,6 +195,7 @@ export const useAppStore = defineStore('app', () => {
userInfo.value = {} userInfo.value = {}
chatList.value = [] chatList.value = []
chatArr.value = [] chatArr.value = []
businessHandleInfo.value = {}
isChatLoading.value = false isChatLoading.value = false
sendMessageList.value = [] sendMessageList.value = []
receiveMessageList.value = [] receiveMessageList.value = []
@ -752,12 +754,17 @@ export const useAppStore = defineStore('app', () => {
chatList.value.splice(index, 1) chatList.value.splice(index, 1)
} }
function setBusinessHandleInfo(payload = {}) {
businessHandleInfo.value = payload || {}
}
function delAllChatList() { function delAllChatList() {
chatList.value.splice(0, chatList.value.length) chatList.value.splice(0, chatList.value.length)
chatArr.value.splice(0, chatArr.value.length) chatArr.value.splice(0, chatArr.value.length)
isChatLoading.value = false isChatLoading.value = false
} }
function delSendFailListById(delId) { function delSendFailListById(delId) {
if (sendFailList.value && sendFailList.value.length > 0) { if (sendFailList.value && sendFailList.value.length > 0) {
sendFailList.value.forEach((item, index) => { sendFailList.value.forEach((item, index) => {
@ -1345,6 +1352,8 @@ export const useAppStore = defineStore('app', () => {
set_hxOpenSuccesed: setHxOpenSuccesed, set_timestamp: setTimestamp, set_hxOpenSuccesed: setHxOpenSuccesed, set_timestamp: setTimestamp,
set_sendParmas: setSendParmas, set_queuedTimeout: setQueuedTimeout, set_sendParmas: setSendParmas, set_queuedTimeout: setQueuedTimeout,
set_chatList: setChatList, del_chatList: delChatList, delall_chatList: delAllChatList, set_chatList: setChatList, del_chatList: delChatList, delall_chatList: delAllChatList,
set_businessHandleInfo: setBusinessHandleInfo,
setBusinessHandleInfo,
set_chatId: setChatId, set_currentItemId: setCurrentItemId, set_chatId: setChatId, set_currentItemId: setCurrentItemId,
set_findActiveItem: setFindActiveItem, set_activeItemExtra: setActiveItemExtra, set_findActiveItem: setFindActiveItem, set_activeItemExtra: setActiveItemExtra,
set_reSendMessage: setReSendMessage, set_isBackstage: setIsBackstage, set_reSendMessage: setReSendMessage, set_isBackstage: setIsBackstage,
@ -1433,6 +1442,7 @@ export const useAppStore = defineStore('app', () => {
isBackstage, isBackstage,
sdkLoaded, sdkLoaded,
chatArr, chatArr,
businessHandleInfo,
isChatLoading, isChatLoading,
bpErrorInfo, bpErrorInfo,
voiceDataList, voiceDataList,
@ -1511,6 +1521,7 @@ export const useAppStore = defineStore('app', () => {
setChatList, setChatList,
delChatList, delChatList,
delAllChatList, delAllChatList,
setBusinessHandleInfo,
setChatId, setChatId,
setCurrentItemId, setCurrentItemId,
setFindActiveItem, setFindActiveItem,

File diff suppressed because it is too large Load Diff

@ -1,465 +1,465 @@
import config from './config' import config from './config'
import { showToast, showDialog, showConfirmDialog } from 'vant' import { showToast, showDialog, showConfirmDialog } from 'vant'
import { useAppStore } from '@/store/app.js' import { useAppStore } from '@/store/app.js'
import utils from './utils' import utils from './utils'
// Keep the legacy socket implementation independent from Vuex. The proxy // Keep the legacy socket implementation independent from Vuex. The proxy
// exposes the old state/commit/dispatch shape while reading the live Pinia // exposes the old state/commit/dispatch shape while reading the live Pinia
// store instance for every call. // store instance for every call.
const store = { const store = {
get state() { get state() {
return useAppStore().state return useAppStore().state
}, },
commit(type, payload) { commit(type, payload) {
return useAppStore().commit(type, payload) return useAppStore().commit(type, payload)
}, },
dispatch(type, payload) { dispatch(type, payload) {
return useAppStore().dispatch(type, payload) return useAppStore().dispatch(type, payload)
}, },
} }
/** /**
* 提示函数 * 提示函数
* 禁止点击蒙层、显示3秒后关闭 * 禁止点击蒙层、显示3秒后关闭
*/ */
const toast = msg => { const toast = msg => {
showToast({ showToast({
message: msg, message: msg,
duration: 5000, duration: 5000,
position: 'top', position: 'top',
}); });
} }
const sendMessageFunction = (msgText) => { const sendMessageFunction = (msgText) => {
return { return {
token: store.state.token, token: store.state.token,
fromFlag: 'other', fromFlag: 'other',
userId: store.state.userId, userId: store.state.userId,
sessionId: store.state.sessionId, sessionId: store.state.sessionId,
reqAndResType: '', reqAndResType: '',
msgType: '', // msgType: '', //
msgText: msgText, msgText: msgText,
intentId: '', intentId: '',
source: store.state.source, source: store.state.source,
timestamp: (new Date()).valueOf(), timestamp: (new Date()).valueOf(),
msgSource: store.state.manualService, //1机器人 2人工 msgSource: store.state.manualService, //1机器人 2人工
cmd: '' cmd: ''
} }
} }
//设置title //设置title
document.setTitle = function (title) { document.setTitle = function (title) {
title = title || '洋洋客服'; title = title || '洋洋客服';
store.commit('set_otherTips', title); store.commit('set_otherTips', title);
// document.title = title; // document.title = title;
var iframe = document.createElement('iframe'); var iframe = document.createElement('iframe');
iframe.src = `${import.meta.env.BASE_URL}favicon.ico`; iframe.src = `${import.meta.env.BASE_URL}favicon.ico`;
iframe.style.display = 'none'; iframe.style.display = 'none';
iframe.onload = function () { iframe.onload = function () {
setTimeout(function () { setTimeout(function () {
iframe.remove(); iframe.remove();
}, 9) }, 9)
} }
document.body.appendChild(iframe); document.body.appendChild(iframe);
} }
let wsConnection = { let wsConnection = {
WSS_URL: config.WSURL, WSS_URL: config.WSURL,
$ws: null, $ws: null,
lockReturn: false, lockReturn: false,
//6s发一次心跳 //6s发一次心跳
timeout: 60 * 100 * 1, timeout: 60 * 100 * 1,
timeoutObj: null, timeoutObj: null,
timeoutNum: null, timeoutNum: null,
serverTimeoutObj: null, serverTimeoutObj: null,
globalCallback: null, globalCallback: null,
sendMessageData: null, sendMessageData: null,
reconectNum: 0, reconectNum: 0,
//最大限制 重连次数 //最大限制 重连次数
reconectTimes: 20, reconectTimes: 20,
closeSession: false,//是否关闭当前会话 closeSession: false,//是否关闭当前会话
//初始化webSocket长连接 //初始化webSocket长连接
initWebSocket: function (callback) { initWebSocket: function (callback) {
var that = this; var that = this;
var userId = store.state.userId; var userId = store.state.userId;
var checkParam = store.state.token; var checkParam = store.state.token;
var sessionId = store.state.sessionId; var sessionId = store.state.sessionId;
try { try {
if ('WebSocket' in window) { if ('WebSocket' in window) {
// this.$ws = new WebSocket(this.WSS_URL + userId + "," + sessionId); // this.$ws = new WebSocket(this.WSS_URL + userId + "," + sessionId);
this.$ws = new WebSocket(`${this.WSS_URL}${userId}&${encodeURIComponent(checkParam)}`); this.$ws = new WebSocket(`${this.WSS_URL}${userId}&${encodeURIComponent(checkParam)}`);
} else if ('MozWebSocket' in window) { } else if ('MozWebSocket' in window) {
// this.$ws = new MozWebSocket(this.WSS_URL + userId + "," + sessionId); // this.$ws = new MozWebSocket(this.WSS_URL + userId + "," + sessionId);
this.$ws = new MozWebSocket(`${this.WSS_URL}${userId}&${encodeURIComponent(checkParam)}`); this.$ws = new MozWebSocket(`${this.WSS_URL}${userId}&${encodeURIComponent(checkParam)}`);
} else { } else {
showDialog({ showDialog({
message: '您的浏览器不支持websocket协议,建议您使用Android系统版本大于4.0,ios系统版本大于7.0。' message: '您的浏览器不支持websocket协议,建议您使用Android系统版本大于4.0,ios系统版本大于7.0。'
}).then(() => { }).then(() => {
utils.closeWindow(); //关闭页面 utils.closeWindow(); //关闭页面
}); });
} }
// this.$ws.onopen = this.wsOpen; // this.$ws.onopen = this.wsOpen;
this.$ws.addEventListener("open", event => { this.$ws.addEventListener("open", event => {
this.wsOpen(); this.wsOpen();
}) })
// this.$ws = new WebSocket(this.WSS_URL + userId + "," + sessionId); // this.$ws = new WebSocket(this.WSS_URL + userId + "," + sessionId);
this.$ws.onclose = this.wsClose; this.$ws.onclose = this.wsClose;
this.$ws.onmessage = this.onMessage; this.$ws.onmessage = this.onMessage;
this.$ws.onerror = this.wsError; this.$ws.onerror = this.wsError;
callback && callback(); callback && callback();
// store.commit('set_otherTips', "连接中..."); // store.commit('set_otherTips', "连接中...");
document.setTitle('连接中...') document.setTitle('连接中...')
} catch (e) { } catch (e) {
wsConnection.reconnect() wsConnection.reconnect()
console.log(e); console.log(e);
} }
}, },
wsResendMessage() { wsResendMessage() {
var sendFailList = store.state.sendFailList;//取出上次发送失败的消息 var sendFailList = store.state.sendFailList;//取出上次发送失败的消息
if (sendFailList.length > 0) { if (sendFailList.length > 0) {
for (let i = 0; i < sendFailList.length; i++) { for (let i = 0; i < sendFailList.length; i++) {
(function (i) {//这个匿名函数生成了闭包的效果,新建了一个作用域,这个作用域接收到每次循环的i值保存了下来,即使循环结束,闭包形成的作用域也不会被销毁 (function (i) {//这个匿名函数生成了闭包的效果,新建了一个作用域,这个作用域接收到每次循环的i值保存了下来,即使循环结束,闭包形成的作用域也不会被销毁
setTimeout(function () { setTimeout(function () {
var dataResend = sendFailList[i]; var dataResend = sendFailList[i];
console.log("发送失败的数据ID==", dataResend.timestamp); console.log("发送失败的数据ID==", dataResend.timestamp);
wsConnection.sendMessage(dataResend, wsConnection.globalCallback); wsConnection.sendMessage(dataResend, wsConnection.globalCallback);
store.commit('set_currentItemId', dataResend.timestamp); store.commit('set_currentItemId', dataResend.timestamp);
store.commit('set_findActiveItem', false);//发送成功 store.commit('set_findActiveItem', false);//发送成功
}, 200); }, 200);
})(i); })(i);
} }
store.commit('del_sendFailList');//删除发送成功的 store.commit('del_sendFailList');//删除发送成功的
console.log(store.state.sendFailList); console.log(store.state.sendFailList);
} }
}, },
//打开websocket //打开websocket
wsOpen: function (e) { wsOpen: function (e) {
var that = this; var that = this;
//开始websocket心跳 //开始websocket心跳
wsConnection.startWsHeartbeat(); wsConnection.startWsHeartbeat();
// toast('会话建立成功'); // toast('会话建立成功');
// store.commit('set_otherTips', "洋洋客服"); // store.commit('set_otherTips', "洋洋客服");
document.setTitle('洋洋客服') document.setTitle('洋洋客服')
//执行重新发送 //执行重新发送
// if(wsConnection.isReconnet){ // if(wsConnection.isReconnet){
// wsConnection.wsResendMessage(); // wsConnection.wsResendMessage();
// wsConnection.isReconnet = false; // wsConnection.isReconnet = false;
// } // }
console.log("ws success " + new Date().format("yyyy-MM-dd hh:mm:ss")); console.log("ws success " + new Date().format("yyyy-MM-dd hh:mm:ss"));
}, },
onMessage: function (event) { onMessage: function (event) {
//每次接收到服务端消息后 重置websocket心跳 //每次接收到服务端消息后 重置websocket心跳
wsConnection.resetHeartbeat(); wsConnection.resetHeartbeat();
let res = event.data; let res = event.data;
try { try {
if (event.data === 'pong') { if (event.data === 'pong') {
console.log("收到心跳了:", event.data); console.log("收到心跳了:", event.data);
return; return;
} }
if (event.data.match(/^\d+$/)) { if (event.data.match(/^\d+$/)) {
wsConnection.globalCallback && wsConnection.globalCallback(event.data); wsConnection.globalCallback && wsConnection.globalCallback(event.data);
return; return;
} }
res = JSON.parse(res); res = JSON.parse(res);
console.log("收到消息啦:", res.data || res); console.log("收到消息啦:", res.data || res);
if (res.data.cmd === 'ServiceSessionClosedEvent' && store.state.hxOpenSucceseds.includes(res.data.sessionId)) { if (res.data.cmd === 'ServiceSessionClosedEvent' && store.state.hxOpenSucceseds.includes(res.data.sessionId)) {
if(store.state.integratedUser){ if(store.state.integratedUser){
utils.useSdk(res.data) utils.useSdk(res.data)
}else{ }else{
setTimeout(() => { setTimeout(() => {
utils.useSdk(res.data) utils.useSdk(res.data)
}, 100); }, 100);
} }
} }
if (res.code != 0) { if (res.code != 0) {
// toast(data.msg); // toast(data.msg);
if (res.code === 10001) { if (res.code === 10001) {
// 删除已经失效或过期的token(不删除也可以,因为登录后覆盖) // 删除已经失效或过期的token(不删除也可以,因为登录后覆盖)
sessionStorage.removeItem("token"); sessionStorage.removeItem("token");
wsConnection.$ws.close(); wsConnection.$ws.close();
if (store.state.notitle === '1') { if (store.state.notitle === '1') {
toast('当前会话已结束,请刷新页面或关闭页面重新进入'); toast('当前会话已结束,请刷新页面或关闭页面重新进入');
return return
} }
showConfirmDialog({ showConfirmDialog({
title: '提示', title: '提示',
message: '当前会话已结束,请刷新页面或关闭页面重新进入', message: '当前会话已结束,请刷新页面或关闭页面重新进入',
confirmButtonText: '取消', confirmButtonText: '取消',
cancelButtonText: '确认', cancelButtonText: '确认',
confirmButtonColor: '#000000', confirmButtonColor: '#000000',
cancelButtonColor: '#000000' cancelButtonColor: '#000000'
}).then(() => { }).then(() => {
// on cancel (现在是右边的取消按钮) // on cancel (现在是右边的取消按钮)
// utils.closeWindow(); // utils.closeWindow();
// router.replace({ // router.replace({
// path: '/404' // 到登录页重新获取token // path: '/404' // 到登录页重新获取token
// }) // })
}).catch(() => { }).catch(() => {
// on confirm (现在是左边的确认按钮) // on confirm (现在是左边的确认按钮)
//确认重新刷新页面获取token //确认重新刷新页面获取token
// window.location.reload(); // window.location.reload();
utils.AppReloadHtml(); utils.AppReloadHtml();
}); });
} else { } else {
if (res.msg) { if (res.msg) {
toast(res.msg); toast(res.msg);
} }
} }
return; return;
} }
//是否转接人工参数 //是否转接人工参数
// if (res.data.cmd && res.data.cmd == "ServiceSessionCreatedEvent") { // if (res.data.cmd && res.data.cmd == "ServiceSessionCreatedEvent") {
// store.dispatch('setTransferCustomer', 2); // store.dispatch('setTransferCustomer', 2);
// } // }
//客服接起不排队了 //客服接起不排队了
if (res.data.cmd && res.data.cmd == "ServiceSessionOpenedEvent") { if (res.data.cmd && res.data.cmd == "ServiceSessionOpenedEvent") {
store.commit('set_queuedTimeout', true); store.commit('set_queuedTimeout', true);
store.commit('set_msgStatus', '');//人工客服排队状态值 等待中:"1",其他:"" store.commit('set_msgStatus', '');//人工客服排队状态值 等待中:"1",其他:""
store.commit('set_msgReply', false);//人工客服排队是否调取自动回复话术 store.commit('set_msgReply', false);//人工客服排队是否调取自动回复话术
} }
if (res.data.cmd && res.data.cmd == "ServiceSessionClosedEvent") { if (res.data.cmd && res.data.cmd == "ServiceSessionClosedEvent") {
wsConnection.closeSession = true;//是关闭会话 wsConnection.closeSession = true;//是关闭会话
setTimeout(() => { setTimeout(() => {
store.commit('set_queuedTimeout', true); store.commit('set_queuedTimeout', true);
store.dispatch('setTransferCustomer', 1);//人工改为机器人 store.dispatch('setTransferCustomer', 1);//人工改为机器人
wsConnection.$ws.close(); wsConnection.$ws.close();
}, 2500) }, 2500)
} }
//服务端发送来的消息存到vuex //服务端发送来的消息存到vuex
store.dispatch('sendingDrtificial', res.data).then((res) => { store.dispatch('sendingDrtificial', res.data).then((res) => {
}).catch(err => { }).catch(err => {
console.log(err) console.log(err)
}); });
//如果会话关闭不用推送消息回执 //如果会话关闭不用推送消息回执
if (res.data && res.data.chatId) { if (res.data && res.data.chatId) {
let chatParam = JSON.stringify(res.data); let chatParam = JSON.stringify(res.data);
// wsConnection.$ws.send(chatParam, ''); // wsConnection.$ws.send(chatParam, '');
wsConnection.sendMessage(chatParam, ''); wsConnection.sendMessage(chatParam, '');
console.log("发送消息回执", chatParam) console.log("发送消息回执", chatParam)
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
}, },
//发送数据,接收处理数据 //发送数据,接收处理数据
sendMessage: function (data, callback) { sendMessage: function (data, callback) {
//存储事件 //存储事件
wsConnection.globalCallback = callback; wsConnection.globalCallback = callback;
// console.log(data, callback) // console.log(data, callback)
switch (wsConnection.$ws.readyState) { switch (wsConnection.$ws.readyState) {
//CONNECTING:值为0,表示正在连接。 //CONNECTING:值为0,表示正在连接。
case wsConnection.$ws.CONNECTING: case wsConnection.$ws.CONNECTING:
setTimeout(function () { setTimeout(function () {
wsConnection.sendMessage(data, callback); wsConnection.sendMessage(data, callback);
}, 1000); }, 1000);
break; break;
//OPEN:值为1,表示连接成功,可以通信了。 //OPEN:值为1,表示连接成功,可以通信了。
case wsConnection.$ws.OPEN: case wsConnection.$ws.OPEN:
wsConnection.$ws.send(data); wsConnection.$ws.send(data);
break; break;
//CLOSING:值为2,表示连接正在关闭。 //CLOSING:值为2,表示连接正在关闭。
case wsConnection.$ws.CLOSING: case wsConnection.$ws.CLOSING:
setTimeout(function () { setTimeout(function () {
wsConnection.sendMessage(data, callback); wsConnection.sendMessage(data, callback);
}, 1000); }, 1000);
break; break;
//CLOSED:值为3,表示连接已经关闭,或者打开连接失败。 //CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
case wsConnection.$ws.CLOSED: case wsConnection.$ws.CLOSED:
// do something // do something
break; break;
default: default:
// this never happens // this never happens
break; break;
} }
//重新监听onmessage,并把数据传给回调函数 //重新监听onmessage,并把数据传给回调函数
// wsConnection.onmessage = ev =>{ // wsConnection.onmessage = ev =>{
// callback && callback(ev) // callback && callback(ev)
// } // }
}, },
//重启websocket //重启websocket
reconnect: function () { reconnect: function () {
let _this = this; let _this = this;
if (_this.lockReturn) { if (_this.lockReturn) {
return; return;
} }
_this.isReconnet = true; _this.isReconnet = true;
_this.lockReturn = true; _this.lockReturn = true;
//没连接上会一直重连,设置延迟避免请求过多 //没连接上会一直重连,设置延迟避免请求过多
if (_this.reconectNum < this.reconectTimes) { if (_this.reconectNum < this.reconectTimes) {
_this.timeoutNum && clearTimeout(_this.timeoutNum); _this.timeoutNum && clearTimeout(_this.timeoutNum);
_this.timeoutNum = setTimeout(function () { _this.timeoutNum = setTimeout(function () {
_this.initWebSocket(); _this.initWebSocket();
// toast('正在重连,请稍后...'); // toast('正在重连,请稍后...');
// store.commit('set_otherTips', "正在重连..."); // store.commit('set_otherTips', "正在重连...");
document.setTitle('正在重连...') document.setTitle('正在重连...')
console.info(`正在重连第${_this.reconectNum + 1}次`) console.info(`正在重连第${_this.reconectNum + 1}次`)
_this.reconectNum++ _this.reconectNum++
_this.lockReturn = false; _this.lockReturn = false;
}, 4000); }, 4000);
} else { } else {
// toast('重连次数超出设定值,不再重连,请检查配置项'); // toast('重连次数超出设定值,不再重连,请检查配置项');
showDialog({ showDialog({
message: '重连次数超出设定值,不再重连。请检查您的网络设置,点击“确定”刷新页面,重新建立会话。' message: '重连次数超出设定值,不再重连。请检查您的网络设置,点击“确定”刷新页面,重新建立会话。'
}).then(() => { }).then(() => {
// window.location.reload(); // window.location.reload();
utils.AppReloadHtml(); utils.AppReloadHtml();
}); });
} }
}, },
//心跳检测,每5s心跳一次 //心跳检测,每5s心跳一次
startWsHeartbeat: function () { startWsHeartbeat: function () {
let _this = this; let _this = this;
this.timeoutObj && clearTimeout(this.timeoutObj); this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj); this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(() => { this.timeoutObj = setTimeout(() => {
//这里发送一个心跳,后端收到后,返回一个心跳消息, //这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常 //onmessage拿到返回的心跳就说明连接正常
if (wsConnection.$ws && wsConnection.$ws.readyState != wsConnection.$ws.CLOSED) { if (wsConnection.$ws && wsConnection.$ws.readyState != wsConnection.$ws.CLOSED) {
//关闭当前会话后不发送ping //关闭当前会话后不发送ping
if (!wsConnection.closeSession) { if (!wsConnection.closeSession) {
_this.$ws.send("ping"); _this.$ws.send("ping");
console.log("ping!", new Date().format("yyyy-MM-dd hh:mm:ss")); console.log("ping!", new Date().format("yyyy-MM-dd hh:mm:ss"));
} }
} }
//如果超过一定时间还没重置,说明后端主动断开了 //如果超过一定时间还没重置,说明后端主动断开了
_this.serverTimeoutObj = setTimeout(() => { _this.serverTimeoutObj = setTimeout(() => {
//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次 //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
_this.$ws.close(); _this.$ws.close();
}, _this.timeout) }, _this.timeout)
}, this.timeout); }, this.timeout);
}, },
//重置websocket心跳 //重置websocket心跳
resetHeartbeat: function () { resetHeartbeat: function () {
let _this = this; let _this = this;
this.reconectNum = 0; this.reconectNum = 0;
clearTimeout(_this.timeoutObj); clearTimeout(_this.timeoutObj);
clearTimeout(_this.serverTimeoutObj); clearTimeout(_this.serverTimeoutObj);
_this.startWsHeartbeat(); _this.startWsHeartbeat();
}, },
wsError: function (err) { wsError: function (err) {
console.log(err, 'ws error'); console.log(err, 'ws error');
// store.commit('set_otherTips', "洋洋客服(未连接)"); // store.commit('set_otherTips', "洋洋客服(未连接)");
document.setTitle('洋洋客服(未连接)') document.setTitle('洋洋客服(未连接)')
wsConnection.reconnect() wsConnection.reconnect()
}, },
wsClose: function (e) { wsClose: function (e) {
// console.log(e, 'ws close') // console.log(e, 'ws close')
console.log("连接关闭! " + new Date().format("yyyy-MM-dd hh:mm:ss")); console.log("连接关闭! " + new Date().format("yyyy-MM-dd hh:mm:ss"));
console.log('websocket 断开: ' + e.code + ' 关闭的原因:' + e.reason + ' 是否正常关闭' + e.wasClean); console.log('websocket 断开: ' + e.code + ' 关闭的原因:' + e.reason + ' 是否正常关闭' + e.wasClean);
if (e.code !== 1000) { if (e.code !== 1000) {
// toast('当前会话已结束'); // toast('当前会话已结束');
this.lockReturn = false this.lockReturn = false
clearTimeout(this.timeoutNum) clearTimeout(this.timeoutNum)
wsConnection.reconnect(); wsConnection.reconnect();
// store.commit('set_otherTips', "洋洋客服(未连接)"); // store.commit('set_otherTips', "洋洋客服(未连接)");
document.setTitle('洋洋客服(未连接)') document.setTitle('洋洋客服(未连接)')
//当前转成机器人 //当前转成机器人
// store.dispatch('setTransferCustomer', 1); // store.dispatch('setTransferCustomer', 1);
} else { } else {
this.$ws = null; this.$ws = null;
clearInterval(this.timeoutObj) clearInterval(this.timeoutObj)
clearTimeout(this.serverTimeoutObj) clearTimeout(this.serverTimeoutObj)
// toast('会话已关闭'); // toast('会话已关闭');
// store.commit('set_otherTips', "洋洋客服"); // store.commit('set_otherTips', "洋洋客服");
document.setTitle('洋洋客服') document.setTitle('洋洋客服')
wsConnection.$ws.close(); wsConnection.$ws.close();
} }
}, },
}; };
//时间格式化 //时间格式化
Date.prototype.format = function (fmt) { Date.prototype.format = function (fmt) {
var o = { var o = {
"M+": this.getMonth() + 1, //月份 "M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日 "d+": this.getDate(), //日
"h+": this.getHours(), //小时 "h+": this.getHours(), //小时
"m+": this.getMinutes(), //分 "m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒 "s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度 "q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒 "S": this.getMilliseconds() //毫秒
}; };
if (/(y+)/.test(fmt)) { if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
} }
for (var k in o) { for (var k in o) {
if (new RegExp("(" + k + ")").test(fmt)) { if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
} }
} }
return fmt; return fmt;
} }
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 // 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function (event) { window.onbeforeunload = function (event) {
event = event || window.event; event = event || window.event;
try { try {
// toast('onbeforeunload关闭当前会话窗口啦'); // toast('onbeforeunload关闭当前会话窗口啦');
var oldSessionId = localStorage.getItem('sessionId'); var oldSessionId = localStorage.getItem('sessionId');
var liccsAds = localStorage.getItem('liccsAds'); // 记录用户弹窗提示 var liccsAds = localStorage.getItem('liccsAds'); // 记录用户弹窗提示
// sessionStorage.removeItem("manualService"); // sessionStorage.removeItem("manualService");
if (wsConnection.$ws) { if (wsConnection.$ws) {
wsConnection.$ws.close(); wsConnection.$ws.close();
} }
//清除本地缓存 //清除本地缓存
localStorage.clear(); localStorage.clear();
localStorage.sessionId = oldSessionId || ''; localStorage.sessionId = oldSessionId || '';
localStorage.liccsAds = liccsAds || ''; localStorage.liccsAds = liccsAds || '';
//清除session //清除session
sessionStorage.clear(); sessionStorage.clear();
} catch (error) { } catch (error) {
console.log(error.message) console.log(error.message)
} }
} }
//安卓处理解决方案 //安卓处理解决方案
window.onunload = function (event) { window.onunload = function (event) {
event = event || window.event; event = event || window.event;
try { try {
// toast('onunload关闭当前会话窗口啦'); // toast('onunload关闭当前会话窗口啦');
var oldSessionId = localStorage.getItem('sessionId'); var oldSessionId = localStorage.getItem('sessionId');
var liccsAds = localStorage.getItem('liccsAds'); // 记录用户弹窗提示 var liccsAds = localStorage.getItem('liccsAds'); // 记录用户弹窗提示
// sessionStorage.removeItem("manualService"); // sessionStorage.removeItem("manualService");
if (wsConnection.$ws) { if (wsConnection.$ws) {
wsConnection.$ws.close(); wsConnection.$ws.close();
} }
//清除本地缓存 //清除本地缓存
localStorage.clear(); localStorage.clear();
localStorage.sessionId = oldSessionId || ''; localStorage.sessionId = oldSessionId || '';
localStorage.liccsAds = liccsAds || ''; localStorage.liccsAds = liccsAds || '';
//清除session //清除session
sessionStorage.clear(); sessionStorage.clear();
} catch (error) { } catch (error) {
console.log(error.message) console.log(error.message)
} }
} }
//抛出websocket对象 //抛出websocket对象
export default wsConnection export default wsConnection

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,10 @@
<template>
<div>
vue3
</div>
</template>
<script setup>
defineOptions({
name: 'word',
})
</script>

@ -19,14 +19,6 @@ export default defineConfig(({ command }) => {
changeOrigin: true, changeOrigin: true,
ws: true, ws: true,
}, },
'/api': {
// target: 'https://aioblu-sit.cpic.com.cn',
// target: 'https://aioblu-dev.cpic.com.cn/yyfw',
target: 'https://ssp-sit.cpic.com.cn',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
}, },
}, },
plugins: [ plugins: [

Loading…
Cancel
Save