代码提交

master
wujingling0908 5 days ago
parent 5a760cfc20
commit 06070a8e24
  1. 6
      src/assets/style/chat.css
  2. 21
      src/components/abstract.vue
  3. 9
      src/components/appraise-popup.vue
  4. 18
      src/components/chat-content.vue
  5. 124
      src/components/chat-new.vue
  6. 6
      src/components/footer-bar-input.vue
  7. 1
      src/components/send-message.vue
  8. 3
      src/components/text-content.vue
  9. 6
      src/components/text-folding.vue
  10. 1
      src/components/welcomes.vue
  11. 4
      src/main.js
  12. 59
      src/utils/index.js
  13. 24
      src/views/chat.vue

@ -449,7 +449,6 @@ img.avatarUrl-img[lazy="loading"] {
line-height: 40rem;
color: #fff;
box-sizing: border-box;
margin: 14rem 5rem;
position: relative;
background: #579aff;
box-shadow:
@ -469,7 +468,6 @@ img.avatarUrl-img[lazy="loading"] {
line-height: 40rem;
color: #fff;
box-sizing: border-box;
margin: 14rem 5rem;
position: relative;
background: rgb(172, 205, 255);
box-shadow:
@ -477,6 +475,7 @@ img.avatarUrl-img[lazy="loading"] {
inset 0 -4rem 15rem 0 rgba(204, 225, 251, 0.25);
border-radius: 8rem;
pointer-events: none;
margin: 16rem 0;
}
.question-answer-con p .jump-web-button::after,
@ -975,7 +974,6 @@ img.avatarUrl-img[lazy="loading"] {
margin-right: 3rem;
}
.elder-wrapper .question-answer-con .open-wx-web-button {
width: 311rem;
height: 40rem;
background: #025cea;
border-radius: 6rem;
@ -983,7 +981,7 @@ img.avatarUrl-img[lazy="loading"] {
font-family: PingFangSC-Medium;
font-size: 13rem;
color: #ffffff;
margin-top: 24rem;
margin: 16rem 0;
}
.elder-wrapper .answer-items {

@ -2,7 +2,7 @@
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";
import { getHiddenProp, getVisibilityState, decryptByVersionNo1, decodeEntryOpenid, isSit, Encrypt, isDev, isProd } from "@/utils";
import orderIcon from '@/assets/images/chat/order.svg'
import todoIcon from '@/assets/images/chat/todo.svg'
import rightsIcon from '@/assets/images/chat/rights‌.svg'
@ -43,7 +43,7 @@ const abstractList = computed(() => [
icon: rightsIcon,
},
]
.filter(item => item.num > 0)
// .filter(item => item.num > 0)
)
const businessHandle = (userId) => {
if (
@ -80,11 +80,6 @@ const getIdentityCardno = () => {
if (!versionNo || !pidCipher) return ''
const identityCardno = decryptByVersionNo1(versionNo, pidCipher)
const testNo = decryptByVersionNo1(versionNo, store.userInfo?.sourceUserId)
console.log('decrypt testNo =>', JSON.stringify(testNo))
console.log('versionNo', versionNo)
console.log(store.userInfo?.entryOpenid)
console.log('decrypt pid =>', JSON.stringify(identityCardno))
return isIdentityNo(identityCardno) ? identityCardno.trim() : ''
}
@ -214,12 +209,6 @@ onActivated(async () => {
/* 样式描述:用于正文标题 */
color: #17204d;
margin-bottom: 4rem;
img {
width: 18rem;
margin-right: 2rem;
vertical-align: bottom;
}
}
.list {
@ -241,7 +230,11 @@ onActivated(async () => {
background: #fcfdfd;
box-shadow: 0rem 4rem 10rem 0rem #e7effd;
img {
width: 18rem;
margin-right: 2rem;
vertical-align: bottom;
}
.title {
font-family: PingFang SC;
font-size: 12rem;

@ -228,7 +228,14 @@ const submitForm = (type) => {
appraiseDisBtn.value.disabled = false
return
}
appraiseBtnDisabled()
// 评价成功后隐藏整块 help-button-box
if (baseItem.value?.item) {
baseItem.value.item.hideHelpButton = true
} else if (appraiseDisBtn.value?.parentNode) {
appraiseDisBtn.value.parentNode.style.display = 'none'
} else {
appraiseBtnDisabled()
}
emit('handleAppraiseClose', false) // 关闭弹层
store.setChatList(res.data)
scrollTopBody()

@ -709,7 +709,7 @@
<!--问题反馈 -->
<div
class="help-button-box flex flex-align-center before-top"
v-if="item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
v-if="!isHistory && !item.hideHelpButton && item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
>
<button
class="help-button-item"
@ -780,7 +780,7 @@
<!--问题反馈 -->
<div
class="help-button-box flex flex-align-center before-top"
v-if="item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
v-if="!isHistory && !item.hideHelpButton && item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
>
<button
class="help-button-item"
@ -982,7 +982,7 @@
<!--问题反馈 -->
<div
class="help-button-box flex flex-align-center before-top"
v-if="item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
v-if="!isHistory && !item.hideHelpButton && item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
>
<button
class="help-button-item"
@ -1041,7 +1041,7 @@
<!--问题反馈 -->
<div
class="help-button-box flex flex-align-center before-top"
v-if="item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
v-if="!isHistory && !item.hideHelpButton && item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
>
<button
class="help-button-item"
@ -1078,7 +1078,7 @@
<!--问题反馈 -->
<div
class="help-button-box flex flex-align-center before-top"
v-if="item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ' && item.specialData"
v-if="!isHistory && !item.hideHelpButton && item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ' && item.specialData"
>
<button
class="help-button-item"
@ -1116,7 +1116,7 @@
/>
<div
class="help-button-box flex flex-align-center before-top"
v-if="item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ' && item.specialData"
v-if="!isHistory && !item.hideHelpButton && item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ' && item.specialData"
>
<button
class="help-button-item"
@ -1395,7 +1395,7 @@
<!--问题反馈 -->
<div
class="help-button-box flex flex-align-center before-top"
v-if="item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
v-if="!isHistory && !item.hideHelpButton && item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
>
<button
class="help-button-item"
@ -1454,7 +1454,7 @@
<!--问题反馈 -->
<div
class="help-button-box flex flex-align-center before-top"
v-if="item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
v-if="!isHistory && !item.hideHelpButton && item.cmd != 'RGFWHS' && item.cmd != 'QXRGPJ'"
>
<button
class="help-button-item"
@ -1697,6 +1697,7 @@ export default {
window.jumpOpenWeb = function (type, url, urlType) {
console.log('chat-content', type, url);
var params = {};
console.log('YY_GWXCX',type, url)
if (type === "YY_0000") {
//保单管家
@ -2373,6 +2374,7 @@ export default {
reqAndResType: "3",
timestamp: new Date().valueOf(),
event: current,
item,
};
this.$emit("handleAppraise", evaluation === 2, baseHandle);

@ -16,6 +16,8 @@ import imageService from '@/utils/imageService'
import { messageSend } from '@/utils/api'
import { Post } from '@/utils/https'
import { SJHLCmdList } from '@/utils/constant'
import { jumpOpenWeb } from '@/utils'
import utils from '@/utils/utils'
import MeScroll from 'mescroll.js'
import 'mescroll.js/mescroll.min.css'
@ -78,20 +80,11 @@ function appraiseIsClose(val, param) {
if (current) current.disabled = false
return
}
if (current) {
try {
const settled = current.parentNode.children[1]
const unsettled = current.parentNode.children[0]
current.disabled = true
current.classList.add('active')
if (current.innerText === '有帮助') {
unsettled.disabled = true
} else if (current.innerText === '没有帮助') {
settled.disabled = true
}
} catch (err) {
console.log(err)
}
// 评价成功后隐藏整块 help-button-box
if (param?.item) {
param.item.hideHelpButton = true
} else if (current?.parentNode) {
current.parentNode.style.display = 'none'
}
if (res?.data != null) {
store.setChatList(res.data)
@ -566,9 +559,110 @@ onMounted(async () => {
restoreScrollPosition()
}
})
const invokedJumpKeys = new Set()
function getLatestMessageText(item) {
return String(
item?.msgText ||
item?.message?.[0]?.options?.label ||
item?.raw?.msgText ||
item?.answer ||
item?.content ||
'',
)
}
function extractJumpOpenWebCalls(msgText) {
const text = String(msgText || '')
const calls = []
// 1) 显式 jumpOpenWeb('type','url','urlType')
if (text.includes('jumpOpenWeb')) {
console.log('jumpOpenWeb');
const re =
/jumpOpenWeb\s*\(\s*(['"`])(.*?)\1\s*,\s*(['"`])(.*?)\3(?:\s*,\s*(['"`])(.*?)\5)?\s*\)/g
let match = re.exec(text)
while (match) {
console.log('match[2]',match[2]);
calls.push({
type: match[2],
url: match[4],
urlType: match[6],
})
match = re.exec(text)
}
}
// 2) 微信开放标签 wx-open-launch-weapp → 小程序内自动跳转(与 text-folding 一致)
// 示例:<wx-open-launch-weapp class="open-wx-web-button" path="/packageMember/...">
if (text.includes('wx-open-launch-weapp') ) {
const pathRe = /<wx-open-launch-weapp[^>]*\spath="([^"]+)"/gi
let pathMatch = pathRe.exec(text)
const seenPath = new Set()
console.log('pathMatch',pathMatch);
while (pathMatch) {
const path = pathMatch[1]
if (path && !seenPath.has(path)) {
seenPath.add(path)
calls.push({
type: 'YY_GWXCX',
url: path,
urlType: undefined,
})
}
pathMatch = pathRe.exec(text)
}
}
console.log('calls', calls)
return calls
}
function maybeAutoJumpOpenWeb(list) {
const arr = Array.isArray(list) ? list : list?.value
if (!arr?.length) return
const latest = arr[arr.length - 1]
if (!latest) return
// 用户消息不自动跳转
const fromFlag = String(latest?.fromFlag ?? latest?.raw?.fromFlag ?? '')
if (fromFlag === '1' || latest?.type === 'user') return
const msgText = getLatestMessageText(latest)
const calls = extractJumpOpenWebCalls(msgText)
if (!calls.length) return
const msgKey = String(latest?.VIEW_ID || latest?.timestamp || latest?.id || arr.length)
calls.forEach(({ type, url, urlType },index) => {
if (!type || !url) return
const key = `${msgKey}::${type}::${url}::${urlType || ''}`
if (invokedJumpKeys.has(key)) return
invokedJumpKeys.add(key)
// 与 text-folding 一致:同消息只自动跳一次,并稍延时等待渲染
const storageKey = `weapp_auto_jump_${msgKey}`
if (type === 'YY_GWXCX') {
try {
if (sessionStorage.getItem(storageKey)) return
sessionStorage.setItem(storageKey, '1')
} catch (e) { }
console.log('[auto jumpOpenWeb YY_GWXCX]', url)
setTimeout(() => {
if(index === 0) jumpOpenWeb(type, url, urlType)
}, 300)
return
}
console.log('[auto jumpOpenWeb]', type, url, urlType)
if(index === 0) jumpOpenWeb(type, url, urlType)
})
}
watch(
chatArr,
() => {
(list) => {
// 最新消息 a 标签内 jumpOpenWeb 入参实时调用
maybeAutoJumpOpenWeb(list)
// 从其他路由返回、正在恢复滚动位置时,不要强制滚到底部
if (isRestoringScroll.value || shouldRestoreScroll.value) return
nextTick(() => {

@ -37,16 +37,16 @@
</div>
<div class="input-box-txt">
<div v-if="!soundFlag" style="width: 100%">
<textarea class="input-txt" placeholder="请简要描述您的问题" rows="1" ref="sendMessageArea" v-model="sendMessage"
<textarea class="input-txt" placeholder="有什么问题想要问我吗?" rows="1" ref="sendMessageArea" v-model="sendMessage"
@input.stop="monitorInputs" @blur.stop="monitorInputsBlur" @focus.stop="monitorInputsFocus($event)"
@keydown.delete="deleteFace($event)" is-pc="移动端" v-if="noTouchStop" :readonly="elderAndOutwork"></textarea>
<textarea class="input-txt" placeholder="请简要描述您的问题" rows="1" ref="sendMessageArea" v-model="sendMessage"
<textarea class="input-txt" placeholder="有什么问题想要问我吗?" rows="1" ref="sendMessageArea" v-model="sendMessage"
@input.stop="monitorInputs" @blur.stop="monitorInputsBlur" @focus.stop="monitorInputsFocus($event)"
@keydown.delete="deleteFace($event)" @touchend.stop="clickInput" is-pc="移动端" v-else-if="!isPc"
:readonly="elderAndOutwork"></textarea>
<textarea class="input-txt" placeholder="请简要描述您的问题" rows="1" ref="sendMessageArea" v-model="sendMessage"
<textarea class="input-txt" placeholder="有什么问题想要问我吗?" rows="1" ref="sendMessageArea" v-model="sendMessage"
@input.stop="monitorInputs" @blur.stop="monitorInputsBlur" @focus.stop="monitorInputsFocus($event)"
@keydown.delete="deleteFace($event)" @touchend.stop="clickInput" is-pc="pc端调试模式"
@keydown.stop="listenEnter($event)" required v-else :readonly="elderAndOutwork"></textarea>

@ -439,6 +439,7 @@ onMounted(() => {
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
padding-top: 10rem;
}
.answer-items {

@ -433,11 +433,12 @@ function manualAnswer(event, item) {
display: flex;
flex-wrap: wrap;
gap: 8rem;
padding-top: 10rem;
margin-top: 10rem;
}
.answer-items {
padding: 6rem 12rem;
/* padding: 6rem 12rem; */
background: #f0f6ff;
border: 1rem solid #d6e4ff;
border-radius: 16rem;

@ -1,5 +1,5 @@
<template>
<div class="text-template-box" v-if="introduce">
<div class="text-template-box aaa" v-if="introduce">
<div
class="text-folding-box"
:class="showTotal ? 'total-introduce' : 'detailed-introduce'"
@ -115,6 +115,7 @@ function autoLaunchWeapp() {
if (sessionStorage.getItem(key)) return
sessionStorage.setItem(key, '1')
setTimeout(() => {
console.log(21212,m);
wx.miniProgram.navigateTo({ url: m[1] })
}, 300)
}
@ -521,6 +522,7 @@ function manualAnswer(event, item) {
flex-wrap: wrap;
gap: 8rem;
margin-top: 10rem;
padding-top: 10rem;
}
.content-question-answer--grid {
@ -531,7 +533,7 @@ function manualAnswer(event, item) {
}
.answer-items {
padding: 6rem 12rem;
/* padding: 6rem 12rem; */
background: #f0f6ff;
border: 1rem solid #d6e4ff;
border-radius: 16rem;

@ -34,6 +34,7 @@ onMounted(() => {
position: relative;
.welcome-title {
font-family: PingFang SC;
max-width: calc(100% - 100rem);
font-size: 20rem;
font-weight: 600;
line-height: normal;

@ -23,11 +23,11 @@ const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/
)
const currentHostname = window.location.hostname
const shouldShowVConsole = enableVConsoleDomains.includes(currentHostname)
if (shouldShowVConsole || (shouldEnableVConsole && isMobile)) {
// if (shouldShowVConsole || (shouldEnableVConsole && isMobile)) {
import('vconsole').then(({ default: VConsole }) => {
new VConsole()
})
}
// }
// 根据rem单位适配
window.onload = function () {

@ -456,6 +456,65 @@ export function decryptByVersionNo1(versionNo1Cipher, cipherText) {
}
return ''
}
/**
* Base64 AES 密文 -> 明文
* 例:SjU9XqI98fNbuE5mk/... + versionNo1 => hDjN0ynT8463W0jH98-eqBask4bW_UtDRjpnMFkqD-Y
*/
export function decryptBase64AesByVersionNo1(versionNo1Cipher, base64CipherText) {
return decryptByVersionNo1(versionNo1Cipher, base64CipherText)
}
/**
* 解析 entryOpenid 得到明文 openId
* 1) versionNo1 AES 解密
* 2) 若已是明文(非 JWT)则原样返回
*/
export function decodeEntryOpenid(entryOpenid, versionNo1Cipher = "") {
if (entryOpenid === undefined || entryOpenid === null || entryOpenid === "") return ""
const raw = String(entryOpenid).trim()
if (!raw) return ""
const normalizePlainOpenId = (text) => {
if (!text) return ""
let trimmed = String(text).replace(/^["']+|["']+$/g, "").trim()
if (!trimmed) return ""
try {
const parsed = JSON.parse(trimmed)
const iss = parsed?.iss ?? parsed?.openId ?? parsed?.openid ?? ""
if (iss) return String(iss).trim()
} catch (e) {}
return trimmed
}
if (versionNo1Cipher) {
const plain = decryptByVersionNo1(versionNo1Cipher, raw)
const openId = normalizePlainOpenId(plain)
if (openId) return openId
}
// 已是明文 openId(例如 hDjN0ynT...),不要把 JWT 当明文
if (/^eyJ/i.test(raw)) return ""
if (raw.length >= 20) return raw
return ""
}
/**
* 使用 versionNo1 将明文 openId 加密为 entryOpenid
*/
export function encodeEntryOpenid(openId, versionNo1Cipher = "") {
if (!openId || !versionNo1Cipher) return ""
const year = new Date().getFullYear()
const yearCandidates = [year, year - 1, year - 2, year + 1]
for (const y of yearCandidates) {
const versionNo1 = DecryptionECB(`${y}${y}${y}${y}`, versionNo1Cipher)
if (!versionNo1 || versionNo1.length < 32) continue
const key = versionNo1.substring(0, 16)
const iv = versionNo1.substring(16, 32)
if (key.length !== 16 || iv.length !== 16) continue
return Encrypt(key, iv, String(openId))
}
return ""
}
export function DecryptionECB(keyECB, valueECB) {
if (valueECB === undefined || valueECB === null || valueECB === '') return ''

@ -33,7 +33,7 @@ import chatNew from '@/components/chat-new.vue'
// import HumanAppraisePopup from "@/components/human-appraise-popup.vue";
import Utils from '@/utils/utils'
import sensors from '@/utils/sensors'
import { Decryption, DecryptionECB, Encrypt, isDev, isProd } from '@/utils'
import { Decryption, DecryptionECB, Encrypt, isDev, isProd, decodeEntryOpenid, decryptBase64AesByVersionNo1, encodeEntryOpenid } from '@/utils'
import { useRouter, onBeforeRouteLeave } from 'vue-router'
const router = useRouter()
@ -66,9 +66,9 @@ onActivated(() => {
}
nextTick(() => {
apply()
;[50, 120, 240, 480].forEach((d) => setTimeout(apply, d))
;[50, 120, 240, 480].forEach((d) => setTimeout(apply, d))
})
} catch (e) {}
} catch (e) { }
})
onBeforeRouteLeave(() => {
@ -80,7 +80,7 @@ onBeforeRouteLeave(() => {
if (top > 0) {
sessionStorage.setItem('yykf_chat_main_scroll_top', String(top))
}
} catch (e) {}
} catch (e) { }
})
onMounted(async () => {
@ -176,6 +176,7 @@ onMounted(async () => {
urlParams.value = Utils.getUrlParams()
initToken()
console.log('window', window)
// 渠道校验
try {
const checkRes = await Post("config/downSource", {
@ -238,6 +239,21 @@ const initToken = async () => {
blacklistFlag: data.blacklistFlag || '',
}
store.setUserInfo(nextUserInfo)
const versionNo = nextUserInfo.versionNo1 || 'DyDzGOFvYWWyQ8jlL5BqjdtvRNj4/UJvWWLm890KZiJ/5LrmwLjwcpfDXI5w2knS'
// Base64 AES 密文 -> 明文
const base64Cipher = 'SjU9XqI98fNbuE5mk/r3AQy3q7sZtWE6ig/urNWQw9PfYhp5eb/J9iLezdOGsxda'
const aesPlain = decryptBase64AesByVersionNo1(versionNo, base64Cipher)
const apiDecoded = decodeEntryOpenid(nextUserInfo.entryOpenid, versionNo)
console.log('Base64 AES 解密 =>', {
base64Cipher,
aesPlain,
expectedPlain: 'hDjN0ynT8463W0jH98-eqBask4bW_UtDRjpnMFkqD-Y',
ok: aesPlain === 'hDjN0ynT8463W0jH98-eqBask4bW_UtDRjpnMFkqD-Y',
apiEntryOpenid: nextUserInfo.entryOpenid,
apiDecoded,
sourceUserId: nextUserInfo.sourceUserId,
})
if (nextUserInfo.blacklistFlag === '1' && nextUserInfo.bpErrorContent) {
const query = new URLSearchParams({

Loading…
Cancel
Save