feat: 改造了组件的渲染逻辑
This commit is contained in:
43
scripts/test-ParsedValueView-strict.mjs
Normal file
43
scripts/test-ParsedValueView-strict.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const source = await readFile(
|
||||
resolve("src/pages/ChatMain/ChatLongAnswer/ParsedValueView.vue"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const forbiddenCompatibilitySnippets = [
|
||||
"valueObj.image_url",
|
||||
"valueObj.image,",
|
||||
"valueObj.image_id",
|
||||
"valueObj.content_image",
|
||||
"valueObj.sopt_name",
|
||||
"valueObj.name",
|
||||
"valueObj.title",
|
||||
"valueObj.sopt_description",
|
||||
"valueObj.description",
|
||||
"valueObj.desc",
|
||||
"valueObj.sopt_longitude",
|
||||
"valueObj.longitude",
|
||||
"valueObj.lng",
|
||||
"valueObj.sopt_latitude",
|
||||
"valueObj.latitude",
|
||||
"valueObj.lat",
|
||||
"valueObj.sopt_tag",
|
||||
"valueObj.tag",
|
||||
"valueObj.type",
|
||||
"value?.questions",
|
||||
"value?.items",
|
||||
"value?.list",
|
||||
];
|
||||
|
||||
for (const snippet of forbiddenCompatibilitySnippets) {
|
||||
assert.equal(
|
||||
source.includes(snippet),
|
||||
false,
|
||||
`ParsedValueView should not guess compatibility field: ${snippet}`
|
||||
);
|
||||
}
|
||||
|
||||
console.log("ParsedValueView strict field checks passed");
|
||||
54
scripts/test-longTextCard.mjs
Normal file
54
scripts/test-longTextCard.mjs
Normal file
@@ -0,0 +1,54 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const source = await readFile(resolve("src/utils/longTextCard.js"), "utf8");
|
||||
const moduleUrl = `data:text/javascript;base64,${Buffer.from(source).toString("base64")}`;
|
||||
const longTextCard = await import(moduleUrl);
|
||||
|
||||
const {
|
||||
parseLongTextDisplayValue,
|
||||
sanitizeLongTextDisplayValue,
|
||||
hasLongTextDisplayValue,
|
||||
formatLongTextDisplayValue,
|
||||
} = longTextCard;
|
||||
|
||||
assert.deepEqual(
|
||||
parseLongTextDisplayValue('[ "see bridge", "hear water" ]'),
|
||||
["see bridge", "hear water"],
|
||||
"serialized arrays should be parsed for display"
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
parseLongTextDisplayValue('{"spot_name":"bridge","spot_longitude:":107.712345}'),
|
||||
{ spot_name: "bridge", "spot_longitude:": 107.712345 },
|
||||
"serialized objects should be parsed for display"
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
sanitizeLongTextDisplayValue(
|
||||
{
|
||||
content: "hidden",
|
||||
components: ["hidden"],
|
||||
view_section_items: '[ "check piers", "count arches" ]',
|
||||
nested: {
|
||||
title: "keep",
|
||||
},
|
||||
},
|
||||
["content", "components"]
|
||||
),
|
||||
{
|
||||
view_section_items: ["check piers", "count arches"],
|
||||
nested: {
|
||||
title: "keep",
|
||||
},
|
||||
},
|
||||
"sanitizing should parse JSON-like strings and ignore configured keys"
|
||||
);
|
||||
|
||||
assert.equal(hasLongTextDisplayValue('["follow up"]'), true);
|
||||
assert.equal(hasLongTextDisplayValue({ a: "", b: [" ", null] }), false);
|
||||
assert.equal(formatLongTextDisplayValue(true), "\u662f");
|
||||
assert.equal(formatLongTextDisplayValue({ title: "bridge" }), '{"title":"bridge"}');
|
||||
|
||||
console.log("longTextCard display helpers passed");
|
||||
@@ -1,52 +1,53 @@
|
||||
<template>
|
||||
<view v-if="shouldRenderField" class="parsed-value">
|
||||
<template v-for="entry in renderFieldEntries" :key="entry.key">
|
||||
<view v-if="entry.type === 'text'" class="content-body-text">
|
||||
{{ entry.value }}
|
||||
</view>
|
||||
|
||||
<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>
|
||||
<image v-if="shouldRenderContentImage" class="content-body-image" :src="contentImageUrl"
|
||||
mode="widthFix" @click="handlePreviewClick(contentImageUrl)" />
|
||||
|
||||
<view v-else-if="entry.type === 'list'" 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) }}
|
||||
<template v-else-if="shouldRenderSpotLocate">
|
||||
<view class="detail-action-zone">
|
||||
<view class="detail-action-label">查看景点详情</view>
|
||||
<view class="detail-action-card is-raised">
|
||||
<view v-if="spotLocateValue.tag" class="poi-mini-tag">{{ spotLocateValue.tag }}</view>
|
||||
<view class="poi-mini-body">
|
||||
<view v-if="spotLocateValue.name" class="poi-mini-title">{{ spotLocateValue.name }}</view>
|
||||
<view v-if="spotLocateValue.description" class="poi-mini-desc">{{ spotLocateValue.description }}</view>
|
||||
<button v-if="hasMapLocation(spotLocateValue)" class="detail-solid-button" @click.stop="openMap(spotLocateValue)">带我去</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<image v-else-if="entry.type === 'content-image'" class="content-body-image" :src="entry.value.url"
|
||||
mode="widthFix" @click="handlePreviewClick(entry.value.url)" />
|
||||
<template v-else-if="shouldRenderQuestionSuggest">
|
||||
<view class="detail-action-label">继续追问</view>
|
||||
<view class="detail-faq-wrap">
|
||||
<view v-for="question in questionSuggestItems" :key="question" class="detail-faq-chip" @click="sendReply(question)">
|
||||
{{ question }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template v-else-if="entry.type === 'spot-locate'">
|
||||
<view class="detail-action-zone">
|
||||
<view class="detail-action-label">查看景点详情</view>
|
||||
<view class="detail-action-card is-raised">
|
||||
<view v-if="entry.value.tag" class="poi-mini-tag">{{ entry.value.tag }}</view>
|
||||
<view class="poi-mini-body">
|
||||
<view v-if="entry.value.name" class="poi-mini-title">{{ entry.value.name }}</view>
|
||||
<view v-if="entry.value.description" class="poi-mini-desc">{{ entry.value.description }}</view>
|
||||
<button v-if="hasMapLocation(entry.value)" class="detail-solid-button" @click.stop="openMap(entry.value)">带我去</button>
|
||||
<template v-else>
|
||||
<template v-for="entry in renderFieldEntries" :key="entry.key">
|
||||
<view v-if="entry.type === 'text'" class="content-body-text">
|
||||
{{ entry.value }}
|
||||
</view>
|
||||
|
||||
<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>
|
||||
|
||||
<view v-else-if="entry.type === 'list'" 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 === 'question-suggest'">
|
||||
<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>
|
||||
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
@@ -57,9 +58,9 @@ import { SEND_MESSAGE_CONTENT_TEXT } from "@/constant/constant";
|
||||
import { getRandomTagToneStyle } from "@/utils/tagTone";
|
||||
import {
|
||||
LONG_TEXT_KEYS,
|
||||
normalizeLongTextContentImage,
|
||||
normalizeLongTextQuestionSuggest,
|
||||
normalizeLongTextSpotLocate,
|
||||
formatLongTextDisplayValue,
|
||||
hasLongTextDisplayValue,
|
||||
sanitizeLongTextDisplayValue,
|
||||
} from "@/utils/longTextCard";
|
||||
|
||||
const props = defineProps({
|
||||
@@ -84,63 +85,34 @@ const contentBodyListTextStyle = computed(() => ({
|
||||
|
||||
const IGNORED_FIELD_KEYS = ["container_type", "content", "components"];
|
||||
|
||||
/// ======== Value Processing Helpers ========
|
||||
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;
|
||||
return sanitizeLongTextDisplayValue(value, IGNORED_FIELD_KEYS);
|
||||
};
|
||||
|
||||
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;
|
||||
return hasLongTextDisplayValue(value, IGNORED_FIELD_KEYS);
|
||||
};
|
||||
|
||||
const formatLeafValue = (value) => {
|
||||
return formatLongTextDisplayValue(value, IGNORED_FIELD_KEYS);
|
||||
};
|
||||
|
||||
const toTrimmedText = (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);
|
||||
return String(value).trim();
|
||||
};
|
||||
|
||||
const toFiniteNumber = (value) => {
|
||||
if (value === undefined || value === null || value === "") return null;
|
||||
const numberValue = Number(value);
|
||||
return Number.isFinite(numberValue) ? numberValue : null;
|
||||
};
|
||||
|
||||
const isImageValue = (value) => {
|
||||
@@ -156,50 +128,55 @@ const createImageEntry = (key, value) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const createSpecialFieldEntry = (key, value) => {
|
||||
if (key === LONG_TEXT_KEYS.contentImage) {
|
||||
const image = normalizeLongTextContentImage(value);
|
||||
return image.url
|
||||
? [{
|
||||
key,
|
||||
type: "content-image",
|
||||
value: image,
|
||||
}]
|
||||
: [];
|
||||
}
|
||||
|
||||
if (key === LONG_TEXT_KEYS.spotLocate) {
|
||||
const spot = normalizeLongTextSpotLocate(value);
|
||||
return hasDisplayValue(spot)
|
||||
? [{
|
||||
key,
|
||||
type: "spot-locate",
|
||||
value: spot,
|
||||
}]
|
||||
: [];
|
||||
}
|
||||
/// ======== Render Logic ========
|
||||
const isIgnoredField = computed(() => IGNORED_FIELD_KEYS.includes(props.fieldKey));
|
||||
const isContentImageField = computed(() => props.fieldKey === LONG_TEXT_KEYS.contentImage);
|
||||
const isSpotLocateField = computed(() => props.fieldKey === LONG_TEXT_KEYS.spotLocate);
|
||||
const isQuestionSuggestField = computed(() => props.fieldKey === LONG_TEXT_KEYS.questionSuggest);
|
||||
|
||||
if (key === LONG_TEXT_KEYS.questionSuggest) {
|
||||
const questions = normalizeLongTextQuestionSuggest(value);
|
||||
return questions.length
|
||||
? [{
|
||||
key,
|
||||
type: "question-suggest",
|
||||
value: questions,
|
||||
}]
|
||||
: [];
|
||||
}
|
||||
const displayValue = computed(() => sanitizeValue(props.value));
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/// 特殊字段的直接渲染数据
|
||||
const contentImageUrl = computed(() => {
|
||||
return typeof displayValue.value === "string" ? toTrimmedText(displayValue.value) : "";
|
||||
});
|
||||
|
||||
const spotLocateValue = computed(() => {
|
||||
const value = isObjectValue(displayValue.value) ? displayValue.value : {};
|
||||
|
||||
return {
|
||||
name: toTrimmedText(value.spot_name),
|
||||
description: toTrimmedText(value.spot_description),
|
||||
longitude: toFiniteNumber(value.spot_longitude),
|
||||
latitude: toFiniteNumber(value.spot_latitude),
|
||||
tag: toTrimmedText(value.spot_tag),
|
||||
};
|
||||
});
|
||||
|
||||
const questionSuggestItems = computed(() => {
|
||||
return isArrayValue(displayValue.value)
|
||||
? displayValue.value.map((item) => toTrimmedText(item)).filter(Boolean)
|
||||
: [];
|
||||
});
|
||||
|
||||
const shouldRenderContentImage = computed(() => {
|
||||
return isContentImageField.value && !!contentImageUrl.value;
|
||||
});
|
||||
|
||||
const shouldRenderSpotLocate = computed(() => {
|
||||
return isSpotLocateField.value && hasDisplayValue(spotLocateValue.value);
|
||||
});
|
||||
|
||||
const shouldRenderQuestionSuggest = computed(() => {
|
||||
return isQuestionSuggestField.value && questionSuggestItems.value.length > 0;
|
||||
});
|
||||
|
||||
/// 其他字段走通用渲染逻辑
|
||||
const renderFieldEntries = computed(() => {
|
||||
if (isIgnoredField.value || !hasDisplayValue(props.value)) return [];
|
||||
|
||||
const specialFieldEntry = createSpecialFieldEntry(props.fieldKey, props.value);
|
||||
if (specialFieldEntry) return specialFieldEntry;
|
||||
|
||||
const value = sanitizeValue(props.value);
|
||||
const value = displayValue.value;
|
||||
if (isImageValue(value)) {
|
||||
return [createImageEntry(props.fieldKey, value)];
|
||||
}
|
||||
@@ -242,13 +219,27 @@ const renderFieldEntries = computed(() => {
|
||||
}];
|
||||
});
|
||||
|
||||
const isIgnoredField = computed(() => IGNORED_FIELD_KEYS.includes(props.fieldKey));
|
||||
const shouldRenderField = computed(() => renderFieldEntries.value.length > 0);
|
||||
|
||||
/// 是否有任何内容可以渲染(特殊字段优先)
|
||||
const shouldRenderField = computed(() => {
|
||||
if (isIgnoredField.value) return false;
|
||||
if (
|
||||
shouldRenderContentImage.value ||
|
||||
shouldRenderSpotLocate.value ||
|
||||
shouldRenderQuestionSuggest.value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return renderFieldEntries.value.length > 0;
|
||||
});
|
||||
|
||||
const hasMapLocation = (spot) => {
|
||||
return Number.isFinite(spot?.latitude) && Number.isFinite(spot?.longitude);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/// ======== Action Handlers ========
|
||||
const openMap = (spot) => {
|
||||
if (!hasMapLocation(spot)) {
|
||||
uni.showToast({
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<view class="flex flex-col px-16 pt-16 pb-12 border-box">
|
||||
<view v-if="tag" class="long-answer-tag" :style="longAnswerTagStyle">{{ tag }}</view>
|
||||
<view v-if="title" class="flex flex-row flex-items-start flex-justify-start mb-4">
|
||||
<uni-icons class="icon-active" type="fire-filled" size="18" color="opacity" />
|
||||
<uni-icons class="icon-active color-FF3D60" type="fire-filled" size="18" color="opacity" />
|
||||
<text class="font-size-16 font-500 text-color-900 ml-6"> {{ title }}</text>
|
||||
</view>
|
||||
<!-- 文字内容,最多显示3行 -->
|
||||
@@ -174,7 +174,6 @@ const lookDetailAction = () => {
|
||||
<style scoped lang="scss">
|
||||
.icon-active {
|
||||
margin-top: 1px;
|
||||
color: $theme-color-500;
|
||||
}
|
||||
.answer-content {
|
||||
display: -webkit-box;
|
||||
|
||||
@@ -8,6 +8,12 @@ export const LONG_TEXT_KEYS = {
|
||||
keyFacts: "key_facts",
|
||||
sceneImage: "scene_image",
|
||||
contentImage: "content_image",
|
||||
viewSectionTitle: "view_section_title",
|
||||
viewSectionItems: "view_section_items",
|
||||
suggestionSectionTitle: "suggestion_section_title",
|
||||
suggestionSectionContent: "suggestion_section_content",
|
||||
lightReminderTitle: "light_reminder_title",
|
||||
lightReminderItems: "light_reminder_items",
|
||||
checklistOrSteps: "checklist_or_steps",
|
||||
bestTimeOrPeople: "best_time_or_people",
|
||||
avoidPitfalls: "avoid_pitfalls",
|
||||
@@ -50,6 +56,12 @@ export const LONG_TEXT_FIELD_CONFIG = [
|
||||
{ key: LONG_TEXT_KEYS.keyFacts },
|
||||
{ key: LONG_TEXT_KEYS.sceneImage },
|
||||
{ key: LONG_TEXT_KEYS.contentImage },
|
||||
{ key: LONG_TEXT_KEYS.viewSectionTitle },
|
||||
{ key: LONG_TEXT_KEYS.viewSectionItems },
|
||||
{ key: LONG_TEXT_KEYS.suggestionSectionTitle },
|
||||
{ key: LONG_TEXT_KEYS.suggestionSectionContent },
|
||||
{ key: LONG_TEXT_KEYS.lightReminderTitle },
|
||||
{ key: LONG_TEXT_KEYS.lightReminderItems },
|
||||
{ key: LONG_TEXT_KEYS.checklistOrSteps },
|
||||
{ key: LONG_TEXT_KEYS.bestTimeOrPeople },
|
||||
{ key: LONG_TEXT_KEYS.avoidPitfalls },
|
||||
@@ -121,112 +133,61 @@ const parseMaybeJSON = (value) => {
|
||||
return parsed.ok ? parsed.value : value;
|
||||
};
|
||||
|
||||
const toTrimmedText = (value) => {
|
||||
if (value === undefined || value === null) return "";
|
||||
return String(value).trim();
|
||||
const isLongTextArrayValue = (value) => Array.isArray(value);
|
||||
|
||||
const isLongTextObjectValue = (value) => {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
};
|
||||
|
||||
const toFiniteNumber = (value) => {
|
||||
if (value === undefined || value === null || value === "") return null;
|
||||
const numberValue = Number(value);
|
||||
return Number.isFinite(numberValue) ? numberValue : null;
|
||||
export const parseLongTextDisplayValue = (value) => {
|
||||
return parseMaybeJSON(value);
|
||||
};
|
||||
|
||||
const pickFirstValue = (...values) => {
|
||||
return values.find((value) => value !== undefined && value !== null && value !== "");
|
||||
};
|
||||
export const sanitizeLongTextDisplayValue = (value, ignoredKeys = []) => {
|
||||
const parsedValue = parseLongTextDisplayValue(value);
|
||||
|
||||
const normalizeObjectKeys = (value) => {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
||||
|
||||
return Object.keys(value).reduce((result, key) => {
|
||||
const normalizedKey = String(key).trim().replace(/[::]+$/, "");
|
||||
result[normalizedKey] = value[key];
|
||||
return result;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const normalizeLongTextContentImage = (value) => {
|
||||
const parsedValue = parseMaybeJSON(value);
|
||||
|
||||
if (typeof parsedValue === "string") {
|
||||
return {
|
||||
url: toTrimmedText(parsedValue),
|
||||
caption: "",
|
||||
};
|
||||
if (isLongTextArrayValue(parsedValue)) {
|
||||
return parsedValue.map((item) => sanitizeLongTextDisplayValue(item, ignoredKeys));
|
||||
}
|
||||
|
||||
const valueObj = normalizeObjectKeys(parsedValue);
|
||||
return {
|
||||
url: toTrimmedText(
|
||||
pickFirstValue(
|
||||
valueObj.url,
|
||||
valueObj.image_url,
|
||||
valueObj.image,
|
||||
valueObj.image_id,
|
||||
valueObj.content_image
|
||||
)
|
||||
),
|
||||
caption: toTrimmedText(pickFirstValue(valueObj.caption, valueObj.image_caption)),
|
||||
};
|
||||
if (isLongTextObjectValue(parsedValue)) {
|
||||
return Object.keys(parsedValue).reduce((result, key) => {
|
||||
if (ignoredKeys.includes(key)) return result;
|
||||
result[key] = sanitizeLongTextDisplayValue(parsedValue[key], ignoredKeys);
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return parsedValue;
|
||||
};
|
||||
|
||||
export const normalizeLongTextQuestionSuggest = (value) => {
|
||||
const parsedValue = parseMaybeJSON(value);
|
||||
const rawList = Array.isArray(parsedValue)
|
||||
? parsedValue
|
||||
: [parsedValue?.questions, parsedValue?.items, parsedValue?.list].find(Array.isArray) || [];
|
||||
export const hasLongTextDisplayValue = (value, ignoredKeys = []) => {
|
||||
const displayValue = sanitizeLongTextDisplayValue(value, ignoredKeys);
|
||||
|
||||
return rawList
|
||||
.map((item) => toTrimmedText(item))
|
||||
.filter(Boolean);
|
||||
if (displayValue === undefined || displayValue === null) return false;
|
||||
if (typeof displayValue === "string") return !!displayValue.trim();
|
||||
if (isLongTextArrayValue(displayValue)) {
|
||||
return displayValue.some((item) => hasLongTextDisplayValue(item, ignoredKeys));
|
||||
}
|
||||
if (isLongTextObjectValue(displayValue)) {
|
||||
return Object.keys(displayValue).some((key) =>
|
||||
hasLongTextDisplayValue(displayValue[key], ignoredKeys)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const normalizeLongTextSpotLocate = (value) => {
|
||||
const valueObj = normalizeObjectKeys(parseMaybeJSON(value));
|
||||
|
||||
return {
|
||||
name: toTrimmedText(
|
||||
pickFirstValue(
|
||||
valueObj.spot_name,
|
||||
valueObj.sopt_name,
|
||||
valueObj.name,
|
||||
valueObj.title
|
||||
)
|
||||
),
|
||||
description: toTrimmedText(
|
||||
pickFirstValue(
|
||||
valueObj.spot_description,
|
||||
valueObj.sopt_description,
|
||||
valueObj.description,
|
||||
valueObj.desc
|
||||
)
|
||||
),
|
||||
longitude: toFiniteNumber(
|
||||
pickFirstValue(
|
||||
valueObj.spot_longitude,
|
||||
valueObj.sopt_longitude,
|
||||
valueObj.longitude,
|
||||
valueObj.lng
|
||||
)
|
||||
),
|
||||
latitude: toFiniteNumber(
|
||||
pickFirstValue(
|
||||
valueObj.spot_latitude,
|
||||
valueObj.sopt_latitude,
|
||||
valueObj.latitude,
|
||||
valueObj.lat
|
||||
)
|
||||
),
|
||||
tag: toTrimmedText(
|
||||
pickFirstValue(
|
||||
valueObj.spot_tag,
|
||||
valueObj.sopt_tag,
|
||||
valueObj.tag,
|
||||
valueObj.type
|
||||
)
|
||||
),
|
||||
};
|
||||
export const formatLongTextDisplayValue = (value, ignoredKeys = []) => {
|
||||
if (value === undefined || value === null) return "";
|
||||
if (typeof value === "boolean") return value ? "是" : "否";
|
||||
if (typeof value === "object") {
|
||||
try {
|
||||
return JSON.stringify(sanitizeLongTextDisplayValue(value, ignoredKeys));
|
||||
} catch (e) {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
return String(value);
|
||||
};
|
||||
|
||||
export const appendLongTextChunk = (target, chunk = {}) => {
|
||||
|
||||
Reference in New Issue
Block a user