Files
YGChatCS/src/pages/ChatMain/ChatLongAnswer/ParsedValueView.vue
2026-06-04 01:16:05 +08:00

254 lines
7.3 KiB
Vue

<template>
<view v-if="shouldRenderField" class="parsed-value">
<template v-for="entry in renderFieldEntries" :key="entry.key">
<template v-if="entry.type === 'text'">
<image v-if="entry.key === LONG_TEXT_KEYS.contentImage"
class="content-body-image"
:src="entry.value"
mode="widthFix"
@click="handlePreviewClick(entry.value)"
/>
<view v-else class="content-body-text">
{{ entry.value }}
</view>
</template>
<view v-else-if="entry.type === 'image'" class="content-body-image-card">
<image
class="content-body-image"
:src="entry.value.image_id"
mode="widthFix"
@click="handlePreviewClick(entry.value.image_id)"
/>
<view v-if="entry.value.caption" class="content-body-image-caption">
{{ entry.value.caption }}
</view>
</view>
<template v-else-if="entry.type === 'list'">
<template v-if="entry.key === LONG_TEXT_KEYS.questionSuggest">
<view class="detail-action-label">继续追问</view>
<view class="detail-faq-wrap">
<view
v-for="question in entry.value"
:key="question"
class="detail-faq-chip"
@click="sendReply(question)"
>
{{ question }}
</view>
</view>
</template>
<view v-else
class="content-body-list-card"
:style="contentBodyListCardStyle"
>
<view v-for="(item, index) in entry.value"
:key="index"
class="content-body-list-item"
>
<view class="content-body-list-text" :style="contentBodyListTextStyle">
{{ formatLeafValue(item) }}
</view>
</view>
</view>
</template>
<template v-else-if="entry.type === 'object'">
<template v-if="entry.key === LONG_TEXT_KEYS.spotLocate">
<view class="detail-action-zone">
<view class="detail-action-label">查看景点详情</view>
<view class="detail-action-card is-raised">
<view v-if="poiTag" class="poi-mini-tag">{{ entry.value.sopt_tag }}</view>
<view class="poi-mini-body">
<view class="poi-mini-title">{{ entry.value.sopt_name }}</view>
<view class="poi-mini-desc">{{ entry.value.spot_description }}</view>
<button class="detail-solid-button" @click.stop="openMap(entry.value)">带我去</button>
</view>
</view>
</view>
</template>
</template>
</template>
</view>
</template>
<script setup>
import { computed, defineProps, ref } from "vue";
import { SEND_MESSAGE_CONTENT_TEXT } from "@/constant/constant";
import { getRandomTagToneStyle } from "@/utils/tagTone";
import { LONG_TEXT_KEYS } from "@/utils/longTextCard";
const props = defineProps({
fieldKey: {
type: String,
default: "",
},
value: {
type: [Object, Array, String, Number, Boolean],
default: null,
},
});
const contentBodyListToneStyle = ref(getRandomTagToneStyle({ borderAlpha: 1, borderWidth: 4 }));
const contentBodyListCardStyle = computed(() => ({
borderLeft: contentBodyListToneStyle.value.border,
background: contentBodyListToneStyle.value.background,
}));
const contentBodyListTextStyle = computed(() => ({
color: contentBodyListToneStyle.value.color,
}));
const IGNORED_FIELD_KEYS = ["container_type", "content", "content_summary", "components"];
const isArrayValue = (value) => Array.isArray(value);
const isObjectValue = (value) => {
return value !== null && typeof value === "object" && !Array.isArray(value);
};
const parseJsonStringValue = (value) => {
if (typeof value !== "string") return value;
const text = value.trim();
if (!/^[\[{]/.test(text)) return value;
try {
return JSON.parse(text);
} catch (e) {
return value;
}
};
const sanitizeValue = (value) => {
const parsedValue = parseJsonStringValue(value);
if (isArrayValue(parsedValue)) {
return parsedValue.map((item) => sanitizeValue(item));
}
if (isObjectValue(parsedValue)) {
return Object.keys(parsedValue).reduce((result, key) => {
if (IGNORED_FIELD_KEYS.includes(key)) return result;
result[key] = sanitizeValue(parsedValue[key]);
return result;
}, {});
}
return parsedValue;
};
const hasDisplayValue = (value) => {
if (value === undefined || value === null) return false;
if (typeof value === "string") return !!value.trim();
if (isArrayValue(value)) return value.some((item) => hasDisplayValue(item));
if (isObjectValue(value)) {
const valueObj = sanitizeValue(value);
return Object.keys(valueObj).some((key) => hasDisplayValue(valueObj[key]));
}
return true;
};
const formatLeafValue = (value) => {
if (value === undefined || value === null) return "";
if (typeof value === "boolean") return value ? "是" : "否";
if (typeof value === "object") {
try {
return JSON.stringify(sanitizeValue(value));
} catch (e) {
return String(value);
}
}
return String(value);
};
const isImageValue = (value) => {
return isObjectValue(value) && hasDisplayValue(value.image_id);
};
const createImageEntry = (key, value) => ({
key,
type: "image",
value: {
image_id: formatLeafValue(value.image_id).trim(),
caption: hasDisplayValue(value.caption) ? formatLeafValue(value.caption) : "",
},
});
const renderFieldEntries = computed(() => {
if (isIgnoredField.value || !hasDisplayValue(props.value)) return [];
const value = sanitizeValue(props.value);
if (isImageValue(value)) {
return [createImageEntry(props.fieldKey, value)];
}
if (isArrayValue(value)) {
return [{
key: props.fieldKey,
type: "list",
value: value.filter((item) => hasDisplayValue(item)),
}];
}
if (isObjectValue(value)) {
return Object.keys(value)
.filter((key) => hasDisplayValue(value[key]))
.map((key) => {
const entryValue = value[key];
if (isImageValue(entryValue)) {
return createImageEntry(key, entryValue);
}
if (isArrayValue(entryValue)) {
return {
key,
type: "list",
value: entryValue.filter((item) => hasDisplayValue(item)),
};
}
return {
key,
type: "text",
value: formatLeafValue(entryValue),
};
});
}
return [{
key: props.fieldKey,
type: "text",
value: formatLeafValue(value),
}];
});
const isIgnoredField = computed(() => IGNORED_FIELD_KEYS.includes(props.fieldKey));
const shouldRenderField = computed(() => renderFieldEntries.value.length > 0);
const openMap = (spot) => {
const latitude = spot.latitude;
const longitude = spot.longitude;
const address = spot.name || '';
uni.getLocation({ type: 'gcj02', success: () => {
uni.openLocation({ latitude, longitude, address });
}, fail: () => {
uni.openLocation({ latitude, longitude, address });
}});
};
const sendReply = (item) => {
uni.navigateBack();
uni.$emit(SEND_MESSAGE_CONTENT_TEXT, item);
};
const handlePreviewClick = (imageUrl) => {
uni.previewImage({
current: imageUrl,
urls: [imageUrl],
});
};
</script>
<style scoped lang="scss">
@import "./styles/ParsedValueView.scss";
</style>