diff --git a/scripts/test-ParsedValueView-strict.mjs b/scripts/test-ParsedValueView-strict.mjs
index 32c6c14..4e5d1d2 100644
--- a/scripts/test-ParsedValueView-strict.mjs
+++ b/scripts/test-ParsedValueView-strict.mjs
@@ -40,4 +40,25 @@ for (const snippet of forbiddenCompatibilitySnippets) {
);
}
+const requiredStrictRenderingSnippets = [
+ "LONG_TEXT_KEYS.sceneImage",
+ "LONG_TEXT_KEYS.commodityList",
+ "commodity.commodity_id",
+ "commodity.commodity_name",
+ "commodity.commodity_price",
+ "commodity.commodity_tag",
+ "commodity.commodity_photo",
+ "content-body-list-marker",
+ "entry.value.length > 1",
+ "formatListMarker(index)",
+];
+
+for (const snippet of requiredStrictRenderingSnippets) {
+ assert.equal(
+ source.includes(snippet),
+ true,
+ `ParsedValueView should render exact strict field: ${snippet}`
+ );
+}
+
console.log("ParsedValueView strict field checks passed");
diff --git a/scripts/test-longTextCard.mjs b/scripts/test-longTextCard.mjs
index 9972902..c56d4dc 100644
--- a/scripts/test-longTextCard.mjs
+++ b/scripts/test-longTextCard.mjs
@@ -7,6 +7,8 @@ const moduleUrl = `data:text/javascript;base64,${Buffer.from(source).toString("b
const longTextCard = await import(moduleUrl);
const {
+ LONG_TEXT_FIELD_CONFIG,
+ LONG_TEXT_KEYS,
parseLongTextDisplayValue,
sanitizeLongTextDisplayValue,
hasLongTextDisplayValue,
@@ -51,4 +53,23 @@ assert.equal(hasLongTextDisplayValue({ a: "", b: [" ", null] }), false);
assert.equal(formatLongTextDisplayValue(true), "\u662f");
assert.equal(formatLongTextDisplayValue({ title: "bridge" }), '{"title":"bridge"}');
+const expectedNewKeys = {
+ preparationSectionTitle: "preparation_section_title",
+ preparationSectionItems: "preparation_section_items",
+ sectionSuggestionTitle: "section_suggestion_title",
+ sectionSuggestionContent: "section_suggestion_content",
+ pitfallSectionTitle: "pitfall_section_title",
+ pitfallSectionItems: "pitfall_section_items",
+ commodityList: "commodity_list",
+};
+
+for (const [keyName, keyValue] of Object.entries(expectedNewKeys)) {
+ assert.equal(LONG_TEXT_KEYS[keyName], keyValue, `${keyName} should be registered`);
+ assert.equal(
+ LONG_TEXT_FIELD_CONFIG.some((item) => item.key === keyValue),
+ true,
+ `${keyValue} should be in LONG_TEXT_FIELD_CONFIG`
+ );
+}
+
console.log("longTextCard display helpers passed");
diff --git a/src/pages/ChatMain/ChatLongAnswer/ParsedValueView.vue b/src/pages/ChatMain/ChatLongAnswer/ParsedValueView.vue
index aa0f706..529e667 100644
--- a/src/pages/ChatMain/ChatLongAnswer/ParsedValueView.vue
+++ b/src/pages/ChatMain/ChatLongAnswer/ParsedValueView.vue
@@ -1,6 +1,6 @@
-
@@ -26,6 +26,47 @@
+
+
+ 相关票务
+
+
+
+
+
+
+ {{ commodity.commodity_tag }}
+
+
+ {{ commodity.commodity_name }}
+
+
+ ¥{{ commodity.commodity_price }}
+
+
+
+
+
+
+
+
+
@@ -42,6 +83,9 @@
+
+ {{ formatListMarker(index) }}
+
{{ formatLeafValue(item) }}
@@ -104,6 +148,17 @@ const formatLeafValue = (value) => {
return formatLongTextDisplayValue(value, IGNORED_FIELD_KEYS);
};
+const LIST_MARKERS = [
+ "①", "②", "③", "④", "⑤",
+ "⑥", "⑦", "⑧", "⑨", "⑩",
+ "⑪", "⑫", "⑬", "⑭", "⑮",
+ "⑯", "⑰", "⑱", "⑲", "⑳",
+];
+
+const formatListMarker = (index) => {
+ return LIST_MARKERS[index] || `${index + 1}.`;
+};
+
const toTrimmedText = (value) => {
if (value === undefined || value === null) return "";
return String(value).trim();
@@ -131,9 +186,13 @@ const createImageEntry = (key, value) => ({
/// ======== Render Logic ========
const isIgnoredField = computed(() => IGNORED_FIELD_KEYS.includes(props.fieldKey));
-const isContentImageField = computed(() => props.fieldKey === LONG_TEXT_KEYS.contentImage);
+const isContentImageField = computed(() =>
+ props.fieldKey === LONG_TEXT_KEYS.contentImage ||
+ props.fieldKey === LONG_TEXT_KEYS.sceneImage
+);
const isSpotLocateField = computed(() => props.fieldKey === LONG_TEXT_KEYS.spotLocate);
const isQuestionSuggestField = computed(() => props.fieldKey === LONG_TEXT_KEYS.questionSuggest);
+const isCommodityListField = computed(() => props.fieldKey === LONG_TEXT_KEYS.commodityList);
const displayValue = computed(() => sanitizeValue(props.value));
@@ -161,6 +220,21 @@ const questionSuggestItems = computed(() => {
: [];
});
+const commodityItems = computed(() => {
+ if (!isArrayValue(displayValue.value)) return [];
+
+ return displayValue.value
+ .filter((item) => isObjectValue(item))
+ .map((commodity) => ({
+ commodity_id: toTrimmedText(commodity.commodity_id),
+ commodity_name: toTrimmedText(commodity.commodity_name),
+ commodity_price: toTrimmedText(commodity.commodity_price),
+ commodity_tag: toTrimmedText(commodity.commodity_tag),
+ commodity_photo: toTrimmedText(commodity.commodity_photo),
+ }))
+ .filter((commodity) => hasDisplayValue(commodity));
+});
+
const shouldRenderContentImage = computed(() => {
return isContentImageField.value && !!contentImageUrl.value;
});
@@ -173,6 +247,10 @@ const shouldRenderQuestionSuggest = computed(() => {
return isQuestionSuggestField.value && questionSuggestItems.value.length > 0;
});
+const shouldRenderCommodityList = computed(() => {
+ return isCommodityListField.value && commodityItems.value.length > 0;
+});
+
/// 其他字段走通用渲染逻辑
const renderFieldEntries = computed(() => {
if (isIgnoredField.value || !hasDisplayValue(props.value)) return [];
@@ -226,7 +304,8 @@ const shouldRenderField = computed(() => {
if (
shouldRenderContentImage.value ||
shouldRenderSpotLocate.value ||
- shouldRenderQuestionSuggest.value
+ shouldRenderQuestionSuggest.value ||
+ shouldRenderCommodityList.value
) {
return true;
}
@@ -265,6 +344,13 @@ const sendReply = (item) => {
uni.$emit(SEND_MESSAGE_CONTENT_TEXT, item);
};
+const openCommodityDetail = (commodity) => {
+ if (!commodity.commodity_id) return;
+ uni.navigateTo({
+ url: `/pages/goods/index?commodityId=${commodity.commodity_id}`,
+ });
+};
+
const handlePreviewClick = (imageUrl) => {
uni.previewImage({
current: imageUrl,
diff --git a/src/pages/ChatMain/ChatLongAnswer/styles/ParsedValueView.scss b/src/pages/ChatMain/ChatLongAnswer/styles/ParsedValueView.scss
index 0fa9e47..216b45c 100644
--- a/src/pages/ChatMain/ChatLongAnswer/styles/ParsedValueView.scss
+++ b/src/pages/ChatMain/ChatLongAnswer/styles/ParsedValueView.scss
@@ -14,6 +14,11 @@
display: flex;
flex-direction: column;
gap: 6px;
+ margin-bottom: 8px;
+}
+
+.content-body-image-bottom {
+ margin-bottom: 8px;
}
.content-body-image {
@@ -36,6 +41,7 @@
gap: 4px;
padding: 12px;
border-radius: 12px;
+ margin-bottom: 8px;
}
.content-body-list-item {
@@ -43,6 +49,14 @@
align-items: flex-start;
}
+.content-body-list-marker {
+ width: 22px;
+ flex-shrink: 0;
+ font-size: 15px;
+ font-weight: 800;
+ line-height: 20px;
+}
+
.content-body-list-text {
flex: 1;
font-size: 15px;
@@ -56,7 +70,7 @@
}
.detail-action-label {
- padding: 12px 0 12px;
+ padding: 0 0 10px;
color: #94a3b8;
font-size: 10px;
font-weight: 900;
@@ -131,6 +145,87 @@
border: 0;
}
+.detail-product-scroll {
+ width: 100%;
+ white-space: nowrap;
+}
+
+.detail-product-row {
+ display: flex;
+ gap: 10px;
+}
+
+.detail-product-card {
+ width: 220px;
+ flex-shrink: 0;
+ overflow: hidden;
+ border: 1px solid #f1f5f9;
+ border-radius: 20px;
+ background: #fff;
+ box-shadow: 0 2px 12px rgba(15, 23, 42, 0.04);
+}
+
+.detail-product-image {
+ width: 100%;
+ height: 110px;
+ background: #f1f5f9;
+}
+
+.detail-product-body {
+ padding: 10px 12px 12px;
+}
+
+.detail-product-tag {
+ display: inline-flex;
+ width: fit-content;
+ margin-bottom: 4px;
+ padding: 2px 6px;
+ border-radius: 6px;
+ color: #f43f5e;
+ background: #fef2f2;
+ font-size: 9px;
+ font-weight: 900;
+}
+
+.detail-product-name {
+ overflow: hidden;
+ color: #1e293b;
+ font-size: 13px;
+ font-weight: 900;
+ line-height: 20px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.detail-product-price {
+ margin: 4px 0 8px;
+ color: #f43f5e;
+ font-size: 20px;
+ font-weight: 900;
+ line-height: 26px;
+}
+
+.detail-product-currency {
+ font-size: 12px;
+}
+
+.detail-buy-button {
+ width: 100%;
+ height: 34px;
+ padding: 0;
+ border: 0;
+ border-radius: 12px;
+ color: #451a03;
+ background: #fbbf24;
+ font-size: 12px;
+ font-weight: 900;
+ line-height: 34px;
+}
+
+.detail-buy-button::after {
+ border: 0;
+}
+
.detail-faq-wrap {
margin: 0;
}
diff --git a/src/utils/longTextCard.js b/src/utils/longTextCard.js
index 8e36ef1..c10c3a4 100644
--- a/src/utils/longTextCard.js
+++ b/src/utils/longTextCard.js
@@ -7,6 +7,13 @@ export const LONG_TEXT_KEYS = {
guideConclusion: "guide_conclusion",
keyFacts: "key_facts",
sceneImage: "scene_image",
+ preparationSectionTitle: "preparation_section_title",
+ preparationSectionItems: "preparation_section_items",
+ sectionSuggestionTitle: "section_suggestion_title",
+ sectionSuggestionContent: "section_suggestion_content",
+ pitfallSectionTitle: "pitfall_section_title",
+ pitfallSectionItems: "pitfall_section_items",
+ commodityList: "commodity_list",
contentImage: "content_image",
viewSectionTitle: "view_section_title",
viewSectionItems: "view_section_items",
@@ -55,6 +62,13 @@ export const LONG_TEXT_FIELD_CONFIG = [
{ key: LONG_TEXT_KEYS.guideConclusion },
{ key: LONG_TEXT_KEYS.keyFacts },
{ key: LONG_TEXT_KEYS.sceneImage },
+ { key: LONG_TEXT_KEYS.preparationSectionTitle },
+ { key: LONG_TEXT_KEYS.preparationSectionItems },
+ { key: LONG_TEXT_KEYS.sectionSuggestionTitle },
+ { key: LONG_TEXT_KEYS.sectionSuggestionContent },
+ { key: LONG_TEXT_KEYS.pitfallSectionTitle },
+ { key: LONG_TEXT_KEYS.pitfallSectionItems },
+ { key: LONG_TEXT_KEYS.commodityList },
{ key: LONG_TEXT_KEYS.contentImage },
{ key: LONG_TEXT_KEYS.viewSectionTitle },
{ key: LONG_TEXT_KEYS.viewSectionItems },