聊天热点卡片内容渲染

master
wujingling0908 2 weeks ago
parent 52d322555e
commit f888312037
  1. 8
      src/components/chat-content.vue
  2. 7
      src/components/chat-new.vue
  3. 21
      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"
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">
<DynamicImage class="avatarUrl-img" :image-key="headerAvatar.robotUrl" />
<div class="chat-name" v-if="item.seatNum && item.seatNum != '调度员'">
@ -702,7 +702,7 @@
<!-- 多轮 文本--->
<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">
<DynamicImage class="avatarUrl-img" :image-key="headerAvatar.robotUrl" />
<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 policyFormTable from "@/components/policy-form-table.vue";
import DynamicImage from '@/components/common/DynamicImage.vue';
import imageService from '@/utils/imageService';
import wx from 'weixin-js-sdk';
export default {
props: ["historylist", "tokenExist", "lostChatInfoNum"],
@ -1876,7 +1877,8 @@ export default {
},
async ImagePreviewBox(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 currentIndex = this.timestampList.indexOf(timestamp);

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

@ -46,12 +46,31 @@ const initHotQuestion = async () => {
console.log(error);
});
}
const manualService = inject('manualService', ref('1'))
const isItemsubmit = ref(true)
const handleAskClick = (record) => {
const text = record?.name
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)
})
.finally(() => {
isItemsubmit.value = true
})
}
onMounted(async () => {
await initHotQuestion()

@ -152,14 +152,17 @@
</template>
<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 { Post } from '@/utils/https'
import utils from '@/utils/utils'
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({
isTab: { type: Boolean, default: true },
isQA: { type: Boolean, default: false },
})
const store = inject('appStore', null)
@ -183,9 +186,13 @@ const timeEnd = ref(1)
const clickNum = ref(0)
const group = ref(0)
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 isItemsubmit = ref(true)
const isQA = ref(false)
const planCode = ref('')
onMounted(() => {
@ -238,7 +245,7 @@ function initHotQuestionYTH(callback) {
function initViewState() {
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
}
}
@ -252,10 +259,10 @@ function activeChange(e) {
if (active.value === 0) {
len = hotList.value.length
} 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
}
if ((active.value === 0 && !isQA.value) || len <= 4) {
if ((active.value === 0 && !props.isQA) || len <= 4) {
footerMore.value = false
}
}
@ -356,6 +363,15 @@ function scrollTopBody() {
}, 300)
}
function getContentHeight() {
return 'auto'
}
function openClick() {
footerOpen.value = !footerOpen.value
isOpen.value = footerOpen.value ? '收起' : '展开'
}
function updateTabHeight() {
nextTick(() => {
try {

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

@ -382,16 +382,20 @@ export const useAppStore = defineStore('app', () => {
if (!payload || typeof payload !== 'object') return ''
const options = payload.message?.[0]?.options || {}
const reqAndResType = String(payload.reqAndResType || '')
const msgType = String(payload.msgType || '')
const fromFlag = String(payload.fromFlag || '')
const reqAndResType = String(payload.reqAndResType || '').trim()
const msgType = String(payload.msgType || '').trim()
const fromFlag = String(payload.fromFlag || '').trim()
const isProtocolImage =
(reqAndResType === '2' && fromFlag === '1' && msgType !== '01') ||
(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(
payload.imgUrl || payload.imgKey || payload.fileKey ||
(payload.imageUrl && !payload.msgText),
(payload.imageUrl && !hasTextBody),
)
const isImageMessage =
!isTextAnswer && (
@ -401,7 +405,7 @@ export const useAppStore = defineStore('app', () => {
)
if (!isImageMessage) {
return payload.imageUrl || options.imageUrl || ''
return ''
}
// 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) {
if (!payload || typeof payload !== 'object') return false
const reqAndResType = String(payload.reqAndResType || '')
const msgType = String(payload.msgType || '')
const fromFlag = String(payload.fromFlag || '')
const reqAndResType = String(payload.reqAndResType || '').trim()
const msgType = String(payload.msgType || '').trim()
const fromFlag = String(payload.fromFlag || '').trim()
const isProtocolImage =
(reqAndResType === '2' && fromFlag === '1' && msgType !== '01') ||
(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(
!isTextAnswer && imageUrl && (
payload.message?.[0]?.type === 'image' ||
@ -435,26 +442,22 @@ export const useAppStore = defineStore('app', () => {
payload.imgUrl ||
payload.imgKey ||
payload.fileKey ||
(payload.imageUrl && !payload.msgText)
(payload.imageUrl && !hasTextBody)
),
)
}
// liics 的 qaChat 使用 msgText/fromFlag,chat-new 使用 message/type。
// 在 store 边界统一转换,旧聊天组件继续读取 chatList,新页面读取 chatArr。
function pushConversationMessage(payload, fallbackType = 'bot') {
const payloadList = Array.isArray(payload) ? payload : [payload]
let lastMessage = null
payloadList.forEach((item) => {
if (item == null) return
// liics qaChat uses msgText/fromFlag; chat-new uses message/type.
// Normalize at the store boundary so chat-content can keep reading chatArr.
function formatConversationMessage(item, fallbackType = 'bot') {
if (item == null) return null
const content = getConversationMessageText(item)
const imageUrl = getConversationImageSource(item)
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 resolvedFromFlag =
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 !== ''
const hasMsgType =
typeof item === 'object' && item.msgType !== undefined && item.msgType !== null && item.msgType !== ''
// chat-content 机器人文本分支依赖 reqAndResType=='01' && msgType=='01'
const resolvedReqAndResType = hasReqType
? item.reqAndResType
: imageUrl
? (messageType === 'user' ? '2' : '2')
// chat-content bot text branch depends on reqAndResType=='01' && msgType=='01'
let resolvedReqAndResType = hasReqType
? String(item.reqAndResType).trim()
: isImage
? '2'
: messageType === 'bot'
? '01'
: '1'
const resolvedMsgType = hasMsgType
? item.msgType
: imageUrl
let resolvedMsgType = hasMsgType
? String(item.msgType).trim()
: isImage
? '02'
: '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 : {}),
type: messageType,
system: (typeof item === 'object' && item.system) || 'LIICS',
isSendSuccess: typeof item === 'object' ? item.isSendSuccess !== false : true,
imageUrl,
system: item?.system || 'LIICS',
isSendSuccess: item?.isSendSuccess !== false,
imageUrl: isImage ? imageUrl : '',
imageKey: isImage ? imageUrl : '',
raw: item,
// 兼容 chat-content:保留/补齐 liics 字段
// keep/fill liics fields for chat-content
msgText: content || (typeof item === 'object' ? item.msgText : '') || '',
fromFlag: resolvedFromFlag,
reqAndResType: resolvedReqAndResType,
msgType: resolvedMsgType,
timestamp: resolvedTimestamp,
imgUrl: (typeof item === 'object' && (item.imgUrl || item.imageUrl)) || imageUrl || '',
imgUrl: isImage
? ((typeof item === 'object' && (item.imgUrl || item.imageUrl)) || imageUrl || '')
: '',
VIEW_ID:
(typeof item === 'object' && item.VIEW_ID) || ('msg' + resolvedTimestamp),
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)
isChatLoading.value = messageType === 'user'
isChatLoading.value = message.type === 'user'
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) {
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
}
@ -1406,6 +1452,9 @@ export const useAppStore = defineStore('app', () => {
setSysConfig,
setHeaderAvatar,
pushUserMessage,
formatConversationMessage,
normalizeHistoryMessages,
pushConversationMessage,
pushImageMessage,
updateImageMessage,
setSendLoading,

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

Loading…
Cancel
Save