聊天热点卡片内容渲染

master
wujingling0908 2 weeks ago
parent 52d322555e
commit f888312037
  1. 8
      src/components/chat-content.vue
  2. 7
      src/components/chat-new.vue
  3. 23
      src/components/hot-question-new.vue
  4. 26
      src/components/hot-question.vue
  5. 2
      src/main.js
  6. 121
      src/store/app.js
  7. 1
      src/utils/api.js

@ -544,7 +544,7 @@
<!-- 加评价 --> <!-- 加评价 -->
<div class="chat-content-left" <div class="chat-content-left"
v-if="(item.reqAndResType == '01' && item.msgType == '01') && item.cmd !== 'CPZX'"> v-if="((item.reqAndResType == '01' || item.reqAndResType == '1') && (item.msgType == '01' || item.msgType == '1')) && item.cmd !== 'CPZX'">
<div class="content-left-icon"> <div class="content-left-icon">
<DynamicImage class="avatarUrl-img" :image-key="headerAvatar.robotUrl" /> <DynamicImage class="avatarUrl-img" :image-key="headerAvatar.robotUrl" />
<div class="chat-name" v-if="item.seatNum && item.seatNum != '调度员'"> <div class="chat-name" v-if="item.seatNum && item.seatNum != '调度员'">
@ -702,7 +702,7 @@
<!-- 多轮 文本---> <!-- 多轮 文本--->
<div class="chat-content-left" <div class="chat-content-left"
v-if="item.reqAndResType == '05' && item.msgType == '01' && !item.cmd.includes('CPLB')"> v-if="item.reqAndResType == '05' && item.msgType == '01' && !(item.cmd || '').includes('CPLB')">
<div class="content-left-icon"> <div class="content-left-icon">
<DynamicImage class="avatarUrl-img" :image-key="headerAvatar.robotUrl" /> <DynamicImage class="avatarUrl-img" :image-key="headerAvatar.robotUrl" />
<div class="chat-name" v-if="item.seatNum && item.seatNum != '调度员'"> <div class="chat-name" v-if="item.seatNum && item.seatNum != '调度员'">
@ -1181,6 +1181,7 @@ import Audio from './Audio.vue';
import ProductCardList from "@/components/product-card-list.vue"; import ProductCardList from "@/components/product-card-list.vue";
import policyFormTable from "@/components/policy-form-table.vue"; import policyFormTable from "@/components/policy-form-table.vue";
import DynamicImage from '@/components/common/DynamicImage.vue'; import DynamicImage from '@/components/common/DynamicImage.vue';
import imageService from '@/utils/imageService';
import wx from 'weixin-js-sdk'; import wx from 'weixin-js-sdk';
export default { export default {
props: ["historylist", "tokenExist", "lostChatInfoNum"], props: ["historylist", "tokenExist", "lostChatInfoNum"],
@ -1876,7 +1877,8 @@ export default {
}, },
async ImagePreviewBox(src) { async ImagePreviewBox(src) {
src = src || ""; src = src || "";
const imageUrl = await this.$getImageUrl(src) const imageUrl = await imageService.getImageByKey(src)
if (!imageUrl) return
// var timestamp = event.currentTarget.dataset.timestamp; // var timestamp = event.currentTarget.dataset.timestamp;
// var currentIndex = this.timestampList.indexOf(timestamp); // var currentIndex = this.timestampList.indexOf(timestamp);

@ -241,8 +241,11 @@ const getChatHistory = async () => {
} }
} }
// 设置列表数据 // 设置列表数据(历史消息也走同一套字段归一,保证 msgType=01 卡片能显示)
store.chatArr = dataList.concat(store.chatArr) const normalizedList = store.normalizeHistoryMessages
? store.normalizeHistoryMessages(dataList)
: dataList
store.chatArr = normalizedList.concat(store.chatArr)
_.map(store.chatArr, (item) => { _.map(store.chatArr, (item) => {
// 历史记录不展示数据互联 // 历史记录不展示数据互联
if (SJHLCmdList.includes(item.cmd) && !_.isEmpty(item.VIEW_ID)) { if (SJHLCmdList.includes(item.cmd) && !_.isEmpty(item.VIEW_ID)) {

@ -46,12 +46,31 @@ const initHotQuestion = async () => {
console.log(error); console.log(error);
}); });
} }
const handleAskClick=(record)=>{ const manualService = inject('manualService', ref('1'))
const isItemsubmit = ref(true)
const handleAskClick = (record) => {
const text = record?.name const text = record?.name
if (!text) return if (!text) return
messageSend(text).catch((err) => { if (String(manualService.value) === '2') return
if (!isItemsubmit.value) return
isItemsubmit.value = false
const sendParmas = {
reqAndResType: '1',
msgText: text,
msgSource: manualService.value ?? store?.manualService,
questionType: 2,
}
const request = store?.sendingRobot?.(sendParmas) || messageSend(text, { questionType: 2 })
Promise.resolve(request)
.catch((err) => {
console.log(err) console.log(err)
}) })
.finally(() => {
isItemsubmit.value = true
})
} }
onMounted(async () => { onMounted(async () => {
await initHotQuestion() await initHotQuestion()

@ -152,14 +152,17 @@
</template> </template>
<script setup> <script setup>
import { ref, inject, onMounted, nextTick } from 'vue' import { ref, computed, inject, onMounted, nextTick } from 'vue'
import { showToast, Tab, Tabs, Cell } from 'vant' import { showToast, Tab, Tabs, Cell } from 'vant'
import { Post } from '@/utils/https' import { Post } from '@/utils/https'
import utils from '@/utils/utils' import utils from '@/utils/utils'
import wx from 'weixin-js-sdk' import wx from 'weixin-js-sdk'
import exchangeImg from '@/assets/images/icon-exchange.png'
import hotBg from '@/assets/images/hot-bg.png'
const props = defineProps({ const props = defineProps({
isTab: { type: Boolean, default: true }, isTab: { type: Boolean, default: true },
isQA: { type: Boolean, default: false },
}) })
const store = inject('appStore', null) const store = inject('appStore', null)
@ -183,9 +186,13 @@ const timeEnd = ref(1)
const clickNum = ref(0) const clickNum = ref(0)
const group = ref(0) const group = ref(0)
const exchangeIcon = ref('') const exchangeIcon = ref('')
const refreshPic = { exchange: exchangeImg }
const hotUrl = hotBg
const one_list_name = ref('热点')
const isOnlyOneTab = computed(() => !tabsList.value || tabsList.value.length === 0)
const isExchange = computed(() => !props.isQA && hotList.value.length > 4)
const isBtnsubmit = ref(true) const isBtnsubmit = ref(true)
const isItemsubmit = ref(true) const isItemsubmit = ref(true)
const isQA = ref(false)
const planCode = ref('') const planCode = ref('')
onMounted(() => { onMounted(() => {
@ -238,7 +245,7 @@ function initHotQuestionYTH(callback) {
function initViewState() { function initViewState() {
footerMore.value = false footerMore.value = false
if (isQA.value && active.value === 0 && hotList.value.length > 4) { if (props.isQA && active.value === 0 && hotList.value.length > 4) {
footerMore.value = true footerMore.value = true
} }
} }
@ -252,10 +259,10 @@ function activeChange(e) {
if (active.value === 0) { if (active.value === 0) {
len = hotList.value.length len = hotList.value.length
} else { } else {
const tabIndex = isQA.value ? active.value - 1 : active.value - 1 const tabIndex = props.isQA ? active.value - 1 : active.value - 1
len = tabsList.value[tabIndex] ? tabsList.value[tabIndex].question.length : 0 len = tabsList.value[tabIndex] ? tabsList.value[tabIndex].question.length : 0
} }
if ((active.value === 0 && !isQA.value) || len <= 4) { if ((active.value === 0 && !props.isQA) || len <= 4) {
footerMore.value = false footerMore.value = false
} }
} }
@ -356,6 +363,15 @@ function scrollTopBody() {
}, 300) }, 300)
} }
function getContentHeight() {
return 'auto'
}
function openClick() {
footerOpen.value = !footerOpen.value
isOpen.value = footerOpen.value ? '收起' : '展开'
}
function updateTabHeight() { function updateTabHeight() {
nextTick(() => { nextTick(() => {
try { try {

@ -8,6 +8,7 @@ import Vant from 'vant'
import 'vant/lib/index.css' import 'vant/lib/index.css'
import Vue3Lazyload from 'vue3-lazyload' import Vue3Lazyload from 'vue3-lazyload'
import { Post } from './utils/https' import { Post } from './utils/https'
import imageService from './utils/imageService'
import xss from 'xss' import xss from 'xss'
import '@/assets/font/iconfont.css' import '@/assets/font/iconfont.css'
@ -66,5 +67,6 @@ app.use(Vant)
// Vue2 全局挂载的 this.$post / this.$xss,迁移后在 globalProperties 上注册 // Vue2 全局挂载的 this.$post / this.$xss,迁移后在 globalProperties 上注册
app.config.globalProperties.$post = Post app.config.globalProperties.$post = Post
app.config.globalProperties.$xss = (val) => xss(val) app.config.globalProperties.$xss = (val) => xss(val)
app.config.globalProperties.$getImageUrl = (key) => imageService.getImageByKey(key)
app.mount('#app') app.mount('#app')

@ -382,16 +382,20 @@ export const useAppStore = defineStore('app', () => {
if (!payload || typeof payload !== 'object') return '' if (!payload || typeof payload !== 'object') return ''
const options = payload.message?.[0]?.options || {} const options = payload.message?.[0]?.options || {}
const reqAndResType = String(payload.reqAndResType || '') const reqAndResType = String(payload.reqAndResType || '').trim()
const msgType = String(payload.msgType || '') const msgType = String(payload.msgType || '').trim()
const fromFlag = String(payload.fromFlag || '') const fromFlag = String(payload.fromFlag || '').trim()
const isProtocolImage = const isProtocolImage =
(reqAndResType === '2' && fromFlag === '1' && msgType !== '01') || (reqAndResType === '2' && fromFlag === '1' && msgType !== '01') ||
(reqAndResType === '08' && msgType === '02') (reqAndResType === '08' && msgType === '02')
const isTextAnswer = Boolean(payload.msgText) && (msgType === '01' || fromFlag !== '1') && !isProtocolImage // msgType=01 text answers must not be treated as images even with leftover imageUrl.
const hasTextBody = Boolean(
payload.msgText || payload.answer || payload.answerText || payload.content || payload.text,
)
const isTextAnswer = hasTextBody && (msgType === '01' || msgType === '1' || fromFlag !== '1') && !isProtocolImage
const hasExplicitImageField = !isTextAnswer && Boolean( const hasExplicitImageField = !isTextAnswer && Boolean(
payload.imgUrl || payload.imgKey || payload.fileKey || payload.imgUrl || payload.imgKey || payload.fileKey ||
(payload.imageUrl && !payload.msgText), (payload.imageUrl && !hasTextBody),
) )
const isImageMessage = const isImageMessage =
!isTextAnswer && ( !isTextAnswer && (
@ -401,7 +405,7 @@ export const useAppStore = defineStore('app', () => {
) )
if (!isImageMessage) { if (!isImageMessage) {
return payload.imageUrl || options.imageUrl || '' return ''
} }
// liics may return an image URL/key in any of these fields. For a // liics may return an image URL/key in any of these fields. For a
@ -421,13 +425,16 @@ export const useAppStore = defineStore('app', () => {
function isConversationImage(payload, imageUrl) { function isConversationImage(payload, imageUrl) {
if (!payload || typeof payload !== 'object') return false if (!payload || typeof payload !== 'object') return false
const reqAndResType = String(payload.reqAndResType || '') const reqAndResType = String(payload.reqAndResType || '').trim()
const msgType = String(payload.msgType || '') const msgType = String(payload.msgType || '').trim()
const fromFlag = String(payload.fromFlag || '') const fromFlag = String(payload.fromFlag || '').trim()
const isProtocolImage = const isProtocolImage =
(reqAndResType === '2' && fromFlag === '1' && msgType !== '01') || (reqAndResType === '2' && fromFlag === '1' && msgType !== '01') ||
(reqAndResType === '08' && msgType === '02') (reqAndResType === '08' && msgType === '02')
const isTextAnswer = Boolean(payload.msgText) && (msgType === '01' || fromFlag !== '1') && !isProtocolImage const hasTextBody = Boolean(
payload.msgText || payload.answer || payload.answerText || payload.content || payload.text,
)
const isTextAnswer = hasTextBody && (msgType === '01' || msgType === '1' || fromFlag !== '1') && !isProtocolImage
return Boolean( return Boolean(
!isTextAnswer && imageUrl && ( !isTextAnswer && imageUrl && (
payload.message?.[0]?.type === 'image' || payload.message?.[0]?.type === 'image' ||
@ -435,26 +442,22 @@ export const useAppStore = defineStore('app', () => {
payload.imgUrl || payload.imgUrl ||
payload.imgKey || payload.imgKey ||
payload.fileKey || payload.fileKey ||
(payload.imageUrl && !payload.msgText) (payload.imageUrl && !hasTextBody)
), ),
) )
} }
// liics 的 qaChat 使用 msgText/fromFlag,chat-new 使用 message/type。 // liics qaChat uses msgText/fromFlag; chat-new uses message/type.
// 在 store 边界统一转换,旧聊天组件继续读取 chatList,新页面读取 chatArr。 // Normalize at the store boundary so chat-content can keep reading chatArr.
function pushConversationMessage(payload, fallbackType = 'bot') { function formatConversationMessage(item, fallbackType = 'bot') {
const payloadList = Array.isArray(payload) ? payload : [payload] if (item == null) return null
let lastMessage = null
payloadList.forEach((item) => {
if (item == null) return
const content = getConversationMessageText(item) const content = getConversationMessageText(item)
const imageUrl = getConversationImageSource(item) const imageUrl = getConversationImageSource(item)
const isImage = isConversationImage(item, imageUrl) const isImage = isConversationImage(item, imageUrl)
if (!content && !imageUrl) return if (!content && !imageUrl) return null
const fromFlag = typeof item === 'object' ? String(item.fromFlag || '') : '' const fromFlag = typeof item === 'object' ? String(item.fromFlag ?? '').trim() : ''
const messageType = fromFlag === '1' ? 'user' : fromFlag === '2' ? 'bot' : fallbackType const messageType = fromFlag === '1' ? 'user' : fromFlag === '2' ? 'bot' : fallbackType
const resolvedFromFlag = const resolvedFromFlag =
fromFlag || (messageType === 'user' ? '1' : messageType === 'bot' ? '2' : '') fromFlag || (messageType === 'user' ? '1' : messageType === 'bot' ? '2' : '')
@ -464,37 +467,45 @@ export const useAppStore = defineStore('app', () => {
typeof item === 'object' && item.reqAndResType !== undefined && item.reqAndResType !== null && item.reqAndResType !== '' typeof item === 'object' && item.reqAndResType !== undefined && item.reqAndResType !== null && item.reqAndResType !== ''
const hasMsgType = const hasMsgType =
typeof item === 'object' && item.msgType !== undefined && item.msgType !== null && item.msgType !== '' typeof item === 'object' && item.msgType !== undefined && item.msgType !== null && item.msgType !== ''
// chat-content 机器人文本分支依赖 reqAndResType=='01' && msgType=='01' // chat-content bot text branch depends on reqAndResType=='01' && msgType=='01'
const resolvedReqAndResType = hasReqType let resolvedReqAndResType = hasReqType
? item.reqAndResType ? String(item.reqAndResType).trim()
: imageUrl : isImage
? (messageType === 'user' ? '2' : '2') ? '2'
: messageType === 'bot' : messageType === 'bot'
? '01' ? '01'
: '1' : '1'
const resolvedMsgType = hasMsgType let resolvedMsgType = hasMsgType
? item.msgType ? String(item.msgType).trim()
: imageUrl : isImage
? '02' ? '02'
: '01' : '01'
const message = {
// Bot plain text: normalize backend '1'/1 into chat-content '01'
if (messageType === 'bot' && !isImage) {
if (resolvedReqAndResType === '1') resolvedReqAndResType = '01'
if (resolvedMsgType === '1') resolvedMsgType = '01'
if (!hasMsgType) resolvedMsgType = '01'
if (!hasReqType) resolvedReqAndResType = '01'
}
return {
...(typeof item === 'object' ? item : {}), ...(typeof item === 'object' ? item : {}),
type: messageType, type: messageType,
system: (typeof item === 'object' && item.system) || 'LIICS', system: (typeof item === 'object' && item.system) || 'LIICS',
isSendSuccess: typeof item === 'object' ? item.isSendSuccess !== false : true, isSendSuccess: typeof item === 'object' ? item.isSendSuccess !== false : true,
imageUrl,
system: item?.system || 'LIICS',
isSendSuccess: item?.isSendSuccess !== false,
imageUrl: isImage ? imageUrl : '', imageUrl: isImage ? imageUrl : '',
imageKey: isImage ? imageUrl : '', imageKey: isImage ? imageUrl : '',
raw: item, raw: item,
// 兼容 chat-content:保留/补齐 liics 字段 // keep/fill liics fields for chat-content
msgText: content || (typeof item === 'object' ? item.msgText : '') || '', msgText: content || (typeof item === 'object' ? item.msgText : '') || '',
fromFlag: resolvedFromFlag, fromFlag: resolvedFromFlag,
reqAndResType: resolvedReqAndResType, reqAndResType: resolvedReqAndResType,
msgType: resolvedMsgType, msgType: resolvedMsgType,
timestamp: resolvedTimestamp, timestamp: resolvedTimestamp,
imgUrl: (typeof item === 'object' && (item.imgUrl || item.imageUrl)) || imageUrl || '', imgUrl: isImage
? ((typeof item === 'object' && (item.imgUrl || item.imageUrl)) || imageUrl || '')
: '',
VIEW_ID: VIEW_ID:
(typeof item === 'object' && item.VIEW_ID) || ('msg' + resolvedTimestamp), (typeof item === 'object' && item.VIEW_ID) || ('msg' + resolvedTimestamp),
message: [ message: [
@ -512,9 +523,26 @@ export const useAppStore = defineStore('app', () => {
}, },
], ],
} }
}
function normalizeHistoryMessages(list = []) {
return (Array.isArray(list) ? list : []).map((item) => {
const fallbackType =
String(item?.fromFlag ?? '') === '1' || item?.fromFlag === 1 ? 'user' : 'bot'
return formatConversationMessage(item, fallbackType) || item
})
}
function pushConversationMessage(payload, fallbackType = 'bot') {
const payloadList = Array.isArray(payload) ? payload : [payload]
let lastMessage = null
payloadList.forEach((item) => {
const message = formatConversationMessage(item, fallbackType)
if (!message) return
chatArr.value.push(message) chatArr.value.push(message)
isChatLoading.value = messageType === 'user' isChatLoading.value = message.type === 'user'
lastMessage = message lastMessage = message
}) })
@ -916,7 +944,25 @@ export const useAppStore = defineStore('app', () => {
} }
} }
function isMessageLike(value) {
if (!value || typeof value !== 'object' || Array.isArray(value)) return false
return (
value.msgText != null ||
value.answer != null ||
value.answerText != null ||
value.msgType != null ||
value.reqAndResType != null ||
value.fromFlag != null ||
value.cmd != null
)
}
function getResponsePayload(response) { function getResponsePayload(response) {
if (response == null) return response
if (Array.isArray(response)) return response
if (Array.isArray(response.data)) return response.data
if (isMessageLike(response.data)) return response.data
if (isMessageLike(response)) return response
return response?.data ?? response return response?.data ?? response
} }
@ -1406,6 +1452,9 @@ export const useAppStore = defineStore('app', () => {
setSysConfig, setSysConfig,
setHeaderAvatar, setHeaderAvatar,
pushUserMessage, pushUserMessage,
formatConversationMessage,
normalizeHistoryMessages,
pushConversationMessage,
pushImageMessage, pushImageMessage,
updateImageMessage, updateImageMessage,
setSendLoading, setSendLoading,

@ -51,5 +51,6 @@ export function messageSend(text, options = {}) {
msgText: text, msgText: text,
msgSource: appStore.manualService, msgSource: appStore.manualService,
quoteData: options.quoteData || null, quoteData: options.quoteData || null,
questionType: options.questionType || '',
}) })
} }

Loading…
Cancel
Save