parent
f22713668a
commit
3f88043322
@ -1,154 +1,162 @@ |
||||
<script setup> |
||||
import { RouterView, useRoute } from 'vue-router' |
||||
import { onMounted, onUnmounted, provide } from 'vue' |
||||
import { storeToRefs } from 'pinia' |
||||
import { useAppStore } from '@/store/app.js' |
||||
// import { leaveProject } from '@/utils/api.js' |
||||
import wx from 'weixin-js-sdk' |
||||
const route = useRoute() |
||||
import { isIOS, isAndroid, isPureHarmonyOS, handleFontSize, detectFontSize } from '@/utils/common' |
||||
console.log('navigator.userAgent', navigator.userAgent, navigator.userAgentData) |
||||
window.wx = wx |
||||
|
||||
// 全局 provide 接线:子组件统一通过 inject('appStore') / inject('xxx') 使用 store |
||||
const appStore = useAppStore() |
||||
const appStoreRefs = storeToRefs(appStore) |
||||
provide('appStore', appStore) |
||||
Object.keys(appStoreRefs).forEach((key) => { |
||||
provide(key, appStoreRefs[key]) |
||||
}) |
||||
|
||||
const handleGlobalBrowserExit = (reason, extra = {}) => { |
||||
const payload = { |
||||
reason, |
||||
href: window.location.href, |
||||
route: route.fullPath, |
||||
timestamp: Date.now(), |
||||
...extra, |
||||
} |
||||
// leaveProject() |
||||
// alert('关闭页面') |
||||
|
||||
console.log('📴 全局检测到浏览器关闭/返回动作', payload) |
||||
|
||||
// 在这里统一处理关闭浏览器、手势返回、刷新离开等动作。 |
||||
// 如果后续需要接业务方法,直接在这里调用即可。 |
||||
// example: |
||||
// someBusinessMethod(payload) |
||||
} |
||||
|
||||
const handleBrowserPopState = () => { |
||||
handleGlobalBrowserExit('popstate') |
||||
} |
||||
|
||||
const handleBrowserPageHide = (event) => { |
||||
handleGlobalBrowserExit('pagehide', { |
||||
persisted: Boolean(event?.persisted), |
||||
}) |
||||
} |
||||
|
||||
const handleBrowserBeforeUnload = () => { |
||||
handleGlobalBrowserExit('beforeunload') |
||||
} |
||||
|
||||
onMounted(() => { |
||||
var html = document.getElementsByTagName('html')[0] |
||||
|
||||
// 初始化 |
||||
function init() { |
||||
console.log('navigator.userAgent', navigator.userAgent, navigator.userAgentData) |
||||
console.log('handleFontSize called with:', localStorage.getItem('bigFont')) |
||||
if (localStorage.getItem('bigFont') === 'true') |
||||
document.getElementsByTagName('html')[0].style.fontSize = '0.79333275px' |
||||
if (localStorage.getItem('localfontSize')) |
||||
html.style.fontSize = localStorage.getItem('localfontSize') |
||||
// 监听WeixinJSBridge |
||||
if (typeof WeixinJSBridge == 'object' && typeof WeixinJSBridge.invoke == 'function') { |
||||
detectFontSize() |
||||
handleFontSize() |
||||
} else { |
||||
if (document.addEventListener) { |
||||
document.addEventListener( |
||||
'WeixinJSBridgeReady', |
||||
function () { |
||||
detectFontSize() |
||||
handleFontSize() |
||||
}, |
||||
false |
||||
) |
||||
} else if (document.attachEvent) { |
||||
document.attachEvent('WeixinJSBridgeReady', function () { |
||||
detectFontSize() |
||||
handleFontSize() |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
init() |
||||
console.log(isPureHarmonyOS(), 'isPureHarmonyOS') |
||||
|
||||
window.onresize = function () { |
||||
if (!isIOS() && !isAndroid() && !isPureHarmonyOS()) init() |
||||
} |
||||
|
||||
window.addEventListener('popstate', handleBrowserPopState) |
||||
window.addEventListener('pagehide', handleBrowserPageHide) |
||||
window.addEventListener('beforeunload', handleBrowserBeforeUnload) |
||||
}) |
||||
|
||||
onUnmounted(() => { |
||||
window.removeEventListener('popstate', handleBrowserPopState) |
||||
window.removeEventListener('pagehide', handleBrowserPageHide) |
||||
window.removeEventListener('beforeunload', handleBrowserBeforeUnload) |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div id="app" class="app-wrapper opacity"> |
||||
<RouterView v-slot="{ Component }"> |
||||
<keep-alive include="chat"> |
||||
<component :is="Component" /> |
||||
<script setup> |
||||
import { RouterView, useRoute } from 'vue-router' |
||||
import { onMounted, onUnmounted, provide } from 'vue' |
||||
import { storeToRefs } from 'pinia' |
||||
import { useAppStore } from '@/store/app.js' |
||||
// import { leaveProject } from '@/utils/api.js' |
||||
import wx from 'weixin-js-sdk' |
||||
const route = useRoute() |
||||
import { isIOS, isAndroid, isPureHarmonyOS, handleFontSize, detectFontSize } from '@/utils/common' |
||||
console.log('navigator.userAgent', navigator.userAgent, navigator.userAgentData) |
||||
window.wx = wx |
||||
|
||||
// 全局 provide 接线:子组件统一通过 inject('appStore') / inject('xxx') 使用 store |
||||
const appStore = useAppStore() |
||||
const appStoreRefs = storeToRefs(appStore) |
||||
provide('appStore', appStore) |
||||
// Object.keys(appStoreRefs).forEach((key) => { |
||||
// provide(key, appStoreRefs[key]) |
||||
// }) |
||||
|
||||
const handleGlobalBrowserExit = (reason, extra = {}) => { |
||||
const payload = { |
||||
reason, |
||||
href: window.location.href, |
||||
route: route.fullPath, |
||||
timestamp: Date.now(), |
||||
...extra, |
||||
} |
||||
// leaveProject() |
||||
// alert('关闭页面') |
||||
|
||||
console.log('📴 全局检测到浏览器关闭/返回动作', payload) |
||||
|
||||
// 在这里统一处理关闭浏览器、手势返回、刷新离开等动作。 |
||||
// 如果后续需要接业务方法,直接在这里调用即可。 |
||||
// example: |
||||
// someBusinessMethod(payload) |
||||
} |
||||
|
||||
const handleBrowserPopState = () => { |
||||
handleGlobalBrowserExit('popstate') |
||||
} |
||||
|
||||
const handleBrowserPageHide = (event) => { |
||||
handleGlobalBrowserExit('pagehide', { |
||||
persisted: Boolean(event?.persisted), |
||||
}) |
||||
} |
||||
|
||||
const handleBrowserBeforeUnload = () => { |
||||
handleGlobalBrowserExit('beforeunload') |
||||
} |
||||
|
||||
onMounted(() => { |
||||
var html = document.getElementsByTagName('html')[0] |
||||
|
||||
// 初始化 |
||||
function init() { |
||||
console.log('navigator.userAgent', navigator.userAgent, navigator.userAgentData) |
||||
console.log('handleFontSize called with:', localStorage.getItem('bigFont')) |
||||
if (localStorage.getItem('bigFont') === 'true') |
||||
document.getElementsByTagName('html')[0].style.fontSize = '0.79333275px' |
||||
if (localStorage.getItem('localfontSize')) |
||||
html.style.fontSize = localStorage.getItem('localfontSize') |
||||
// 监听WeixinJSBridge |
||||
if (typeof WeixinJSBridge == 'object' && typeof WeixinJSBridge.invoke == 'function') { |
||||
detectFontSize() |
||||
handleFontSize() |
||||
} else { |
||||
if (document.addEventListener) { |
||||
document.addEventListener( |
||||
'WeixinJSBridgeReady', |
||||
function () { |
||||
detectFontSize() |
||||
handleFontSize() |
||||
}, |
||||
false |
||||
) |
||||
} else if (document.attachEvent) { |
||||
document.attachEvent('WeixinJSBridgeReady', function () { |
||||
detectFontSize() |
||||
handleFontSize() |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
init() |
||||
console.log(isPureHarmonyOS(), 'isPureHarmonyOS') |
||||
window.onresize = function () { |
||||
if (!isIOS() && !isAndroid() && !isPureHarmonyOS()) init() |
||||
} |
||||
|
||||
window.addEventListener('popstate', handleBrowserPopState) |
||||
window.addEventListener('pagehide', handleBrowserPageHide) |
||||
window.addEventListener('beforeunload', handleBrowserBeforeUnload) |
||||
}) |
||||
|
||||
onUnmounted(() => { |
||||
window.removeEventListener('popstate', handleBrowserPopState) |
||||
window.removeEventListener('pagehide', handleBrowserPageHide) |
||||
window.removeEventListener('beforeunload', handleBrowserBeforeUnload) |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="app-wrapper opacity"> |
||||
<RouterView v-slot="{ Component, route }"> |
||||
<keep-alive> |
||||
<component |
||||
:is="Component" |
||||
v-if="Component && route.meta.keepAlive" |
||||
:key="route.name" |
||||
/> |
||||
</keep-alive> |
||||
<component |
||||
:is="Component" |
||||
v-if="Component && !route.meta.keepAlive" |
||||
:key="route.name" |
||||
/> |
||||
</RouterView> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped> |
||||
.header-box { |
||||
width: 100%; |
||||
height: 88rem; |
||||
display: flex; |
||||
align-items: center; |
||||
background: #e8f2ff; |
||||
padding: 0 24rem 0 54rem; |
||||
} |
||||
|
||||
.view { |
||||
width: 100vw; |
||||
height: 100vh; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.left-box { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
|
||||
.back-icon { |
||||
display: block; |
||||
width: 18rem; |
||||
height: 32rem; |
||||
} |
||||
|
||||
.back-icon img { |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
</style> |
||||
<style scoped lang="scss"> |
||||
#app { |
||||
.con { |
||||
color: red; |
||||
} |
||||
} |
||||
</style> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped> |
||||
.header-box { |
||||
width: 100%; |
||||
height: 88rem; |
||||
display: flex; |
||||
align-items: center; |
||||
background: #e8f2ff; |
||||
padding: 0 24rem 0 54rem; |
||||
} |
||||
|
||||
.view { |
||||
width: 100vw; |
||||
height: 100vh; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.left-box { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
|
||||
.back-icon { |
||||
display: block; |
||||
width: 18rem; |
||||
height: 32rem; |
||||
} |
||||
|
||||
.back-icon img { |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
</style> |
||||
<style scoped lang="scss"> |
||||
#app { |
||||
.con { |
||||
color: red; |
||||
} |
||||
} |
||||
</style> |
||||
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
@ -1,301 +1,340 @@ |
||||
<script setup> |
||||
import { ref, onMounted, computed, inject } from 'vue'; |
||||
import { Post } from '@/utils/https' |
||||
import { messageSend } from '@/utils/api' |
||||
import { getHiddenProp, getVisibilityState, decryptByVersionNo1, isSit, Encrypt, isDev, isProd } from "@/utils"; |
||||
const store = inject('appStore', null) |
||||
const businessHandleInfo = ref({}) |
||||
const orderCount = ref(0) |
||||
const abstractList = computed(() => [ |
||||
{ |
||||
title: '保障状态', |
||||
num: orderCount.value, |
||||
text: '保单保障中', |
||||
unit: '张', |
||||
name: '查保单', |
||||
btnText: '查保单', |
||||
btnBg: 'linear-gradient(90deg, #025cea 0%, #0595f6 100%)', |
||||
}, |
||||
{ |
||||
title: '待办任务', |
||||
num: businessHandleInfo.value?.contents?.length || 0, |
||||
text: '待办需处理', |
||||
unit: '个', |
||||
name: '去处理', |
||||
btnText: '去处理', |
||||
btnBg: 'linear-gradient(111deg, #ff752a 44%, #feb52d 136%)', |
||||
}, |
||||
{ |
||||
title: '权益领取', |
||||
num: 0, |
||||
text: '生存金权益', |
||||
unit: '项', |
||||
name: '去领取', |
||||
btnText: '去领取', |
||||
btnBg: 'linear-gradient(261deg, #2ed0c6 3%, #08b7ac 65%)', |
||||
}, |
||||
].filter(item => item.num > 0)) |
||||
const businessHandle = () => { |
||||
if ( |
||||
store.source == "95270000" || |
||||
store.source == "95320000" || |
||||
store.source == "95350000" || |
||||
store.source == "96060000" |
||||
) { |
||||
Post("notify/info", { userInfoId: store.userId }) |
||||
.then((res) => { |
||||
if (res.data?.contents.length > 0) { |
||||
businessHandleInfo.value = res.data; |
||||
} |
||||
}) |
||||
.catch((error) => { |
||||
console.log(error); |
||||
}); |
||||
} |
||||
} |
||||
const isIdentityNo = (value) => /^\d{15}$|^\d{17}[\dXx]$/.test(String(value || '').trim()) |
||||
|
||||
// 与 liics 一致:明文身份证来自解密 userInfo.pid(encryptPid 是给外部系统用的密文,不能直接解) |
||||
const getIdentityCardno = () => { |
||||
const versionNo = store.userInfo?.versionNo1 || '' |
||||
const pidCipher = store.userInfo?.pid || '' |
||||
if (!versionNo || !pidCipher) return '' |
||||
|
||||
const identityCardno = decryptByVersionNo1(versionNo, pidCipher) |
||||
console.log(decryptByVersionNo1(versionNo, '0FLzZwhD08nkUE4BjRGOSEri4KWO'),id); |
||||
return isIdentityNo(identityCardno) ? identityCardno.trim() : '' |
||||
} |
||||
|
||||
const getOrderCount = () => { |
||||
try { |
||||
const identityCardno = getIdentityCardno() |
||||
if (!identityCardno) { |
||||
console.warn('证件号解密失败', { |
||||
hasPid: Boolean(store.userInfo?.pid), |
||||
hasVersionNo1: Boolean(store.userInfo?.versionNo1), |
||||
}) |
||||
return |
||||
} |
||||
|
||||
Post('policy/queryCount', { |
||||
dentityCardno: identityCardno, |
||||
userId: store.userId |
||||
}).then(res => { |
||||
orderCount.value = res?.data?.policyCount || 0 |
||||
}) |
||||
} catch (error) { |
||||
console.log(error) |
||||
} |
||||
} |
||||
const toDetail = (record) => { |
||||
const text = record?.name |
||||
console.log('text', text); |
||||
if (!text) return |
||||
messageSend(text).catch((err) => { |
||||
console.log(err) |
||||
}) |
||||
} |
||||
onMounted(async () => { |
||||
businessHandle() |
||||
getOrderCount() |
||||
}) |
||||
</script> |
||||
<template> |
||||
<div class="abstract-con" v-if="abstractList.length"> |
||||
<div class="title"> |
||||
<span>今日皮卡丘摘要</span><img src="@/assets/images/chat/star.svg" alt="" srcset="" /> |
||||
</div> |
||||
<div class="list"> |
||||
<div :class="`item ${(abstractList || []).length === 1 ? 'singleItem' : ''}`" |
||||
v-for="(item, index) in abstractList || []" :key="index"> |
||||
<div> |
||||
<div class="title">{{ item.title }}</div> |
||||
<div class="content" :style="{ display: (abstractList || []).length > 2 ? 'block' : 'flex' }"> |
||||
<div class="nums"> |
||||
<div class="num"> |
||||
{{ item.num }}<span>{{ item.unit }}</span> |
||||
</div> |
||||
</div> |
||||
<div class="text" :style="{ marginTop: (abstractList || []).length > 2 ? '8rem' : '0' }"> |
||||
{{ item.text }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="btn" :style="{ background: item.btnBg }" @click="toDetail(item)">{{ item.btnText }}</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<style lang="scss" scoped> |
||||
.abstract-con { |
||||
position: relative; |
||||
z-index: 1; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
padding: 10rem 12rem; |
||||
gap: 12rem; |
||||
align-self: stretch; |
||||
margin-top: 10rem; |
||||
background: linear-gradient(77deg, #fcfdfd80 -1%, #f5f7fee6 99%); |
||||
|
||||
box-sizing: border-box; |
||||
border: 1rem solid rgba(255, 255, 255, 0.9); |
||||
|
||||
backdrop-filter: blur(10rem); |
||||
border-radius: 12rem; |
||||
|
||||
.title { |
||||
font-family: PingFang SC; |
||||
font-size: 16rem; |
||||
font-weight: 500; |
||||
line-height: normal; |
||||
letter-spacing: 0rem; |
||||
|
||||
/* 科个中性/正文-标题 17204D */ |
||||
/* 样式描述:用于正文标题 */ |
||||
color: #17204d; |
||||
|
||||
img { |
||||
width: 20rem; |
||||
margin-left: 4.6rem; |
||||
} |
||||
} |
||||
|
||||
.list { |
||||
margin-top: 13rem; |
||||
display: flex; |
||||
gap: 8rem; |
||||
|
||||
.item { |
||||
display: flex; |
||||
flex-direction: column; |
||||
gap: 12rem; |
||||
flex: 1; |
||||
border-radius: 8rem; |
||||
display: flex; |
||||
flex-direction: column; |
||||
padding: 12rem; |
||||
gap: 12rem; |
||||
|
||||
background: #fcfdfd; |
||||
|
||||
box-shadow: 0rem 4rem 10rem 0rem #e7effd; |
||||
|
||||
.title { |
||||
margin-left: 18rem; |
||||
font-family: PingFang SC; |
||||
font-size: 12rem; |
||||
font-weight: normal; |
||||
line-height: 16rem; |
||||
letter-spacing: 0rem; |
||||
|
||||
/* 科个中性/正文-副标题 7E849F */ |
||||
color: #7e849f; |
||||
} |
||||
|
||||
.content { |
||||
.nums { |
||||
font-family: DIN-Medium; |
||||
font-size: 24rem; |
||||
font-weight: bold; |
||||
line-height: 24rem; |
||||
letter-spacing: 0rem; |
||||
|
||||
font-variation-settings: 'opsz' auto; |
||||
color: #17204d; |
||||
|
||||
.num { |
||||
display: flex; |
||||
align-items: flex-end; |
||||
} |
||||
|
||||
span { |
||||
font-family: PingFang SC; |
||||
font-size: 12rem; |
||||
font-weight: normal; |
||||
line-height: 16rem; |
||||
letter-spacing: 0rem; |
||||
margin: 0 3rem 0 3rem; |
||||
|
||||
/* 中性/重要文本色 */ |
||||
/* 样式描述:17204D */ |
||||
color: #17204d; |
||||
} |
||||
} |
||||
|
||||
.text { |
||||
font-family: PingFang SC; |
||||
font-size: 12rem; |
||||
font-weight: normal; |
||||
line-height: 16rem; |
||||
letter-spacing: 0rem; |
||||
|
||||
/* 中性/次要文本色 */ |
||||
/* 样式描述:7E849F */ |
||||
color: #7e849f; |
||||
align-self: flex-end; |
||||
} |
||||
} |
||||
|
||||
.btn { |
||||
border-radius: 100rem; |
||||
font-family: PingFang SC; |
||||
font-size: 14rem; |
||||
font-weight: 500; |
||||
line-height: 19rem; |
||||
text-align: center; |
||||
letter-spacing: 0rem; |
||||
|
||||
color: #ffffff; |
||||
padding: 4rem 12rem; |
||||
height: 28rem; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
|
||||
.singleItem { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
|
||||
>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> |
||||
<script setup> |
||||
import { ref, onMounted, onActivated, computed, inject } from 'vue'; |
||||
import { Post } from '@/utils/https' |
||||
import { messageSend } from '@/utils/api' |
||||
import { getHiddenProp, getVisibilityState, decryptByVersionNo1, isSit, Encrypt, isDev, isProd } from "@/utils"; |
||||
const store = inject('appStore', null) |
||||
const businessHandleInfo = ref({}) |
||||
const orderCount = ref(0) |
||||
const emit = defineEmits(['ready', 'businessClick']) |
||||
const abstractList = computed(() => [ |
||||
{ |
||||
title: '保障状态', |
||||
num: orderCount.value, |
||||
text: '保单保障中', |
||||
unit: '张', |
||||
name: '查保单', |
||||
btnText: '查保单', |
||||
btnBg: 'linear-gradient(90deg, #025cea 0%, #0595f6 100%)', |
||||
}, |
||||
{ |
||||
title: '待办任务', |
||||
num: businessHandleInfo.value?.contents?.length || 0, |
||||
text: '待办需处理', |
||||
unit: '个', |
||||
name: '去处理', |
||||
btnText: '去处理', |
||||
btnBg: 'linear-gradient(111deg, #ff752a 44%, #feb52d 136%)', |
||||
}, |
||||
{ |
||||
title: '权益领取', |
||||
num: 0, |
||||
text: '生存金权益', |
||||
unit: '项', |
||||
name: '去领取', |
||||
btnText: '去领取', |
||||
btnBg: 'linear-gradient(261deg, #2ed0c6 3%, #08b7ac 65%)', |
||||
}, |
||||
].filter(item => item.num > 0)) |
||||
const businessHandle = (userId) => { |
||||
if ( |
||||
store.source == "95270000" || |
||||
store.source == "95320000" || |
||||
store.source == "95350000" || |
||||
store.source == "96060000" |
||||
) { |
||||
return Post("notify/info", { userInfoId: userId }) |
||||
.then((res) => { |
||||
if (res.data?.contents?.length > 0) { |
||||
businessHandleInfo.value = res.data |
||||
} else { |
||||
businessHandleInfo.value = {} |
||||
} |
||||
store?.setBusinessHandleInfo?.(businessHandleInfo.value) |
||||
}) |
||||
.catch((error) => { |
||||
console.log(error); |
||||
}); |
||||
} |
||||
return Promise.resolve() |
||||
} |
||||
|
||||
const businessClick = () => { |
||||
emit('businessClick') |
||||
} |
||||
const isIdentityNo = (value) => /^\d{15}$|^\d{17}[\dXx]$/.test(String(value || '').trim()) |
||||
|
||||
// 与 liics 一致:明文身份证来自解密 userInfo.pid(encryptPid 是给外部系统用的密文,不能直接解) |
||||
const getIdentityCardno = () => { |
||||
const versionNo = store.userInfo?.versionNo1 || '' |
||||
const pidCipher = store.userInfo?.pid || '' |
||||
if (!versionNo || !pidCipher) return '' |
||||
|
||||
const identityCardno = decryptByVersionNo1(versionNo, pidCipher) |
||||
console.log('decrypt pid =>', JSON.stringify(identityCardno)) |
||||
return isIdentityNo(identityCardno) ? identityCardno.trim() : '' |
||||
} |
||||
|
||||
const getOrderCount = () => { |
||||
try { |
||||
const identityCardno = getIdentityCardno() |
||||
if (!identityCardno) { |
||||
console.warn('证件号解密失败', { |
||||
hasPid: Boolean(store.userInfo?.pid), |
||||
hasVersionNo1: Boolean(store.userInfo?.versionNo1), |
||||
}) |
||||
return Promise.resolve() |
||||
} |
||||
|
||||
return Post('policy/queryCount', { |
||||
dentityCardno: identityCardno, |
||||
userId: store.userId |
||||
}).then(res => { |
||||
orderCount.value = res?.data?.policyCount || 0 |
||||
}).catch((error) => { |
||||
console.log(error) |
||||
}) |
||||
} catch (error) { |
||||
console.log(error) |
||||
return Promise.resolve() |
||||
} |
||||
} |
||||
const toDetail = (record) => { |
||||
if (record.title === '待办任务') { |
||||
businessClick() |
||||
} |
||||
if (record.title === '保障状态') { |
||||
// 只把问题写入对话气泡,不走对话接口 |
||||
store.addSendMessageList({ msgText: record.name, reqAndResType: '1', sendLoading: false }) |
||||
} |
||||
} |
||||
const refreshAbstractData = async () => { |
||||
await Promise.all([businessHandle(store?.userId), getOrderCount()]) |
||||
} |
||||
|
||||
const hasLoadedOnce = ref(false) |
||||
|
||||
onMounted(async () => { |
||||
try { |
||||
await refreshAbstractData() |
||||
} finally { |
||||
hasLoadedOnce.value = true |
||||
emit('ready') |
||||
} |
||||
}) |
||||
|
||||
// keep-alive 返回 chat 时刷新待办和保单数量 |
||||
onActivated(async () => { |
||||
if (!hasLoadedOnce.value) return |
||||
await refreshAbstractData() |
||||
}) |
||||
</script> |
||||
<template> |
||||
<div class="abstract-con" v-if="abstractList.length"> |
||||
<div class="title"> |
||||
<span>今日皮卡丘摘要</span><img src="@/assets/images/chat/star.svg" alt="" srcset="" /> |
||||
</div> |
||||
<div class="list"> |
||||
<div |
||||
:class="`item ${(abstractList || []).length === 1 ? 'singleItem' : ''}`" |
||||
v-for="(item, index) in abstractList || []" |
||||
:key="index" |
||||
> |
||||
<div> |
||||
<div class="title">{{ item.title }}</div> |
||||
<div |
||||
class="content" |
||||
:style="{ display: (abstractList || []).length > 2 ? 'block' : 'flex' }" |
||||
> |
||||
<div class="nums"> |
||||
<div class="num"> |
||||
{{ item.num }}<span>{{ item.unit }}</span> |
||||
</div> |
||||
</div> |
||||
<div |
||||
class="text" |
||||
:style="{ marginTop: (abstractList || []).length > 2 ? '8rem' : '0' }" |
||||
> |
||||
{{ item.text }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="btn" :style="{ background: item.btnBg }" @click="toDetail(item)"> |
||||
{{ item.btnText }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<style lang="scss" scoped> |
||||
.abstract-con { |
||||
position: relative; |
||||
z-index: 1; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
padding: 10rem 12rem; |
||||
gap: 12rem; |
||||
align-self: stretch; |
||||
background: linear-gradient(77deg, #fcfdfd80 -1%, #f5f7fee6 99%); |
||||
margin-bottom: 16rem; |
||||
box-sizing: border-box; |
||||
border: 1rem solid rgba(255, 255, 255, 0.9); |
||||
|
||||
backdrop-filter: blur(10rem); |
||||
border-radius: 12rem; |
||||
|
||||
.title { |
||||
font-family: PingFang SC; |
||||
font-size: 16rem; |
||||
font-weight: 500; |
||||
line-height: normal; |
||||
letter-spacing: 0rem; |
||||
|
||||
/* 科个中性/正文-标题 17204D */ |
||||
/* 样式描述:用于正文标题 */ |
||||
color: #17204d; |
||||
|
||||
img { |
||||
width: 20rem; |
||||
margin-left: 4.6rem; |
||||
} |
||||
} |
||||
|
||||
.list { |
||||
margin-top: 13rem; |
||||
display: flex; |
||||
gap: 8rem; |
||||
|
||||
.item { |
||||
display: flex; |
||||
flex-direction: column; |
||||
gap: 12rem; |
||||
flex: 1; |
||||
border-radius: 8rem; |
||||
display: flex; |
||||
flex-direction: column; |
||||
padding: 12rem; |
||||
gap: 12rem; |
||||
|
||||
background: #fcfdfd; |
||||
|
||||
box-shadow: 0rem 4rem 10rem 0rem #e7effd; |
||||
|
||||
.title { |
||||
margin-left: 18rem; |
||||
font-family: PingFang SC; |
||||
font-size: 12rem; |
||||
font-weight: normal; |
||||
line-height: 16rem; |
||||
letter-spacing: 0rem; |
||||
|
||||
/* 科个中性/正文-副标题 7E849F */ |
||||
color: #7e849f; |
||||
} |
||||
|
||||
.content { |
||||
.nums { |
||||
font-family: DIN-Medium; |
||||
font-size: 24rem; |
||||
font-weight: bold; |
||||
line-height: 24rem; |
||||
letter-spacing: 0rem; |
||||
|
||||
font-variation-settings: 'opsz' auto; |
||||
color: #17204d; |
||||
|
||||
.num { |
||||
display: flex; |
||||
align-items: flex-end; |
||||
} |
||||
|
||||
span { |
||||
font-family: PingFang SC; |
||||
font-size: 12rem; |
||||
font-weight: normal; |
||||
line-height: 16rem; |
||||
letter-spacing: 0rem; |
||||
margin: 0 3rem 0 3rem; |
||||
|
||||
/* 中性/重要文本色 */ |
||||
/* 样式描述:17204D */ |
||||
color: #17204d; |
||||
} |
||||
} |
||||
|
||||
.text { |
||||
font-family: PingFang SC; |
||||
font-size: 12rem; |
||||
font-weight: normal; |
||||
line-height: 16rem; |
||||
letter-spacing: 0rem; |
||||
|
||||
/* 中性/次要文本色 */ |
||||
/* 样式描述:7E849F */ |
||||
color: #7e849f; |
||||
align-self: flex-end; |
||||
} |
||||
} |
||||
|
||||
.btn { |
||||
border-radius: 100rem; |
||||
font-family: PingFang SC; |
||||
font-size: 14rem; |
||||
font-weight: 500; |
||||
line-height: 19rem; |
||||
text-align: center; |
||||
letter-spacing: 0rem; |
||||
|
||||
color: #ffffff; |
||||
padding: 4rem 12rem; |
||||
height: 28rem; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
|
||||
.singleItem { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
|
||||
> 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> |
||||
<div> |
||||
<van-swipe style="height:30rem;margin-top:10rem" class="businessBox" vertical :scrollable="false" :autoplay="3000" |
||||
:show-indicators="false"> |
||||
<van-swipe-item @click="swiperClick" v-for="(item, index) in businessHandleInfo.contents" :key="index"> |
||||
<div class="item"> |
||||
<img :src="getIconUrl(item.icon)" alt="" style="width:30rem;height:30rem;"> |
||||
<div class="item-content"> |
||||
<div class="line">{{ item.name }}</div> |
||||
<van-icon name="arrow-down" /> |
||||
</div> |
||||
</div> |
||||
</van-swipe-item> |
||||
</van-swipe> |
||||
|
||||
<van-popup get-container="#app" v-model:show="Popupshow" round position="bottom" |
||||
:style="{ padding: '15rem', boxSizing: ' border-box' }"> |
||||
<div class="displayFlex"> |
||||
<div style="font-size: 13rem;color: #878D99;"> |
||||
您有{{ businessHandleInfo.contents.length }}个待办{{ businessHandleInfo.tip }}</div> |
||||
<van-icon name="close" color="#5A9CFF" @click="Popupshow = false" /> |
||||
</div> |
||||
<div class="popupContent"> |
||||
<div class="item popupContentItem" v-for="(item, index) in businessHandleInfo.contents" :key="index" |
||||
@click="richCallAnswer(item)"> |
||||
<img :src="getIconUrl(item.icon)" alt="" style="width:30rem;height:30rem;"> |
||||
<div class="item-content"> |
||||
<div>{{ item.name }}</div> |
||||
<van-icon name="arrow" color="#5A9CFF" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</van-popup> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import { ref, inject, computed } from 'vue' |
||||
import { Icon, Swipe, SwipeItem, Popup } from 'vant' |
||||
|
||||
defineProps({ |
||||
businessHandleInfo: { type: Object, default: () => ({}) } |
||||
}) |
||||
|
||||
const store = inject('appStore', null) |
||||
const userId = inject('userId', ref('')) |
||||
const mescrollContent = inject('mescrollContent', ref(null)) |
||||
const manualService = inject('manualService', ref('1')) |
||||
|
||||
const Popupshow = ref(false) |
||||
const isBtnsubmit = ref(true) |
||||
|
||||
function getIconUrl(icon) { |
||||
try { |
||||
return require(`../assets/images/${icon}.png`) |
||||
} catch (e) { |
||||
return '' |
||||
} |
||||
} |
||||
|
||||
function swiperClick() { |
||||
Popupshow.value = true |
||||
} |
||||
|
||||
function richCallAnswer(item) { |
||||
const text = item.name |
||||
const serialNumber = item.serialNumber |
||||
const sessionId = item.sessionId |
||||
|
||||
const mdParmas = { |
||||
userInfoId: userId.value, |
||||
sessionId, |
||||
serialNumber, |
||||
clickTime: new Date().getTime() |
||||
} |
||||
store?.dispatch?.('updateClickTime', mdParmas).catch(err => { |
||||
console.error(err) |
||||
}) |
||||
|
||||
let sendParmas = { |
||||
cmd: 'business-handle', |
||||
reqAndResType: '1', |
||||
msgText: '&' + text + '&', |
||||
intentId: '', |
||||
msgSource: manualService.value, |
||||
questionType: 6 |
||||
} |
||||
|
||||
if (isBtnsubmit.value) { |
||||
isBtnsubmit.value = false |
||||
Popupshow.value = false |
||||
scrollTopBody() |
||||
store?.dispatch?.('sendingRobot', sendParmas).then(() => { |
||||
isBtnsubmit.value = true |
||||
}).catch(err => { |
||||
isBtnsubmit.value = true |
||||
console.log(err) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
function scrollTopBody() { |
||||
setTimeout(() => { |
||||
try { |
||||
if (mescrollContent.value) { |
||||
mescrollContent.value.scrollTop = mescrollContent.value.scrollHeight || 0 |
||||
} |
||||
} catch (error) { |
||||
console.log(error.message) |
||||
} |
||||
}, 300) |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.businessBox { |
||||
background: #fff; |
||||
padding: 10rem; |
||||
border-radius: 16rem 16rem 16rem 2rem; |
||||
} |
||||
|
||||
.item { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-around; |
||||
} |
||||
|
||||
.item>img { |
||||
width: 30rem; |
||||
height: 30rem; |
||||
margin-right: 10rem; |
||||
} |
||||
|
||||
.item-content { |
||||
flex: 1; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
font-size: 15rem; |
||||
} |
||||
|
||||
.line { |
||||
font-size: 14rem; |
||||
} |
||||
|
||||
.displayFlex { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.popupContent { |
||||
min-height: 180rem; |
||||
max-height: 290rem; |
||||
overflow: auto; |
||||
} |
||||
|
||||
.popupContentItem { |
||||
height: 50rem; |
||||
background: #FFFFFF; |
||||
border: 1rem solid #F1F3F6; |
||||
padding: 13rem; |
||||
box-shadow: 0 2rem 8rem 0 rgba(229, 234, 237, 0.50); |
||||
border-radius: 8rem; |
||||
box-sizing: border-box; |
||||
margin: 7rem 5rem; |
||||
} |
||||
</style> |
||||
<template> |
||||
<div> |
||||
<!-- <van-swipe style="height:30rem;margin-top:10rem" class="businessBox" vertical :scrollable="false" :autoplay="3000" |
||||
:show-indicators="false"> |
||||
<van-swipe-item @click="swiperClick" v-for="(item, index) in businessHandleInfo.contents" :key="index"> |
||||
<div class="item"> |
||||
<img :src="getIconUrl(item.icon)" alt="" style="width:30rem;height:30rem;"> |
||||
<div class="item-content"> |
||||
<div class="line">{{ item.name }}</div> |
||||
<van-icon name="arrow-down" /> |
||||
</div> |
||||
</div> |
||||
</van-swipe-item> |
||||
</van-swipe> --> |
||||
<van-popup |
||||
v-model:show="Popupshow" |
||||
teleport="body" |
||||
round |
||||
position="bottom" |
||||
closeable |
||||
:z-index="3000" |
||||
> |
||||
<div class="displayFlex"> |
||||
<div class="title">您有{{ displayInfo?.contents?.length || 0 }}个待办</div> |
||||
<div class="popupContent"> |
||||
<div |
||||
class="item popupContentItem" |
||||
v-for="(item, index) in displayInfo.contents || []" |
||||
:key="index" |
||||
@click="richCallAnswer(item)" |
||||
> |
||||
<img |
||||
src="@/assets/images/chat/todo-icon.svg" |
||||
alt="" |
||||
style="width: 22rem; height: 22rem" |
||||
/> |
||||
<div class="item-content"> |
||||
<div>{{ item.name }}</div> |
||||
<van-icon name="arrow" color="#5A9CFF" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</van-popup> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import { ref, inject, computed, watch } from 'vue' |
||||
import { Icon, Swipe, SwipeItem, Popup } from 'vant' |
||||
|
||||
const props = defineProps({ |
||||
businessHandleInfo: { type: Object, default: () => ({}) }, |
||||
autoOpen: { type: Boolean, default: true }, |
||||
}) |
||||
|
||||
const store = inject('appStore', null) |
||||
const userId = inject('userId', ref('')) |
||||
const mescrollContent = inject('mescrollContent', ref(null)) |
||||
const manualService = inject('manualService', ref('1')) |
||||
|
||||
const Popupshow = ref(false) |
||||
const isBtnsubmit = ref(true) |
||||
const hasAutoOpened = ref(false) |
||||
|
||||
const displayInfo = computed(() => { |
||||
const fromProp = props.businessHandleInfo |
||||
if (fromProp && Array.isArray(fromProp.contents) && fromProp.contents.length) return fromProp |
||||
return store?.businessHandleInfo || {} |
||||
}) |
||||
|
||||
function getIconUrl(icon) { |
||||
try { |
||||
return new URL(`../assets/images/${icon}.png`, import.meta.url).href |
||||
} catch (e) { |
||||
return '' |
||||
} |
||||
} |
||||
|
||||
function swiperClick() { |
||||
Popupshow.value = true |
||||
} |
||||
|
||||
function richCallAnswer(item) { |
||||
const text = item.name |
||||
const serialNumber = item.serialNumber |
||||
const sessionId = item.sessionId |
||||
|
||||
const mdParmas = { |
||||
userInfoId: userId.value, |
||||
sessionId, |
||||
serialNumber, |
||||
clickTime: new Date().getTime() |
||||
} |
||||
store?.dispatch?.('updateClickTime', mdParmas).catch(err => { |
||||
console.error(err) |
||||
}) |
||||
|
||||
let sendParmas = { |
||||
cmd: 'business-handle', |
||||
reqAndResType: '1', |
||||
msgText: '&' + text + '&', |
||||
intentId: '', |
||||
msgSource: manualService.value, |
||||
questionType: 6 |
||||
} |
||||
|
||||
if (isBtnsubmit.value) { |
||||
isBtnsubmit.value = false |
||||
Popupshow.value = false |
||||
scrollTopBody() |
||||
const sending = store?.sendingRobot?.(sendParmas) || store?.dispatch?.('sendingRobot', sendParmas) |
||||
Promise.resolve(sending).then(() => { |
||||
isBtnsubmit.value = true |
||||
}).catch(err => { |
||||
isBtnsubmit.value = true |
||||
console.log(err) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
function scrollTopBody() { |
||||
setTimeout(() => { |
||||
try { |
||||
if (mescrollContent.value) { |
||||
mescrollContent.value.scrollTop = mescrollContent.value.scrollHeight || 0 |
||||
} |
||||
} catch (error) { |
||||
console.log(error.message) |
||||
} |
||||
}, 300) |
||||
} |
||||
defineExpose({ |
||||
swiperClick |
||||
}) |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.businessBox { |
||||
background: #fff; |
||||
padding: 10rem; |
||||
border-radius: 16rem 16rem 16rem 2rem; |
||||
} |
||||
|
||||
.item { |
||||
display: flex; |
||||
justify-content: space-around; |
||||
} |
||||
|
||||
.item > img { |
||||
width: 30rem; |
||||
height: 30rem; |
||||
margin-right: 10rem; |
||||
} |
||||
|
||||
.item-content { |
||||
flex: 1; |
||||
display: flex; |
||||
justify-content: space-between; |
||||
font-size: 15rem; |
||||
i{ |
||||
padding-top: 4rem; |
||||
} |
||||
} |
||||
|
||||
.line { |
||||
font-size: 14rem; |
||||
} |
||||
|
||||
.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
File diff suppressed because it is too large
Load Diff
@ -1,457 +1,457 @@ |
||||
<template> |
||||
<div class="text-template-box" v-if="introduce"> |
||||
<div |
||||
class="question-answer-con" |
||||
v-if="introduce" |
||||
ref="desc" |
||||
:class="showTotal || heightMax == 'autoHeight' ? 'total-introduce' : 'detailed-introduce'" |
||||
> |
||||
<p |
||||
:class="{ connectionArtificial: disabled }" |
||||
v-html="replacementRagEmoticon(introduce)" |
||||
:data-qaid="itemlist && itemlist.qaId ? itemlist.qaId : ''" |
||||
></p> |
||||
</div> |
||||
<div class="content-question-answer" v-if="intents && intents.length > 0"> |
||||
<button |
||||
class="answer-items" |
||||
v-for="(items, index) in intents" |
||||
:key="index" |
||||
:disabled="disabled ? true : false" |
||||
@click.stop="richCallAnswer(items)" |
||||
> |
||||
{{ items.intent }} |
||||
</button> |
||||
</div> |
||||
<div |
||||
class="unfold-box" |
||||
:class="exchangeButton ? 'unfold-bg' : 'unfold-style'" |
||||
v-if="(!intents || intents.length == 0) && showExchangeButton" |
||||
> |
||||
<a href="javascript:;" @click="showTotalIntro" |
||||
>{{ exchangeButton ? '展开' : '收起' |
||||
}}<em class="look-more-icon" :class="!exchangeButton ? 'active' : ''"></em |
||||
></a> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import { ref, inject, onMounted, nextTick } from 'vue' |
||||
import { showToast, showLoadingToast, closeToast } from 'vant' |
||||
import faceJSON from '@/mock/face.json' |
||||
import utils from '@/utils/utils' |
||||
import wx from 'weixin-js-sdk' |
||||
|
||||
const props = defineProps({ |
||||
introduce: { type: String, default: '' }, |
||||
intents: { type: Array, default: () => [] }, |
||||
disabled: { type: Boolean, default: false }, |
||||
heightMax: { type: String, default: '' }, |
||||
itemlist: { type: Object, default: () => ({}) }, |
||||
}) |
||||
|
||||
const store = inject('appStore', null) |
||||
const userId = inject('userId', ref('')) |
||||
const userInfo = inject('userInfo', ref({})) |
||||
const mescrollContent = inject('mescrollContent', ref(null)) |
||||
const manualService = inject('manualService', ref('1')) |
||||
const sendLoading = inject('sendLoading', ref(false)) |
||||
|
||||
const publicPath = import.meta.env.BASE_URL |
||||
const showTotal = ref(true) |
||||
const exchangeButton = ref(true) |
||||
const showExchangeButton = ref(false) |
||||
const expressionProcessing = ref({}) |
||||
const bigExpressionProcessing = ref({}) |
||||
const isBtnsubmit = ref(true) |
||||
const desc = ref(null) |
||||
|
||||
onMounted(() => { |
||||
processedEmoji() |
||||
descHeightChange() |
||||
nextTick(() => { |
||||
initGlobalCallbacks() |
||||
}) |
||||
}) |
||||
|
||||
function initGlobalCallbacks() { |
||||
try { |
||||
window.getLink = (item) => { |
||||
utils.AppToHttpHtml(item) |
||||
} |
||||
window.jumpOpenLink = (item) => { |
||||
utils.AppToHttpHtml(item) |
||||
} |
||||
|
||||
window.jumpOpenWeb = (type, url, urlType) => { |
||||
let params = {} |
||||
if (type === 'YY_0000') { |
||||
params = { |
||||
idnum: '1', |
||||
ownerId: userInfo.value?.encryptPid, |
||||
openid: userInfo.value?.sourceUserId, |
||||
} |
||||
const completeURL = handleURL(url, params) |
||||
store |
||||
?.dispatch?.('post', { |
||||
url: 'semantic/getYdbqTicket', |
||||
data: { url: completeURL, userId: userId.value }, |
||||
}) |
||||
.then((res) => { |
||||
if (res.code === 0) { |
||||
if (!res.data) { |
||||
utils.openWxPage(url, params) |
||||
} else { |
||||
utils.openWxPage(url, { accessTicket: res.data }) |
||||
} |
||||
} else { |
||||
showToast('网络异常,请重试') |
||||
} |
||||
}) |
||||
} else if (type === 'YY_ydbq') { |
||||
params = { |
||||
mobile: userInfo.value?.phone, |
||||
pid: userInfo.value?.encryptPid, |
||||
openid: userInfo.value?.sourceUserId, |
||||
} |
||||
const completeURL = handleURL(url, params) |
||||
store |
||||
?.dispatch?.('post', { |
||||
url: 'semantic/getYdbqTicket', |
||||
data: { url: completeURL, userId: userId.value }, |
||||
}) |
||||
.then((res) => { |
||||
if (res.code === 0) { |
||||
if (!res.data) { |
||||
utils.openWxPage(url, params) |
||||
} else { |
||||
utils.openWxPage(url, { accessTicket: res.data }) |
||||
} |
||||
} else { |
||||
showToast('网络异常,请重试') |
||||
} |
||||
}) |
||||
} else if (type === 'YY_APP_H5') { |
||||
utils.AppOpenH5(url, { mobile: userInfo.value?.phone, pid: userInfo.value?.encryptPid }) |
||||
} else if (type === 'ydbq_2') { |
||||
utils.openSpecPage({ url, generateType: type, userId: userId.value }) |
||||
} else if (type === 'YY_CMD') { |
||||
const newUrl = |
||||
url + |
||||
`&idnum=1&openId=${userInfo.value?.sourceUserId}&entryOpenid=${userInfo.value?.entryOpenid}` |
||||
store |
||||
?.dispatch?.('post', { |
||||
url: 'semantic/getYdbqTicket', |
||||
data: { url: newUrl, userId: userId.value, isCmd: '1' }, |
||||
}) |
||||
.then((res) => { |
||||
if (res.code === 0) { |
||||
if (!res.data) { |
||||
utils.AppToHttpHtml(newUrl) |
||||
} else { |
||||
utils.AppToHttpHtml(url + `&accessTicket=${res.data}`) |
||||
} |
||||
} else { |
||||
showToast('网络异常,请重试') |
||||
} |
||||
}) |
||||
} else if (type === 'YY_hfhz') { |
||||
store |
||||
?.dispatch?.('post', { |
||||
url: 'semantic/xdhfCreateToken', |
||||
data: { certNum: '1', systemSource: 'yykf', userId: userId.value }, |
||||
}) |
||||
.then((res) => { |
||||
const entryptedId = userInfo.value?.versionHFHZ |
||||
utils.openWxPage(url, { |
||||
sessionToken: res.data, |
||||
holderId: encodeURI(entryptedId), |
||||
holderType: 'a', |
||||
hzhfSystemSource: '0803', |
||||
ishwFlag: '1', |
||||
isqshzFlag: '1', |
||||
}) |
||||
}) |
||||
} else if (type === 'YY_1v1') { |
||||
store |
||||
?.dispatch?.('post', { url: 'thirdPartyAuth/getExclusiveServicesToken' }) |
||||
.then((res) => { |
||||
if (res.code === 0 && res.data) { |
||||
utils.openWxPage(url, { |
||||
sour: 'yykf', |
||||
omoSource: userInfo.value?.source, |
||||
omoSourceName: encodeURIComponent(userInfo.value?.sourceName), |
||||
token: res.data, |
||||
}) |
||||
} else { |
||||
showToast('网络异常,请重试') |
||||
} |
||||
}) |
||||
} else if (type === 'YY_yzsfx') { |
||||
store |
||||
?.dispatch?.('post', { |
||||
url: 'semantic/getYzsUrl', |
||||
data: { userId: userId.value, bqType: url, urlType }, |
||||
}) |
||||
.then((res) => { |
||||
if (res.code === 0 && res.data) { |
||||
utils.openWxPage(res.data, '') |
||||
} else { |
||||
showToast('网络异常,请重试') |
||||
} |
||||
}) |
||||
} else if (type === 'YY_GWXCX') { |
||||
wx.miniProgram.navigateTo({ url: url }) |
||||
} else if (type === 'YY_GWXCX_HOME') { |
||||
wx.miniProgram.reLaunch({ url: url }) |
||||
} |
||||
} |
||||
|
||||
window.openAppPage = (params) => { |
||||
if (params) { |
||||
params = params.replace(/\'/g, '"') |
||||
utils.AppToOutModules(params) |
||||
} |
||||
} |
||||
window.connectionArtificial = (event) => { |
||||
let isClick = false, |
||||
isclassName = false |
||||
if (event.parentElement && !event.parentElement.className) { |
||||
isclassName = false |
||||
} else { |
||||
isclassName = true |
||||
} |
||||
if (isclassName && event.parentElement) { |
||||
isClick = utils.hasClass(event.parentElement, 'connectionArtificial') |
||||
} |
||||
if (!isclassName || !isClick) { |
||||
connectionArtificial(event) |
||||
} |
||||
} |
||||
} catch (error) { |
||||
console.log(error) |
||||
} |
||||
} |
||||
|
||||
function handleURL(url, params) { |
||||
let flag = url.indexOf('?') != -1 |
||||
let urlPar = '' |
||||
if (params) { |
||||
Object.getOwnPropertyNames(params).forEach((item, index) => { |
||||
if (index === 0 && !flag) { |
||||
urlPar += `?${item}=${params[item]}` |
||||
} else { |
||||
urlPar += `&${item}=${params[item]}` |
||||
} |
||||
}) |
||||
} |
||||
return url + urlPar |
||||
} |
||||
|
||||
function replacementRagEmoticon(str) { |
||||
str = str || '' |
||||
let res = str.replace(/\\n/gm, '<br/>') |
||||
return emojiToImage(res) |
||||
} |
||||
|
||||
function processedEmoji() { |
||||
const tempExp = {} |
||||
const tempBigExp = {} |
||||
faceJSON.forEach((item) => { |
||||
item.faceList.forEach((items) => { |
||||
tempExp[`[${items.title}]`] = items.src |
||||
}) |
||||
item.bigExpression.forEach((items) => { |
||||
tempBigExp[`[${items.title}]`] = items.src |
||||
}) |
||||
}) |
||||
expressionProcessing.value = tempExp |
||||
bigExpressionProcessing.value = tempBigExp |
||||
} |
||||
|
||||
function emojiToImage(str) { |
||||
let srcImg = '' |
||||
let regex = /\[[^\]]+\]/g |
||||
srcImg = str.replace(regex, (item) => { |
||||
if (expressionProcessing.value[item]) { |
||||
return `<img src="${ |
||||
publicPath + expressionProcessing.value[item] |
||||
}" alt="small-image" title="${item}" />` |
||||
} else if (bigExpressionProcessing.value[item]) { |
||||
return `<img src="${ |
||||
publicPath + bigExpressionProcessing.value[item] |
||||
}" alt="big-image" title="${item}"/>` |
||||
} |
||||
return item |
||||
}) |
||||
return srcImg |
||||
} |
||||
|
||||
function showTotalIntro() { |
||||
showTotal.value = !showTotal.value |
||||
exchangeButton.value = !exchangeButton.value |
||||
} |
||||
|
||||
function getRem() { |
||||
let winWidth = window.innerWidth |
||||
const defaultRem = document.body.style.fontSize.replace('rem', '') |
||||
let rem = (winWidth / 375) * defaultRem |
||||
return rem |
||||
} |
||||
|
||||
function descHeightChange() { |
||||
nextTick(() => { |
||||
let rem = parseFloat(getRem()) |
||||
if (!desc.value) return |
||||
let descHeight = window.getComputedStyle(desc.value).height.replace('rem', '') |
||||
descHeight = descHeight || desc.value.offsetHeight |
||||
if (descHeight > 2.325 * rem) { |
||||
showExchangeButton.value = true |
||||
exchangeButton.value = true |
||||
showTotal.value = false |
||||
} else { |
||||
showExchangeButton.value = false |
||||
showTotal.value = true |
||||
} |
||||
}) |
||||
} |
||||
|
||||
function scrollTopBody() { |
||||
setTimeout(() => { |
||||
try { |
||||
if (mescrollContent.value) |
||||
mescrollContent.value.scrollTop = mescrollContent.value.scrollHeight || 0 |
||||
} catch (e) { |
||||
console.log(e.message) |
||||
} |
||||
}, 300) |
||||
} |
||||
|
||||
function connectionArtificial(event) { |
||||
try { |
||||
let qaId = event.parentNode ? event.parentNode.dataset.qaid : '' |
||||
let sendParmas = { reqAndResType: '1', msgText: '&转人工服务&', msgSource: manualService.value } |
||||
scrollTopBody() |
||||
store |
||||
?.dispatch?.('connectionArtificial', sendParmas) |
||||
.then(() => { |
||||
event.disabled = true |
||||
}) |
||||
.catch((err) => console.log(err)) |
||||
store |
||||
?.dispatch?.('userBehavior', { qaId, eventType: '5', eventMsg: '知识库进入人工服务' }) |
||||
.catch((err) => console.log(err)) |
||||
} catch (error) { |
||||
console.log(error) |
||||
} |
||||
} |
||||
|
||||
function richCallAnswer(item) { |
||||
if (!isBtnsubmit.value) return |
||||
isBtnsubmit.value = false |
||||
let sendParmas = { |
||||
reqAndResType: '1', |
||||
msgText: item.intent, |
||||
intentId: item.intentId, |
||||
msgSource: manualService.value, |
||||
} |
||||
scrollTopBody() |
||||
store |
||||
?.dispatch?.('sendingRobot', sendParmas) |
||||
.then(() => { |
||||
isBtnsubmit.value = true |
||||
}) |
||||
.catch((err) => { |
||||
isBtnsubmit.value = true |
||||
console.log(err) |
||||
}) |
||||
} |
||||
|
||||
function manualAnswer(event, item) { |
||||
let sendParmas = { reqAndResType: '1', msgText: '&转人工服务&', msgSource: manualService.value } |
||||
const toast = showLoadingToast({ |
||||
message: '加载中...', |
||||
duration: 0, |
||||
loadingType: 'spinner', |
||||
forbidClick: true, |
||||
}) |
||||
if (!sendLoading.value) { |
||||
scrollTopBody() |
||||
store |
||||
?.dispatch?.('sendingRobot', sendParmas) |
||||
.then(() => { |
||||
closeToast() |
||||
event.disabled = true |
||||
}) |
||||
.catch((err) => { |
||||
closeToast() |
||||
console.log(err) |
||||
}) |
||||
} else { |
||||
closeToast() |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.text-template-box { |
||||
padding: 0; |
||||
} |
||||
|
||||
.question-answer-con { |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.total-introduce { |
||||
max-height: none; |
||||
} |
||||
|
||||
.detailed-introduce { |
||||
max-height: 2.325rem; |
||||
} |
||||
|
||||
.content-question-answer { |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
gap: 8rem; |
||||
margin-top: 10rem; |
||||
} |
||||
|
||||
.answer-items { |
||||
padding: 6rem 12rem; |
||||
background: #f0f6ff; |
||||
border: 1rem solid #d6e4ff; |
||||
border-radius: 16rem; |
||||
font-size: 13rem; |
||||
color: #025cea; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.answer-items[disabled] { |
||||
opacity: 0.5; |
||||
cursor: not-allowed; |
||||
} |
||||
|
||||
.unfold-box { |
||||
text-align: center; |
||||
margin-top: 8rem; |
||||
} |
||||
|
||||
.unfold-box a { |
||||
font-size: 13rem; |
||||
color: #025cea; |
||||
} |
||||
|
||||
.look-more-icon { |
||||
display: inline-block; |
||||
width: 12rem; |
||||
height: 12rem; |
||||
margin-left: 4rem; |
||||
transition: transform 0.3s; |
||||
} |
||||
|
||||
.look-more-icon.active { |
||||
transform: rotate(180deg); |
||||
} |
||||
</style> |
||||
<template> |
||||
<div class="text-template-box" v-if="introduce"> |
||||
<div |
||||
class="question-answer-con" |
||||
v-if="introduce" |
||||
ref="desc" |
||||
:class="showTotal || heightMax == 'autoHeight' ? 'total-introduce' : 'detailed-introduce'" |
||||
> |
||||
<p |
||||
:class="{ connectionArtificial: disabled }" |
||||
v-html="replacementRagEmoticon(introduce)" |
||||
:data-qaid="itemlist && itemlist.qaId ? itemlist.qaId : ''" |
||||
></p> |
||||
</div> |
||||
<div class="content-question-answer" v-if="intents && intents.length > 0"> |
||||
<button |
||||
class="answer-items" |
||||
v-for="(items, index) in intents" |
||||
:key="index" |
||||
:disabled="disabled ? true : false" |
||||
@click.stop="richCallAnswer(items)" |
||||
> |
||||
{{ items.intent }} |
||||
</button> |
||||
</div> |
||||
<div |
||||
class="unfold-box" |
||||
:class="exchangeButton ? 'unfold-bg' : 'unfold-style'" |
||||
v-if="(!intents || intents.length == 0) && showExchangeButton" |
||||
> |
||||
<a href="javascript:;" @click="showTotalIntro" |
||||
>{{ exchangeButton ? '展开' : '收起' |
||||
}}<em class="look-more-icon" :class="!exchangeButton ? 'active' : ''"></em |
||||
></a> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import { ref, inject, onMounted, nextTick } from 'vue' |
||||
import { showToast, showLoadingToast, closeToast } from 'vant' |
||||
import faceJSON from '@/mock/face.json' |
||||
import utils from '@/utils/utils' |
||||
import wx from 'weixin-js-sdk' |
||||
|
||||
const props = defineProps({ |
||||
introduce: { type: String, default: '' }, |
||||
intents: { type: Array, default: () => [] }, |
||||
disabled: { type: Boolean, default: false }, |
||||
heightMax: { type: String, default: '' }, |
||||
itemlist: { type: Object, default: () => ({}) }, |
||||
}) |
||||
|
||||
const store = inject('appStore', null) |
||||
const userId = inject('userId', ref('')) |
||||
const userInfo = inject('userInfo', ref({})) |
||||
const mescrollContent = inject('mescrollContent', ref(null)) |
||||
const manualService = inject('manualService', ref('1')) |
||||
const sendLoading = inject('sendLoading', ref(false)) |
||||
|
||||
const publicPath = import.meta.env.BASE_URL |
||||
const showTotal = ref(true) |
||||
const exchangeButton = ref(true) |
||||
const showExchangeButton = ref(false) |
||||
const expressionProcessing = ref({}) |
||||
const bigExpressionProcessing = ref({}) |
||||
const isBtnsubmit = ref(true) |
||||
const desc = ref(null) |
||||
|
||||
onMounted(() => { |
||||
processedEmoji() |
||||
descHeightChange() |
||||
nextTick(() => { |
||||
initGlobalCallbacks() |
||||
}) |
||||
}) |
||||
|
||||
function initGlobalCallbacks() { |
||||
try { |
||||
window.getLink = (item) => { |
||||
utils.AppToHttpHtml(item) |
||||
} |
||||
window.jumpOpenLink = (item) => { |
||||
utils.AppToHttpHtml(item) |
||||
} |
||||
|
||||
window.jumpOpenWeb = (type, url, urlType) => { |
||||
let params = {} |
||||
if (type === 'YY_0000') { |
||||
params = { |
||||
idnum: '1', |
||||
ownerId: userInfo.value?.encryptPid, |
||||
openid: userInfo.value?.sourceUserId, |
||||
} |
||||
const completeURL = handleURL(url, params) |
||||
store |
||||
?.dispatch?.('post', { |
||||
url: 'semantic/getYdbqTicket', |
||||
data: { url: completeURL, userId: userId.value }, |
||||
}) |
||||
.then((res) => { |
||||
if (res.code === 0) { |
||||
if (!res.data) { |
||||
utils.openWxPage(url, params) |
||||
} else { |
||||
utils.openWxPage(url, { accessTicket: res.data }) |
||||
} |
||||
} else { |
||||
showToast('网络异常,请重试') |
||||
} |
||||
}) |
||||
} else if (type === 'YY_ydbq') { |
||||
params = { |
||||
mobile: userInfo.value?.phone, |
||||
pid: userInfo.value?.encryptPid, |
||||
openid: userInfo.value?.sourceUserId, |
||||
} |
||||
const completeURL = handleURL(url, params) |
||||
store |
||||
?.dispatch?.('post', { |
||||
url: 'semantic/getYdbqTicket', |
||||
data: { url: completeURL, userId: userId.value }, |
||||
}) |
||||
.then((res) => { |
||||
if (res.code === 0) { |
||||
if (!res.data) { |
||||
utils.openWxPage(url, params) |
||||
} else { |
||||
utils.openWxPage(url, { accessTicket: res.data }) |
||||
} |
||||
} else { |
||||
showToast('网络异常,请重试') |
||||
} |
||||
}) |
||||
} else if (type === 'YY_APP_H5') { |
||||
utils.AppOpenH5(url, { mobile: userInfo.value?.phone, pid: userInfo.value?.encryptPid }) |
||||
} else if (type === 'ydbq_2') { |
||||
utils.openSpecPage({ url, generateType: type, userId: userId.value }) |
||||
} else if (type === 'YY_CMD') { |
||||
const newUrl = |
||||
url + |
||||
`&idnum=1&openId=${userInfo.value?.sourceUserId}&entryOpenid=${userInfo.value?.entryOpenid}` |
||||
store |
||||
?.dispatch?.('post', { |
||||
url: 'semantic/getYdbqTicket', |
||||
data: { url: newUrl, userId: userId.value, isCmd: '1' }, |
||||
}) |
||||
.then((res) => { |
||||
if (res.code === 0) { |
||||
if (!res.data) { |
||||
utils.AppToHttpHtml(newUrl) |
||||
} else { |
||||
utils.AppToHttpHtml(url + `&accessTicket=${res.data}`) |
||||
} |
||||
} else { |
||||
showToast('网络异常,请重试') |
||||
} |
||||
}) |
||||
} else if (type === 'YY_hfhz') { |
||||
store |
||||
?.dispatch?.('post', { |
||||
url: 'semantic/xdhfCreateToken', |
||||
data: { certNum: '1', systemSource: 'yykf', userId: userId.value }, |
||||
}) |
||||
.then((res) => { |
||||
const entryptedId = userInfo.value?.versionHFHZ |
||||
utils.openWxPage(url, { |
||||
sessionToken: res.data, |
||||
holderId: encodeURI(entryptedId), |
||||
holderType: 'a', |
||||
hzhfSystemSource: '0803', |
||||
ishwFlag: '1', |
||||
isqshzFlag: '1', |
||||
}) |
||||
}) |
||||
} else if (type === 'YY_1v1') { |
||||
store |
||||
?.dispatch?.('post', { url: 'thirdPartyAuth/getExclusiveServicesToken' }) |
||||
.then((res) => { |
||||
if (res.code === 0 && res.data) { |
||||
utils.openWxPage(url, { |
||||
sour: 'yykf', |
||||
omoSource: userInfo.value?.source, |
||||
omoSourceName: encodeURIComponent(userInfo.value?.sourceName), |
||||
token: res.data, |
||||
}) |
||||
} else { |
||||
showToast('网络异常,请重试') |
||||
} |
||||
}) |
||||
} else if (type === 'YY_yzsfx') { |
||||
store |
||||
?.dispatch?.('post', { |
||||
url: 'semantic/getYzsUrl', |
||||
data: { userId: userId.value, bqType: url, urlType }, |
||||
}) |
||||
.then((res) => { |
||||
if (res.code === 0 && res.data) { |
||||
utils.openWxPage(res.data, '') |
||||
} else { |
||||
showToast('网络异常,请重试') |
||||
} |
||||
}) |
||||
} else if (type === 'YY_GWXCX') { |
||||
wx.miniProgram.navigateTo({ url: url }) |
||||
} else if (type === 'YY_GWXCX_HOME') { |
||||
wx.miniProgram.reLaunch({ url: url }) |
||||
} |
||||
} |
||||
|
||||
window.openAppPage = (params) => { |
||||
if (params) { |
||||
params = params.replace(/\'/g, '"') |
||||
utils.AppToOutModules(params) |
||||
} |
||||
} |
||||
window.connectionArtificial = (event) => { |
||||
let isClick = false, |
||||
isclassName = false |
||||
if (event.parentElement && !event.parentElement.className) { |
||||
isclassName = false |
||||
} else { |
||||
isclassName = true |
||||
} |
||||
if (isclassName && event.parentElement) { |
||||
isClick = utils.hasClass(event.parentElement, 'connectionArtificial') |
||||
} |
||||
if (!isclassName || !isClick) { |
||||
connectionArtificial(event) |
||||
} |
||||
} |
||||
} catch (error) { |
||||
console.log(error) |
||||
} |
||||
} |
||||
|
||||
function handleURL(url, params) { |
||||
let flag = url.indexOf('?') != -1 |
||||
let urlPar = '' |
||||
if (params) { |
||||
Object.getOwnPropertyNames(params).forEach((item, index) => { |
||||
if (index === 0 && !flag) { |
||||
urlPar += `?${item}=${params[item]}` |
||||
} else { |
||||
urlPar += `&${item}=${params[item]}` |
||||
} |
||||
}) |
||||
} |
||||
return url + urlPar |
||||
} |
||||
|
||||
function replacementRagEmoticon(str) { |
||||
str = str || '' |
||||
let res = str.replace(/\\n/gm, '<br/>') |
||||
return emojiToImage(res) |
||||
} |
||||
|
||||
function processedEmoji() { |
||||
const tempExp = {} |
||||
const tempBigExp = {} |
||||
faceJSON.forEach((item) => { |
||||
item.faceList.forEach((items) => { |
||||
tempExp[`[${items.title}]`] = items.src |
||||
}) |
||||
item.bigExpression.forEach((items) => { |
||||
tempBigExp[`[${items.title}]`] = items.src |
||||
}) |
||||
}) |
||||
expressionProcessing.value = tempExp |
||||
bigExpressionProcessing.value = tempBigExp |
||||
} |
||||
|
||||
function emojiToImage(str) { |
||||
let srcImg = '' |
||||
let regex = /\[[^\]]+\]/g |
||||
srcImg = str.replace(regex, (item) => { |
||||
if (expressionProcessing.value[item]) { |
||||
return `<img src="${ |
||||
publicPath + expressionProcessing.value[item] |
||||
}" alt="small-image" title="${item}" />` |
||||
} else if (bigExpressionProcessing.value[item]) { |
||||
return `<img src="${ |
||||
publicPath + bigExpressionProcessing.value[item] |
||||
}" alt="big-image" title="${item}"/>` |
||||
} |
||||
return item |
||||
}) |
||||
return srcImg |
||||
} |
||||
|
||||
function showTotalIntro() { |
||||
showTotal.value = !showTotal.value |
||||
exchangeButton.value = !exchangeButton.value |
||||
} |
||||
|
||||
function getRem() { |
||||
let winWidth = window.innerWidth |
||||
const defaultRem = document.body.style.fontSize.replace('rem', '') |
||||
let rem = (winWidth / 375) * defaultRem |
||||
return rem |
||||
} |
||||
|
||||
function descHeightChange() { |
||||
nextTick(() => { |
||||
let rem = parseFloat(getRem()) |
||||
if (!desc.value) return |
||||
let descHeight = window.getComputedStyle(desc.value).height.replace('rem', '') |
||||
descHeight = descHeight || desc.value.offsetHeight |
||||
if (descHeight > 2.325 * rem) { |
||||
showExchangeButton.value = true |
||||
exchangeButton.value = true |
||||
showTotal.value = false |
||||
} else { |
||||
showExchangeButton.value = false |
||||
showTotal.value = true |
||||
} |
||||
}) |
||||
} |
||||
|
||||
function scrollTopBody() { |
||||
setTimeout(() => { |
||||
try { |
||||
if (mescrollContent.value) |
||||
mescrollContent.value.scrollTop = mescrollContent.value.scrollHeight || 0 |
||||
} catch (e) { |
||||
console.log(e.message) |
||||
} |
||||
}, 300) |
||||
} |
||||
|
||||
function connectionArtificial(event) { |
||||
try { |
||||
let qaId = event.parentNode ? event.parentNode.dataset.qaid : '' |
||||
let sendParmas = { reqAndResType: '1', msgText: '&转人工服务&', msgSource: manualService.value } |
||||
scrollTopBody() |
||||
store |
||||
?.dispatch?.('connectionArtificial', sendParmas) |
||||
.then(() => { |
||||
event.disabled = true |
||||
}) |
||||
.catch((err) => console.log(err)) |
||||
store |
||||
?.dispatch?.('userBehavior', { qaId, eventType: '5', eventMsg: '知识库进入人工服务' }) |
||||
.catch((err) => console.log(err)) |
||||
} catch (error) { |
||||
console.log(error) |
||||
} |
||||
} |
||||
|
||||
function richCallAnswer(item) { |
||||
if (!isBtnsubmit.value) return |
||||
isBtnsubmit.value = false |
||||
let sendParmas = { |
||||
reqAndResType: '1', |
||||
msgText: item.intent, |
||||
intentId: item.intentId, |
||||
msgSource: manualService.value, |
||||
} |
||||
scrollTopBody() |
||||
store |
||||
?.dispatch?.('sendingRobot', sendParmas) |
||||
.then(() => { |
||||
isBtnsubmit.value = true |
||||
}) |
||||
.catch((err) => { |
||||
isBtnsubmit.value = true |
||||
console.log(err) |
||||
}) |
||||
} |
||||
|
||||
function manualAnswer(event, item) { |
||||
let sendParmas = { reqAndResType: '1', msgText: '&转人工服务&', msgSource: manualService.value } |
||||
const toast = showLoadingToast({ |
||||
message: '加载中...', |
||||
duration: 0, |
||||
loadingType: 'spinner', |
||||
forbidClick: true, |
||||
}) |
||||
if (!sendLoading.value) { |
||||
scrollTopBody() |
||||
store |
||||
?.dispatch?.('sendingRobot', sendParmas) |
||||
.then(() => { |
||||
closeToast() |
||||
event.disabled = true |
||||
}) |
||||
.catch((err) => { |
||||
closeToast() |
||||
console.log(err) |
||||
}) |
||||
} else { |
||||
closeToast() |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.text-template-box { |
||||
padding: 0; |
||||
} |
||||
|
||||
.question-answer-con { |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.total-introduce { |
||||
max-height: none; |
||||
} |
||||
|
||||
.detailed-introduce { |
||||
max-height: 2.325rem; |
||||
} |
||||
|
||||
.content-question-answer { |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
gap: 8rem; |
||||
margin-top: 10rem; |
||||
} |
||||
|
||||
.answer-items { |
||||
padding: 6rem 12rem; |
||||
background: #f0f6ff; |
||||
border: 1rem solid #d6e4ff; |
||||
border-radius: 16rem; |
||||
font-size: 13rem; |
||||
color: #025cea; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.answer-items[disabled] { |
||||
opacity: 0.5; |
||||
cursor: not-allowed; |
||||
} |
||||
|
||||
.unfold-box { |
||||
text-align: center; |
||||
margin-top: 8rem; |
||||
} |
||||
|
||||
.unfold-box a { |
||||
font-size: 13rem; |
||||
color: #025cea; |
||||
} |
||||
|
||||
.look-more-icon { |
||||
display: inline-block; |
||||
width: 12rem; |
||||
height: 12rem; |
||||
margin-left: 4rem; |
||||
transition: transform 0.3s; |
||||
} |
||||
|
||||
.look-more-icon.active { |
||||
transform: rotate(180deg); |
||||
} |
||||
</style> |
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,37 +1,34 @@ |
||||
import { createRouter, createWebHashHistory } from 'vue-router' |
||||
import Utils from "@/utils/utils"; |
||||
|
||||
const router = createRouter({ |
||||
history: createWebHashHistory(), |
||||
routes: [ |
||||
{ path: '/', redirect: '/chat' }, |
||||
{ path: '/chat', name: 'chat', component: () => import('../views/chat.vue') }, |
||||
{ |
||||
path: '/history', |
||||
name: 'history', |
||||
component: () => import('../views/history.vue'), |
||||
meta: { title: '洋洋客服-历史记录查询' }, |
||||
}, |
||||
{ |
||||
path: '/previewPdf', |
||||
name: 'previewPdf', |
||||
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 = '洋洋客服' |
||||
|
||||
// 全局前置路由守卫
|
||||
router.beforeEach(async (to, from, next) => { |
||||
/* 路由发生变化修改页面title */ |
||||
var title = to.meta.title ? to.meta.title : defaultTitle; |
||||
Utils.setTitle(title);//修改页面title
|
||||
next(); |
||||
}) |
||||
|
||||
export default router |
||||
import { createRouter, createWebHashHistory } from 'vue-router' |
||||
import Utils from "@/utils/utils"; |
||||
|
||||
const router = createRouter({ |
||||
history: createWebHashHistory(), |
||||
routes: [ |
||||
{ path: '/', redirect: '/chat' }, |
||||
{ 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',
|
||||
// component: () => import('../views/history.vue'),
|
||||
// meta: { title: '小蓝管家-历史记录查询' },
|
||||
// },
|
||||
// {
|
||||
// path: '/previewPdf',
|
||||
// name: 'previewPdf',
|
||||
// component: () => import('../components/previewPDF/index.vue'),
|
||||
// meta: { title: '预览PDF' },
|
||||
// },
|
||||
], |
||||
}) |
||||
const defaultTitle = '小蓝管家' |
||||
|
||||
// 全局前置路由守卫
|
||||
router.beforeEach(async (to, from, next) => { |
||||
/* 路由发生变化修改页面title */ |
||||
var title = to.meta.title ? to.meta.title : defaultTitle; |
||||
Utils.setTitle(title);//修改页面title
|
||||
next(); |
||||
}) |
||||
|
||||
export default router |
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,465 +1,465 @@ |
||||
import config from './config' |
||||
import { showToast, showDialog, showConfirmDialog } from 'vant' |
||||
import { useAppStore } from '@/store/app.js' |
||||
import utils from './utils' |
||||
|
||||
// Keep the legacy socket implementation independent from Vuex. The proxy
|
||||
// exposes the old state/commit/dispatch shape while reading the live Pinia
|
||||
// store instance for every call.
|
||||
const store = { |
||||
get state() { |
||||
return useAppStore().state |
||||
}, |
||||
commit(type, payload) { |
||||
return useAppStore().commit(type, payload) |
||||
}, |
||||
dispatch(type, payload) { |
||||
return useAppStore().dispatch(type, payload) |
||||
}, |
||||
} |
||||
/** |
||||
* 提示函数 |
||||
* 禁止点击蒙层、显示3秒后关闭 |
||||
*/ |
||||
const toast = msg => { |
||||
showToast({ |
||||
message: msg, |
||||
duration: 5000, |
||||
position: 'top', |
||||
}); |
||||
} |
||||
|
||||
const sendMessageFunction = (msgText) => { |
||||
return { |
||||
token: store.state.token, |
||||
fromFlag: 'other', |
||||
userId: store.state.userId, |
||||
sessionId: store.state.sessionId, |
||||
reqAndResType: '', |
||||
msgType: '', //
|
||||
msgText: msgText, |
||||
intentId: '', |
||||
source: store.state.source, |
||||
timestamp: (new Date()).valueOf(), |
||||
msgSource: store.state.manualService, //1机器人 2人工
|
||||
cmd: '' |
||||
} |
||||
} |
||||
|
||||
//设置title
|
||||
document.setTitle = function (title) { |
||||
title = title || '洋洋客服'; |
||||
store.commit('set_otherTips', title); |
||||
|
||||
// document.title = title;
|
||||
var iframe = document.createElement('iframe'); |
||||
iframe.src = `${import.meta.env.BASE_URL}favicon.ico`; |
||||
iframe.style.display = 'none'; |
||||
iframe.onload = function () { |
||||
setTimeout(function () { |
||||
iframe.remove(); |
||||
}, 9) |
||||
} |
||||
document.body.appendChild(iframe); |
||||
} |
||||
|
||||
|
||||
let wsConnection = { |
||||
WSS_URL: config.WSURL, |
||||
$ws: null, |
||||
lockReturn: false, |
||||
//6s发一次心跳
|
||||
timeout: 60 * 100 * 1, |
||||
timeoutObj: null, |
||||
timeoutNum: null, |
||||
serverTimeoutObj: null, |
||||
globalCallback: null, |
||||
sendMessageData: null, |
||||
reconectNum: 0, |
||||
//最大限制 重连次数
|
||||
reconectTimes: 20, |
||||
closeSession: false,//是否关闭当前会话
|
||||
//初始化webSocket长连接
|
||||
initWebSocket: function (callback) { |
||||
var that = this; |
||||
var userId = store.state.userId; |
||||
var checkParam = store.state.token; |
||||
var sessionId = store.state.sessionId; |
||||
|
||||
try { |
||||
if ('WebSocket' in window) { |
||||
// this.$ws = new WebSocket(this.WSS_URL + userId + "," + sessionId);
|
||||
this.$ws = new WebSocket(`${this.WSS_URL}${userId}&${encodeURIComponent(checkParam)}`); |
||||
} else if ('MozWebSocket' in window) { |
||||
// this.$ws = new MozWebSocket(this.WSS_URL + userId + "," + sessionId);
|
||||
this.$ws = new MozWebSocket(`${this.WSS_URL}${userId}&${encodeURIComponent(checkParam)}`); |
||||
} else { |
||||
showDialog({ |
||||
message: '您的浏览器不支持websocket协议,建议您使用Android系统版本大于4.0,ios系统版本大于7.0。' |
||||
}).then(() => { |
||||
utils.closeWindow(); //关闭页面
|
||||
}); |
||||
} |
||||
// this.$ws.onopen = this.wsOpen;
|
||||
this.$ws.addEventListener("open", event => { |
||||
this.wsOpen(); |
||||
}) |
||||
|
||||
// this.$ws = new WebSocket(this.WSS_URL + userId + "," + sessionId);
|
||||
this.$ws.onclose = this.wsClose; |
||||
this.$ws.onmessage = this.onMessage; |
||||
this.$ws.onerror = this.wsError; |
||||
|
||||
callback && callback(); |
||||
|
||||
// store.commit('set_otherTips', "连接中...");
|
||||
document.setTitle('连接中...') |
||||
} catch (e) { |
||||
wsConnection.reconnect() |
||||
console.log(e); |
||||
} |
||||
}, |
||||
wsResendMessage() { |
||||
var sendFailList = store.state.sendFailList;//取出上次发送失败的消息
|
||||
if (sendFailList.length > 0) { |
||||
for (let i = 0; i < sendFailList.length; i++) { |
||||
(function (i) {//这个匿名函数生成了闭包的效果,新建了一个作用域,这个作用域接收到每次循环的i值保存了下来,即使循环结束,闭包形成的作用域也不会被销毁
|
||||
setTimeout(function () { |
||||
var dataResend = sendFailList[i]; |
||||
console.log("发送失败的数据ID==", dataResend.timestamp); |
||||
|
||||
wsConnection.sendMessage(dataResend, wsConnection.globalCallback); |
||||
store.commit('set_currentItemId', dataResend.timestamp); |
||||
store.commit('set_findActiveItem', false);//发送成功
|
||||
}, 200); |
||||
})(i); |
||||
} |
||||
|
||||
store.commit('del_sendFailList');//删除发送成功的
|
||||
console.log(store.state.sendFailList); |
||||
} |
||||
}, |
||||
//打开websocket
|
||||
wsOpen: function (e) { |
||||
var that = this; |
||||
//开始websocket心跳
|
||||
wsConnection.startWsHeartbeat(); |
||||
// toast('会话建立成功');
|
||||
// store.commit('set_otherTips', "洋洋客服");
|
||||
document.setTitle('洋洋客服') |
||||
|
||||
//执行重新发送
|
||||
// if(wsConnection.isReconnet){
|
||||
// wsConnection.wsResendMessage();
|
||||
// wsConnection.isReconnet = false;
|
||||
// }
|
||||
|
||||
console.log("ws success " + new Date().format("yyyy-MM-dd hh:mm:ss")); |
||||
}, |
||||
onMessage: function (event) { |
||||
//每次接收到服务端消息后 重置websocket心跳
|
||||
wsConnection.resetHeartbeat(); |
||||
let res = event.data; |
||||
try { |
||||
if (event.data === 'pong') { |
||||
console.log("收到心跳了:", event.data); |
||||
return; |
||||
} |
||||
|
||||
if (event.data.match(/^\d+$/)) { |
||||
wsConnection.globalCallback && wsConnection.globalCallback(event.data); |
||||
return; |
||||
} |
||||
|
||||
res = JSON.parse(res); |
||||
console.log("收到消息啦:", res.data || res); |
||||
if (res.data.cmd === 'ServiceSessionClosedEvent' && store.state.hxOpenSucceseds.includes(res.data.sessionId)) { |
||||
if(store.state.integratedUser){ |
||||
utils.useSdk(res.data) |
||||
}else{ |
||||
setTimeout(() => { |
||||
utils.useSdk(res.data) |
||||
}, 100); |
||||
} |
||||
} |
||||
|
||||
if (res.code != 0) { |
||||
// toast(data.msg);
|
||||
if (res.code === 10001) { |
||||
// 删除已经失效或过期的token(不删除也可以,因为登录后覆盖)
|
||||
sessionStorage.removeItem("token"); |
||||
wsConnection.$ws.close(); |
||||
|
||||
if (store.state.notitle === '1') { |
||||
toast('当前会话已结束,请刷新页面或关闭页面重新进入'); |
||||
return |
||||
} |
||||
|
||||
showConfirmDialog({ |
||||
title: '提示', |
||||
message: '当前会话已结束,请刷新页面或关闭页面重新进入', |
||||
confirmButtonText: '取消', |
||||
cancelButtonText: '确认', |
||||
confirmButtonColor: '#000000', |
||||
cancelButtonColor: '#000000' |
||||
}).then(() => { |
||||
// on cancel (现在是右边的取消按钮)
|
||||
// utils.closeWindow();
|
||||
// router.replace({
|
||||
// path: '/404' // 到登录页重新获取token
|
||||
// })
|
||||
}).catch(() => { |
||||
// on confirm (现在是左边的确认按钮)
|
||||
//确认重新刷新页面获取token
|
||||
// window.location.reload();
|
||||
utils.AppReloadHtml(); |
||||
}); |
||||
} else { |
||||
if (res.msg) { |
||||
toast(res.msg); |
||||
} |
||||
} |
||||
|
||||
return; |
||||
} |
||||
|
||||
//是否转接人工参数
|
||||
// if (res.data.cmd && res.data.cmd == "ServiceSessionCreatedEvent") {
|
||||
// store.dispatch('setTransferCustomer', 2);
|
||||
// }
|
||||
|
||||
//客服接起不排队了
|
||||
if (res.data.cmd && res.data.cmd == "ServiceSessionOpenedEvent") { |
||||
store.commit('set_queuedTimeout', true); |
||||
|
||||
store.commit('set_msgStatus', '');//人工客服排队状态值 等待中:"1",其他:""
|
||||
store.commit('set_msgReply', false);//人工客服排队是否调取自动回复话术
|
||||
} |
||||
|
||||
if (res.data.cmd && res.data.cmd == "ServiceSessionClosedEvent") { |
||||
wsConnection.closeSession = true;//是关闭会话
|
||||
setTimeout(() => { |
||||
store.commit('set_queuedTimeout', true); |
||||
store.dispatch('setTransferCustomer', 1);//人工改为机器人
|
||||
wsConnection.$ws.close(); |
||||
|
||||
}, 2500) |
||||
} |
||||
|
||||
//服务端发送来的消息存到vuex
|
||||
store.dispatch('sendingDrtificial', res.data).then((res) => { |
||||
}).catch(err => { |
||||
console.log(err) |
||||
}); |
||||
|
||||
//如果会话关闭不用推送消息回执
|
||||
if (res.data && res.data.chatId) { |
||||
let chatParam = JSON.stringify(res.data); |
||||
// wsConnection.$ws.send(chatParam, '');
|
||||
wsConnection.sendMessage(chatParam, ''); |
||||
console.log("发送消息回执", chatParam) |
||||
} |
||||
} catch (error) { |
||||
console.log(error) |
||||
} |
||||
}, |
||||
//发送数据,接收处理数据
|
||||
sendMessage: function (data, callback) { |
||||
//存储事件
|
||||
wsConnection.globalCallback = callback; |
||||
// console.log(data, callback)
|
||||
|
||||
switch (wsConnection.$ws.readyState) { |
||||
//CONNECTING:值为0,表示正在连接。
|
||||
case wsConnection.$ws.CONNECTING: |
||||
setTimeout(function () { |
||||
wsConnection.sendMessage(data, callback); |
||||
}, 1000); |
||||
break; |
||||
//OPEN:值为1,表示连接成功,可以通信了。
|
||||
case wsConnection.$ws.OPEN: |
||||
wsConnection.$ws.send(data); |
||||
break; |
||||
//CLOSING:值为2,表示连接正在关闭。
|
||||
case wsConnection.$ws.CLOSING: |
||||
setTimeout(function () { |
||||
wsConnection.sendMessage(data, callback); |
||||
}, 1000); |
||||
break; |
||||
//CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
|
||||
case wsConnection.$ws.CLOSED: |
||||
// do something
|
||||
break; |
||||
default: |
||||
// this never happens
|
||||
break; |
||||
} |
||||
|
||||
//重新监听onmessage,并把数据传给回调函数
|
||||
// wsConnection.onmessage = ev =>{
|
||||
// callback && callback(ev)
|
||||
// }
|
||||
}, |
||||
//重启websocket
|
||||
reconnect: function () { |
||||
let _this = this; |
||||
if (_this.lockReturn) { |
||||
return; |
||||
} |
||||
_this.isReconnet = true; |
||||
_this.lockReturn = true; |
||||
|
||||
//没连接上会一直重连,设置延迟避免请求过多
|
||||
if (_this.reconectNum < this.reconectTimes) { |
||||
_this.timeoutNum && clearTimeout(_this.timeoutNum); |
||||
_this.timeoutNum = setTimeout(function () { |
||||
_this.initWebSocket(); |
||||
// toast('正在重连,请稍后...');
|
||||
// store.commit('set_otherTips', "正在重连...");
|
||||
document.setTitle('正在重连...') |
||||
console.info(`正在重连第${_this.reconectNum + 1}次`) |
||||
_this.reconectNum++ |
||||
_this.lockReturn = false; |
||||
}, 4000); |
||||
} else { |
||||
// toast('重连次数超出设定值,不再重连,请检查配置项');
|
||||
showDialog({ |
||||
message: '重连次数超出设定值,不再重连。请检查您的网络设置,点击“确定”刷新页面,重新建立会话。' |
||||
}).then(() => { |
||||
// window.location.reload();
|
||||
utils.AppReloadHtml(); |
||||
}); |
||||
} |
||||
}, |
||||
//心跳检测,每5s心跳一次
|
||||
startWsHeartbeat: function () { |
||||
let _this = this; |
||||
this.timeoutObj && clearTimeout(this.timeoutObj); |
||||
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj); |
||||
|
||||
this.timeoutObj = setTimeout(() => { |
||||
//这里发送一个心跳,后端收到后,返回一个心跳消息,
|
||||
//onmessage拿到返回的心跳就说明连接正常
|
||||
if (wsConnection.$ws && wsConnection.$ws.readyState != wsConnection.$ws.CLOSED) { |
||||
//关闭当前会话后不发送ping
|
||||
if (!wsConnection.closeSession) { |
||||
_this.$ws.send("ping"); |
||||
console.log("ping!", new Date().format("yyyy-MM-dd hh:mm:ss")); |
||||
} |
||||
} |
||||
|
||||
//如果超过一定时间还没重置,说明后端主动断开了
|
||||
_this.serverTimeoutObj = setTimeout(() => { |
||||
//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
|
||||
_this.$ws.close(); |
||||
}, _this.timeout) |
||||
}, this.timeout); |
||||
}, |
||||
//重置websocket心跳
|
||||
resetHeartbeat: function () { |
||||
let _this = this; |
||||
this.reconectNum = 0; |
||||
clearTimeout(_this.timeoutObj); |
||||
clearTimeout(_this.serverTimeoutObj); |
||||
_this.startWsHeartbeat(); |
||||
}, |
||||
wsError: function (err) { |
||||
console.log(err, 'ws error'); |
||||
// store.commit('set_otherTips', "洋洋客服(未连接)");
|
||||
document.setTitle('洋洋客服(未连接)') |
||||
wsConnection.reconnect() |
||||
}, |
||||
wsClose: function (e) { |
||||
// console.log(e, 'ws close')
|
||||
console.log("连接关闭! " + new Date().format("yyyy-MM-dd hh:mm:ss")); |
||||
console.log('websocket 断开: ' + e.code + ' 关闭的原因:' + e.reason + ' 是否正常关闭' + e.wasClean); |
||||
|
||||
if (e.code !== 1000) { |
||||
// toast('当前会话已结束');
|
||||
this.lockReturn = false |
||||
clearTimeout(this.timeoutNum) |
||||
wsConnection.reconnect(); |
||||
// store.commit('set_otherTips', "洋洋客服(未连接)");
|
||||
document.setTitle('洋洋客服(未连接)') |
||||
//当前转成机器人
|
||||
// store.dispatch('setTransferCustomer', 1);
|
||||
} else { |
||||
this.$ws = null; |
||||
clearInterval(this.timeoutObj) |
||||
clearTimeout(this.serverTimeoutObj) |
||||
// toast('会话已关闭');
|
||||
// store.commit('set_otherTips', "洋洋客服");
|
||||
document.setTitle('洋洋客服') |
||||
wsConnection.$ws.close(); |
||||
} |
||||
}, |
||||
}; |
||||
|
||||
//时间格式化
|
||||
Date.prototype.format = function (fmt) { |
||||
var o = { |
||||
"M+": this.getMonth() + 1, //月份
|
||||
"d+": this.getDate(), //日
|
||||
"h+": this.getHours(), //小时
|
||||
"m+": this.getMinutes(), //分
|
||||
"s+": this.getSeconds(), //秒
|
||||
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
|
||||
"S": this.getMilliseconds() //毫秒
|
||||
}; |
||||
if (/(y+)/.test(fmt)) { |
||||
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); |
||||
} |
||||
for (var k in o) { |
||||
if (new RegExp("(" + k + ")").test(fmt)) { |
||||
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); |
||||
} |
||||
} |
||||
return fmt; |
||||
} |
||||
|
||||
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
|
||||
window.onbeforeunload = function (event) { |
||||
event = event || window.event; |
||||
try { |
||||
// toast('onbeforeunload关闭当前会话窗口啦');
|
||||
var oldSessionId = localStorage.getItem('sessionId'); |
||||
var liccsAds = localStorage.getItem('liccsAds'); // 记录用户弹窗提示
|
||||
// sessionStorage.removeItem("manualService");
|
||||
if (wsConnection.$ws) { |
||||
wsConnection.$ws.close(); |
||||
} |
||||
//清除本地缓存
|
||||
localStorage.clear(); |
||||
localStorage.sessionId = oldSessionId || ''; |
||||
localStorage.liccsAds = liccsAds || ''; |
||||
//清除session
|
||||
sessionStorage.clear(); |
||||
} catch (error) { |
||||
console.log(error.message) |
||||
} |
||||
} |
||||
|
||||
//安卓处理解决方案
|
||||
window.onunload = function (event) { |
||||
event = event || window.event; |
||||
try { |
||||
// toast('onunload关闭当前会话窗口啦');
|
||||
var oldSessionId = localStorage.getItem('sessionId'); |
||||
var liccsAds = localStorage.getItem('liccsAds'); // 记录用户弹窗提示
|
||||
// sessionStorage.removeItem("manualService");
|
||||
if (wsConnection.$ws) { |
||||
wsConnection.$ws.close(); |
||||
} |
||||
//清除本地缓存
|
||||
localStorage.clear(); |
||||
localStorage.sessionId = oldSessionId || ''; |
||||
localStorage.liccsAds = liccsAds || ''; |
||||
//清除session
|
||||
sessionStorage.clear(); |
||||
} catch (error) { |
||||
console.log(error.message) |
||||
} |
||||
} |
||||
|
||||
//抛出websocket对象
|
||||
export default wsConnection |
||||
import config from './config' |
||||
import { showToast, showDialog, showConfirmDialog } from 'vant' |
||||
import { useAppStore } from '@/store/app.js' |
||||
import utils from './utils' |
||||
|
||||
// Keep the legacy socket implementation independent from Vuex. The proxy
|
||||
// exposes the old state/commit/dispatch shape while reading the live Pinia
|
||||
// store instance for every call.
|
||||
const store = { |
||||
get state() { |
||||
return useAppStore().state |
||||
}, |
||||
commit(type, payload) { |
||||
return useAppStore().commit(type, payload) |
||||
}, |
||||
dispatch(type, payload) { |
||||
return useAppStore().dispatch(type, payload) |
||||
}, |
||||
} |
||||
/** |
||||
* 提示函数 |
||||
* 禁止点击蒙层、显示3秒后关闭 |
||||
*/ |
||||
const toast = msg => { |
||||
showToast({ |
||||
message: msg, |
||||
duration: 5000, |
||||
position: 'top', |
||||
}); |
||||
} |
||||
|
||||
const sendMessageFunction = (msgText) => { |
||||
return { |
||||
token: store.state.token, |
||||
fromFlag: 'other', |
||||
userId: store.state.userId, |
||||
sessionId: store.state.sessionId, |
||||
reqAndResType: '', |
||||
msgType: '', //
|
||||
msgText: msgText, |
||||
intentId: '', |
||||
source: store.state.source, |
||||
timestamp: (new Date()).valueOf(), |
||||
msgSource: store.state.manualService, //1机器人 2人工
|
||||
cmd: '' |
||||
} |
||||
} |
||||
|
||||
//设置title
|
||||
document.setTitle = function (title) { |
||||
title = title || '洋洋客服'; |
||||
store.commit('set_otherTips', title); |
||||
|
||||
// document.title = title;
|
||||
var iframe = document.createElement('iframe'); |
||||
iframe.src = `${import.meta.env.BASE_URL}favicon.ico`; |
||||
iframe.style.display = 'none'; |
||||
iframe.onload = function () { |
||||
setTimeout(function () { |
||||
iframe.remove(); |
||||
}, 9) |
||||
} |
||||
document.body.appendChild(iframe); |
||||
} |
||||
|
||||
|
||||
let wsConnection = { |
||||
WSS_URL: config.WSURL, |
||||
$ws: null, |
||||
lockReturn: false, |
||||
//6s发一次心跳
|
||||
timeout: 60 * 100 * 1, |
||||
timeoutObj: null, |
||||
timeoutNum: null, |
||||
serverTimeoutObj: null, |
||||
globalCallback: null, |
||||
sendMessageData: null, |
||||
reconectNum: 0, |
||||
//最大限制 重连次数
|
||||
reconectTimes: 20, |
||||
closeSession: false,//是否关闭当前会话
|
||||
//初始化webSocket长连接
|
||||
initWebSocket: function (callback) { |
||||
var that = this; |
||||
var userId = store.state.userId; |
||||
var checkParam = store.state.token; |
||||
var sessionId = store.state.sessionId; |
||||
|
||||
try { |
||||
if ('WebSocket' in window) { |
||||
// this.$ws = new WebSocket(this.WSS_URL + userId + "," + sessionId);
|
||||
this.$ws = new WebSocket(`${this.WSS_URL}${userId}&${encodeURIComponent(checkParam)}`); |
||||
} else if ('MozWebSocket' in window) { |
||||
// this.$ws = new MozWebSocket(this.WSS_URL + userId + "," + sessionId);
|
||||
this.$ws = new MozWebSocket(`${this.WSS_URL}${userId}&${encodeURIComponent(checkParam)}`); |
||||
} else { |
||||
showDialog({ |
||||
message: '您的浏览器不支持websocket协议,建议您使用Android系统版本大于4.0,ios系统版本大于7.0。' |
||||
}).then(() => { |
||||
utils.closeWindow(); //关闭页面
|
||||
}); |
||||
} |
||||
// this.$ws.onopen = this.wsOpen;
|
||||
this.$ws.addEventListener("open", event => { |
||||
this.wsOpen(); |
||||
}) |
||||
|
||||
// this.$ws = new WebSocket(this.WSS_URL + userId + "," + sessionId);
|
||||
this.$ws.onclose = this.wsClose; |
||||
this.$ws.onmessage = this.onMessage; |
||||
this.$ws.onerror = this.wsError; |
||||
|
||||
callback && callback(); |
||||
|
||||
// store.commit('set_otherTips', "连接中...");
|
||||
document.setTitle('连接中...') |
||||
} catch (e) { |
||||
wsConnection.reconnect() |
||||
console.log(e); |
||||
} |
||||
}, |
||||
wsResendMessage() { |
||||
var sendFailList = store.state.sendFailList;//取出上次发送失败的消息
|
||||
if (sendFailList.length > 0) { |
||||
for (let i = 0; i < sendFailList.length; i++) { |
||||
(function (i) {//这个匿名函数生成了闭包的效果,新建了一个作用域,这个作用域接收到每次循环的i值保存了下来,即使循环结束,闭包形成的作用域也不会被销毁
|
||||
setTimeout(function () { |
||||
var dataResend = sendFailList[i]; |
||||
console.log("发送失败的数据ID==", dataResend.timestamp); |
||||
|
||||
wsConnection.sendMessage(dataResend, wsConnection.globalCallback); |
||||
store.commit('set_currentItemId', dataResend.timestamp); |
||||
store.commit('set_findActiveItem', false);//发送成功
|
||||
}, 200); |
||||
})(i); |
||||
} |
||||
|
||||
store.commit('del_sendFailList');//删除发送成功的
|
||||
console.log(store.state.sendFailList); |
||||
} |
||||
}, |
||||
//打开websocket
|
||||
wsOpen: function (e) { |
||||
var that = this; |
||||
//开始websocket心跳
|
||||
wsConnection.startWsHeartbeat(); |
||||
// toast('会话建立成功');
|
||||
// store.commit('set_otherTips', "洋洋客服");
|
||||
document.setTitle('洋洋客服') |
||||
|
||||
//执行重新发送
|
||||
// if(wsConnection.isReconnet){
|
||||
// wsConnection.wsResendMessage();
|
||||
// wsConnection.isReconnet = false;
|
||||
// }
|
||||
|
||||
console.log("ws success " + new Date().format("yyyy-MM-dd hh:mm:ss")); |
||||
}, |
||||
onMessage: function (event) { |
||||
//每次接收到服务端消息后 重置websocket心跳
|
||||
wsConnection.resetHeartbeat(); |
||||
let res = event.data; |
||||
try { |
||||
if (event.data === 'pong') { |
||||
console.log("收到心跳了:", event.data); |
||||
return; |
||||
} |
||||
|
||||
if (event.data.match(/^\d+$/)) { |
||||
wsConnection.globalCallback && wsConnection.globalCallback(event.data); |
||||
return; |
||||
} |
||||
|
||||
res = JSON.parse(res); |
||||
console.log("收到消息啦:", res.data || res); |
||||
if (res.data.cmd === 'ServiceSessionClosedEvent' && store.state.hxOpenSucceseds.includes(res.data.sessionId)) { |
||||
if(store.state.integratedUser){ |
||||
utils.useSdk(res.data) |
||||
}else{ |
||||
setTimeout(() => { |
||||
utils.useSdk(res.data) |
||||
}, 100); |
||||
} |
||||
} |
||||
|
||||
if (res.code != 0) { |
||||
// toast(data.msg);
|
||||
if (res.code === 10001) { |
||||
// 删除已经失效或过期的token(不删除也可以,因为登录后覆盖)
|
||||
sessionStorage.removeItem("token"); |
||||
wsConnection.$ws.close(); |
||||
|
||||
if (store.state.notitle === '1') { |
||||
toast('当前会话已结束,请刷新页面或关闭页面重新进入'); |
||||
return |
||||
} |
||||
|
||||
showConfirmDialog({ |
||||
title: '提示', |
||||
message: '当前会话已结束,请刷新页面或关闭页面重新进入', |
||||
confirmButtonText: '取消', |
||||
cancelButtonText: '确认', |
||||
confirmButtonColor: '#000000', |
||||
cancelButtonColor: '#000000' |
||||
}).then(() => { |
||||
// on cancel (现在是右边的取消按钮)
|
||||
// utils.closeWindow();
|
||||
// router.replace({
|
||||
// path: '/404' // 到登录页重新获取token
|
||||
// })
|
||||
}).catch(() => { |
||||
// on confirm (现在是左边的确认按钮)
|
||||
//确认重新刷新页面获取token
|
||||
// window.location.reload();
|
||||
utils.AppReloadHtml(); |
||||
}); |
||||
} else { |
||||
if (res.msg) { |
||||
toast(res.msg); |
||||
} |
||||
} |
||||
|
||||
return; |
||||
} |
||||
|
||||
//是否转接人工参数
|
||||
// if (res.data.cmd && res.data.cmd == "ServiceSessionCreatedEvent") {
|
||||
// store.dispatch('setTransferCustomer', 2);
|
||||
// }
|
||||
|
||||
//客服接起不排队了
|
||||
if (res.data.cmd && res.data.cmd == "ServiceSessionOpenedEvent") { |
||||
store.commit('set_queuedTimeout', true); |
||||
|
||||
store.commit('set_msgStatus', '');//人工客服排队状态值 等待中:"1",其他:""
|
||||
store.commit('set_msgReply', false);//人工客服排队是否调取自动回复话术
|
||||
} |
||||
|
||||
if (res.data.cmd && res.data.cmd == "ServiceSessionClosedEvent") { |
||||
wsConnection.closeSession = true;//是关闭会话
|
||||
setTimeout(() => { |
||||
store.commit('set_queuedTimeout', true); |
||||
store.dispatch('setTransferCustomer', 1);//人工改为机器人
|
||||
wsConnection.$ws.close(); |
||||
|
||||
}, 2500) |
||||
} |
||||
|
||||
//服务端发送来的消息存到vuex
|
||||
store.dispatch('sendingDrtificial', res.data).then((res) => { |
||||
}).catch(err => { |
||||
console.log(err) |
||||
}); |
||||
|
||||
//如果会话关闭不用推送消息回执
|
||||
if (res.data && res.data.chatId) { |
||||
let chatParam = JSON.stringify(res.data); |
||||
// wsConnection.$ws.send(chatParam, '');
|
||||
wsConnection.sendMessage(chatParam, ''); |
||||
console.log("发送消息回执", chatParam) |
||||
} |
||||
} catch (error) { |
||||
console.log(error) |
||||
} |
||||
}, |
||||
//发送数据,接收处理数据
|
||||
sendMessage: function (data, callback) { |
||||
//存储事件
|
||||
wsConnection.globalCallback = callback; |
||||
// console.log(data, callback)
|
||||
|
||||
switch (wsConnection.$ws.readyState) { |
||||
//CONNECTING:值为0,表示正在连接。
|
||||
case wsConnection.$ws.CONNECTING: |
||||
setTimeout(function () { |
||||
wsConnection.sendMessage(data, callback); |
||||
}, 1000); |
||||
break; |
||||
//OPEN:值为1,表示连接成功,可以通信了。
|
||||
case wsConnection.$ws.OPEN: |
||||
wsConnection.$ws.send(data); |
||||
break; |
||||
//CLOSING:值为2,表示连接正在关闭。
|
||||
case wsConnection.$ws.CLOSING: |
||||
setTimeout(function () { |
||||
wsConnection.sendMessage(data, callback); |
||||
}, 1000); |
||||
break; |
||||
//CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
|
||||
case wsConnection.$ws.CLOSED: |
||||
// do something
|
||||
break; |
||||
default: |
||||
// this never happens
|
||||
break; |
||||
} |
||||
|
||||
//重新监听onmessage,并把数据传给回调函数
|
||||
// wsConnection.onmessage = ev =>{
|
||||
// callback && callback(ev)
|
||||
// }
|
||||
}, |
||||
//重启websocket
|
||||
reconnect: function () { |
||||
let _this = this; |
||||
if (_this.lockReturn) { |
||||
return; |
||||
} |
||||
_this.isReconnet = true; |
||||
_this.lockReturn = true; |
||||
|
||||
//没连接上会一直重连,设置延迟避免请求过多
|
||||
if (_this.reconectNum < this.reconectTimes) { |
||||
_this.timeoutNum && clearTimeout(_this.timeoutNum); |
||||
_this.timeoutNum = setTimeout(function () { |
||||
_this.initWebSocket(); |
||||
// toast('正在重连,请稍后...');
|
||||
// store.commit('set_otherTips', "正在重连...");
|
||||
document.setTitle('正在重连...') |
||||
console.info(`正在重连第${_this.reconectNum + 1}次`) |
||||
_this.reconectNum++ |
||||
_this.lockReturn = false; |
||||
}, 4000); |
||||
} else { |
||||
// toast('重连次数超出设定值,不再重连,请检查配置项');
|
||||
showDialog({ |
||||
message: '重连次数超出设定值,不再重连。请检查您的网络设置,点击“确定”刷新页面,重新建立会话。' |
||||
}).then(() => { |
||||
// window.location.reload();
|
||||
utils.AppReloadHtml(); |
||||
}); |
||||
} |
||||
}, |
||||
//心跳检测,每5s心跳一次
|
||||
startWsHeartbeat: function () { |
||||
let _this = this; |
||||
this.timeoutObj && clearTimeout(this.timeoutObj); |
||||
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj); |
||||
|
||||
this.timeoutObj = setTimeout(() => { |
||||
//这里发送一个心跳,后端收到后,返回一个心跳消息,
|
||||
//onmessage拿到返回的心跳就说明连接正常
|
||||
if (wsConnection.$ws && wsConnection.$ws.readyState != wsConnection.$ws.CLOSED) { |
||||
//关闭当前会话后不发送ping
|
||||
if (!wsConnection.closeSession) { |
||||
_this.$ws.send("ping"); |
||||
console.log("ping!", new Date().format("yyyy-MM-dd hh:mm:ss")); |
||||
} |
||||
} |
||||
|
||||
//如果超过一定时间还没重置,说明后端主动断开了
|
||||
_this.serverTimeoutObj = setTimeout(() => { |
||||
//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
|
||||
_this.$ws.close(); |
||||
}, _this.timeout) |
||||
}, this.timeout); |
||||
}, |
||||
//重置websocket心跳
|
||||
resetHeartbeat: function () { |
||||
let _this = this; |
||||
this.reconectNum = 0; |
||||
clearTimeout(_this.timeoutObj); |
||||
clearTimeout(_this.serverTimeoutObj); |
||||
_this.startWsHeartbeat(); |
||||
}, |
||||
wsError: function (err) { |
||||
console.log(err, 'ws error'); |
||||
// store.commit('set_otherTips', "洋洋客服(未连接)");
|
||||
document.setTitle('洋洋客服(未连接)') |
||||
wsConnection.reconnect() |
||||
}, |
||||
wsClose: function (e) { |
||||
// console.log(e, 'ws close')
|
||||
console.log("连接关闭! " + new Date().format("yyyy-MM-dd hh:mm:ss")); |
||||
console.log('websocket 断开: ' + e.code + ' 关闭的原因:' + e.reason + ' 是否正常关闭' + e.wasClean); |
||||
|
||||
if (e.code !== 1000) { |
||||
// toast('当前会话已结束');
|
||||
this.lockReturn = false |
||||
clearTimeout(this.timeoutNum) |
||||
wsConnection.reconnect(); |
||||
// store.commit('set_otherTips', "洋洋客服(未连接)");
|
||||
document.setTitle('洋洋客服(未连接)') |
||||
//当前转成机器人
|
||||
// store.dispatch('setTransferCustomer', 1);
|
||||
} else { |
||||
this.$ws = null; |
||||
clearInterval(this.timeoutObj) |
||||
clearTimeout(this.serverTimeoutObj) |
||||
// toast('会话已关闭');
|
||||
// store.commit('set_otherTips', "洋洋客服");
|
||||
document.setTitle('洋洋客服') |
||||
wsConnection.$ws.close(); |
||||
} |
||||
}, |
||||
}; |
||||
|
||||
//时间格式化
|
||||
Date.prototype.format = function (fmt) { |
||||
var o = { |
||||
"M+": this.getMonth() + 1, //月份
|
||||
"d+": this.getDate(), //日
|
||||
"h+": this.getHours(), //小时
|
||||
"m+": this.getMinutes(), //分
|
||||
"s+": this.getSeconds(), //秒
|
||||
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
|
||||
"S": this.getMilliseconds() //毫秒
|
||||
}; |
||||
if (/(y+)/.test(fmt)) { |
||||
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); |
||||
} |
||||
for (var k in o) { |
||||
if (new RegExp("(" + k + ")").test(fmt)) { |
||||
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); |
||||
} |
||||
} |
||||
return fmt; |
||||
} |
||||
|
||||
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
|
||||
window.onbeforeunload = function (event) { |
||||
event = event || window.event; |
||||
try { |
||||
// toast('onbeforeunload关闭当前会话窗口啦');
|
||||
var oldSessionId = localStorage.getItem('sessionId'); |
||||
var liccsAds = localStorage.getItem('liccsAds'); // 记录用户弹窗提示
|
||||
// sessionStorage.removeItem("manualService");
|
||||
if (wsConnection.$ws) { |
||||
wsConnection.$ws.close(); |
||||
} |
||||
//清除本地缓存
|
||||
localStorage.clear(); |
||||
localStorage.sessionId = oldSessionId || ''; |
||||
localStorage.liccsAds = liccsAds || ''; |
||||
//清除session
|
||||
sessionStorage.clear(); |
||||
} catch (error) { |
||||
console.log(error.message) |
||||
} |
||||
} |
||||
|
||||
//安卓处理解决方案
|
||||
window.onunload = function (event) { |
||||
event = event || window.event; |
||||
try { |
||||
// toast('onunload关闭当前会话窗口啦');
|
||||
var oldSessionId = localStorage.getItem('sessionId'); |
||||
var liccsAds = localStorage.getItem('liccsAds'); // 记录用户弹窗提示
|
||||
// sessionStorage.removeItem("manualService");
|
||||
if (wsConnection.$ws) { |
||||
wsConnection.$ws.close(); |
||||
} |
||||
//清除本地缓存
|
||||
localStorage.clear(); |
||||
localStorage.sessionId = oldSessionId || ''; |
||||
localStorage.liccsAds = liccsAds || ''; |
||||
//清除session
|
||||
sessionStorage.clear(); |
||||
} catch (error) { |
||||
console.log(error.message) |
||||
} |
||||
} |
||||
|
||||
//抛出websocket对象
|
||||
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> |
||||
Loading…
Reference in new issue