5 Commits

Author SHA1 Message Date
74cdf80b76 feat: ip 位置调整 2026-06-12 16:01:47 +08:00
395ed154b2 feat: 增加对比组件 2026-06-12 15:35:02 +08:00
4b4554e901 feat: 调整了一下组件的顺序 2026-06-12 14:47:57 +08:00
e87865ae09 feat: 完整路线和设备设施组件 2026-06-12 14:34:20 +08:00
d723c690fc feat: markdown 样式调整 2026-06-12 14:32:50 +08:00
7 changed files with 294 additions and 23 deletions

View File

@@ -1,8 +1,10 @@
<template>
<view v-if="shouldRenderField" class="parsed-value">
<image v-if="shouldRenderContentImage" class="content-body-image content-body-image-bottom" :src="contentImageUrl"
mode="widthFix" @click="handlePreviewClick(contentImageUrl)" />
<template v-if="shouldRenderContentImage">
<image class="content-body-image content-body-image-bottom" :src="contentImageUrl" mode="widthFix"
@click="handlePreviewClick(contentImageUrl)" />
</template>
<template v-else-if="shouldRenderSpotLocate">
<view class="detail-action-zone">
<view class="detail-action-label">查看景点详情</view>
@@ -17,13 +19,33 @@
</view>
</template>
<template v-else-if="shouldRenderQuestionSuggest">
<view class="detail-action-label">继续追问</view>
<scroll-view class="detail-faq-wrap" scroll-x>
<view v-for="question in questionSuggestItems" :key="question" class="detail-faq-chip" @click="sendReply(question)">
{{ question }}
<template v-else-if="shouldRenderTourRoutes">
<view class="detail-action-zone">
<view class="detail-action-label">完整路线</view>
<view class="route-step-card">
<!-- <view class="route-step-title">{{ tourRoutesValue.full_routes }}</view> -->
<ChatMarkdown class="content-body-markdown" :text="tourRoutesValue.full_routes" />
<view v-for="(step, index) in tourRoutesValue.mount_condition" :key="step.act" class="route-step">
<view class="route-step-index">{{ index + 1 }}</view>
<view class="route-step-main">
<view class="route-step-act">{{ step.from }} {{ step.to }}</view>
<view class="route-step-meta">{{ step.howToGetThere }}</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<template v-else-if="shouldRenderFacilitiesAlongTheWay">
<view class="route-step-card">
<!-- <view class="route-step-title"> {{ facilitiesAlongTheWayValue.facilities_title }} </view> -->
<ChatMarkdown class="content-body-markdown" :text="facilitiesAlongTheWayValue.facilities_title" />
<view v-for="facility in facilitiesAlongTheWayValue.facility_types" :key="facility.facility_type"
class="facility-row">
<view class="facility-label">{{ facility.facility_type }}</view>
<view>{{ facility.facility_content }}</view>
</view>
</view>
</template>
<template v-else-if="shouldRenderCommodityList">
@@ -161,6 +183,29 @@
</view>
</template>
<template v-else-if="shouldRenderQuestionSuggest">
<view class="detail-action-label">继续追问</view>
<scroll-view class="detail-faq-wrap" scroll-x>
<view v-for="question in questionSuggestItems" :key="question" class="detail-faq-chip"
@click="sendReply(question)">
{{ question }}
</view>
</scroll-view>
</template>
<template v-else-if="shouldRenderDecisionSuggestionList">
<scroll-view class="route-vs-scroll" scroll-x>
<view class="route-vs-wrap">
<view class="route-vs-col" v-for="suggestion in decisionSuggestionListValue" :key="suggestion.item_name">
<view class="route-vs-head">{{ suggestion.item_name }}</view>
<view class="route-vs-boat" style="color: #2563eb">{{ suggestion.item_commodity_price }}</view>
<ChatMarkdown class="content-body-markdown" :text="suggestion.item_content" />
</view>
</view>
</scroll-view>
</template>
<template v-else>
<template v-for="entry in renderFieldEntries" :key="entry.key">
<ChatMarkdown
@@ -300,6 +345,9 @@ const isQuestionSuggestField = computed(() => props.fieldKey === LONG_TEXT_KEYS.
const isCommodityListField = computed(() => props.fieldKey === LONG_TEXT_KEYS.commodityList);
const isPhotoListField = computed(() => props.fieldKey === LONG_TEXT_KEYS.photoList);
const isAigcComponetField = computed(() => props.fieldKey === LONG_TEXT_KEYS.aigcComponet);
const isTourRoutesField = computed(() => props.fieldKey === LONG_TEXT_KEYS.tourRoutes);
const isFacilitiesAlongTheWayField = computed(() => props.fieldKey === LONG_TEXT_KEYS.facilitiesAlongTheWay);
const isDecisionSuggestionListField = computed(() => props.fieldKey === LONG_TEXT_KEYS.decisionSuggestionList);
const displayValue = computed(() => sanitizeValue(props.value));
@@ -379,6 +427,56 @@ const getAigcComponetValue = (value) => {
const aigcComponetValue = computed(() => getAigcComponetValue(displayValue.value));
const getTourRoutesValue = (value) => {
const tourRoutes = isObjectValue(value) ? value : {};
return {
routes_title: toTrimmedText(tourRoutes.routes_title),
full_routes: toTrimmedText(tourRoutes.full_routes),
mount_condition: isArrayValue(tourRoutes.mount_condition)
? tourRoutes.mount_condition.map((step) => ({
from: toTrimmedText(step.from),
to: toTrimmedText(step.to),
howToGetThere: toTrimmedText(step.howToGetThere),
}))
: [],
};
};
const tourRoutesValue = computed(() => getTourRoutesValue(displayValue.value));
const getFacilitiesAlongTheWayValue = (value) => {
const facilities = isObjectValue(value) ? value : {};
return {
facilities_title: toTrimmedText(facilities.facilities_title),
facility_types: isArrayValue(facilities.facility_types)
? facilities.facility_types.map((facility) => ({
facility_type: toTrimmedText(facility.facility_type),
facility_content: toTrimmedText(facility.facility_content),
}))
: [],
};
};
const facilitiesAlongTheWayValue = computed(() => getFacilitiesAlongTheWayValue(displayValue.value));
const getDecisionSuggestionListValue = (value) => {
if (!isArrayValue(value)) return [];
return value
.filter((item) => isObjectValue(item))
.map((suggestion) => ({
item_name: toTrimmedText(suggestion.item_name),
item_commodity_price: toTrimmedText(suggestion.item_commodity_price),
item_content: toTrimmedText(suggestion.item_content),
}))
.filter((suggestion) => hasDisplayValue(suggestion));
};
const decisionSuggestionListValue = computed(() => getDecisionSuggestionListValue(displayValue.value));
const createRenderEntry = (key, value) => {
if (isImageValue(value)) {
return [createImageEntry(key, value)];
@@ -438,6 +536,18 @@ const shouldRenderAigcComponet = computed(() => {
return isAigcComponetField.value && hasDisplayValue(aigcComponetValue.value);
});
const shouldRenderTourRoutes = computed(() => {
return isTourRoutesField.value && hasDisplayValue(tourRoutesValue.value);
});
const shouldRenderFacilitiesAlongTheWay = computed(() => {
return isFacilitiesAlongTheWayField.value && hasDisplayValue(facilitiesAlongTheWayValue.value);
});
const shouldRenderDecisionSuggestionList = computed(() => {
return isDecisionSuggestionListField.value && decisionSuggestionListValue.value.length > 0;
});
/// 其他字段走通用渲染逻辑
const renderFieldEntries = computed(() => {
if (isIgnoredField.value || !hasDisplayValue(props.value)) return [];
@@ -476,7 +586,10 @@ const shouldRenderField = computed(() => {
shouldRenderQuestionSuggest.value ||
shouldRenderCommodityList.value ||
shouldRenderPhotoList.value ||
shouldRenderAigcComponet.value
shouldRenderAigcComponet.value ||
shouldRenderTourRoutes.value ||
shouldRenderFacilitiesAlongTheWay.value ||
shouldRenderDecisionSuggestionList.value
) {
return true;
}

View File

@@ -7,7 +7,7 @@
<!-- 滚动区域 -->
<scroll-view class="flex-full overflow-hidden chat-scroll" scroll-y :scroll-into-view="scrollIntoViewId" scroll-with-animation @scroll="onScroll" @touchstart="onTouchStart" @touchend="onTouchEnd" @touchcancel="onTouchEnd">
<view class="flex flex-col pt-12 px-16 pb-24 border-box gap-8">
<view class="flex flex-col pt-12 px-16 pb-24 border-box gap-10">
<template v-if="hasStructuredLongTextData">
<view v-if="headerSections.title || headerSections.tag" class="long-answer-header">
<view v-if="headerSections.title" class="long-answer-title">

View File

@@ -37,7 +37,7 @@
flex-direction: column;
padding: 12px;
border-radius: 12px;
margin-bottom: 8px;
margin-bottom: 2px;
}
.content-body-list-item {
@@ -442,3 +442,148 @@
font-weight: 900;
line-height: 11px;
}
.route-step-card {
padding: 6px 16px 14px 16px;
border: 1px solid rgba(15, 23, 42, 0.07);
border-radius: 16px;
background: #fff;
}
.route-step-title {
margin-bottom: 12px;
color: #1e293b;
font-size: 13px;
font-weight: 900;
line-height: 20px;
}
.route-step {
position: relative;
display: flex;
gap: 10px;
margin: 10px 0;
}
.route-step:not(:last-child)::before {
content: "";
position: absolute;
top: 22px;
bottom: -4px;
left: 8px;
width: 2px;
border-radius: 999px;
background: #cbd5e1;
pointer-events: none;
}
.route-step:not(:last-child)::after {
content: "";
position: absolute;
bottom: -4px;
left: 4px;
width: 0;
height: 0;
border-right: 5px solid transparent;
border-left: 5px solid transparent;
border-top: 5px solid #cbd5e1;
pointer-events: none;
}
.route-step:last-child {
margin-bottom: 0;
}
.route-step-index {
position: relative;
z-index: 1;
width: 18px;
height: 18px;
flex-shrink: 0;
border-radius: 50%;
color: #fff;
background: #8b5cf6;
font-size: 10px;
font-weight: 900;
line-height: 18px;
text-align: center;
}
.route-step-main {
flex: 1;
}
.route-step-act {
color: #334155;
font-size: 12px;
font-weight: 800;
line-height: 18px;
}
.route-step-meta {
margin-top: 1px;
color: #94a3b8;
font-size: 10.5px;
font-weight: 700;
line-height: 16px;
}
.facility-row {
display: flex;
gap: 8px;
padding: 5px 0;
color: #475569;
font-size: 11.5px;
font-weight: 700;
line-height: 18px;
}
.facility-label {
width: 52px;
flex-shrink: 0;
color: #7c3aed;
font-weight: 900;
}
.route-vs-scroll {
width: 100%;
margin-bottom: 6px;
}
.route-vs-wrap {
display: flex;
flex-direction: row;
}
.route-vs-col {
width: calc(50% - 4px);
flex-shrink: 0;
margin-right: 8px;
box-sizing: border-box;
padding: 11px 12px;
border: 1px solid #e8edf2;
border-radius: 12px;
background: #f8fafc;
}
.route-vs-col:last-child {
margin-right: 0;
}
.route-vs-head {
margin-bottom: 8px;
padding-bottom: 7px;
border-bottom: 1px dashed #d8e0e8;
color: #1e293b;
font-size: 12px;
font-weight: 900;
line-height: 18px;
}
.route-vs-boat {
margin-bottom: 7px;
font-size: 13px;
font-weight: 900;
line-height: 18px;
}

View File

@@ -13,7 +13,7 @@
<image class="w-full block" :src="mainPageDataModel?.initPageImages?.backgroundImageUrl"
mode="aspectFill" style="height: 252px;" />
<view class="absolute bottom-0 left-0 right-0 flex-full">
<view class="px-12 pt-12">
<view class="px-12 pt-12" style="margin-bottom: -1.5px;">
<HomeWelcome :mainPageDataModel="mainPageDataModel" />
</view>
<view style="margin-bottom: -1px;">

View File

@@ -9,7 +9,7 @@
<NoticeMessage :tipsMessage="props.mainPageDataModel.tipsMessage" />
</view>
<SpriteAnimator :src="spriteStyle.ipLargeImage" :frameWidth="spriteStyle.frameWidth"
<SpriteAnimator class="sprite-self-end" :src="spriteStyle.ipLargeImage" :frameWidth="spriteStyle.frameWidth"
:frameHeight="spriteStyle.frameHeight" :totalFrames="spriteStyle.totalFrames" :columns="spriteStyle.columns"
:displayWidth="spriteStyle.displayWidth" :fps="16" />
</view>
@@ -150,4 +150,9 @@ const weatherTextMap = {
</script>
<style scoped></style>
<style scoped>
.sprite-self-end {
align-self: flex-end;
flex-shrink: 0;
}
</style>

View File

@@ -112,9 +112,9 @@ export default {
`,
// 一级标题
h1: `
margin:8px 0;
margin:4px 0;
font-size: 20px;
line-height: 1.6;
line-height: 1.85;
text-align: center;
font-weight: 900;
color: ${themeColor};
@@ -126,7 +126,7 @@ export default {
`,
// 二级标题
h2: `
margin:6px 0;
margin:4px 0;
font-size: 18px;
line-height: 1.85;
text-align:center;
@@ -138,7 +138,7 @@ export default {
`,
// 三级标题
h3: `
margin:6px 0;
margin:4px 0;
font-size: 16px;
line-height: 1.85;
color: ${themeColor};
@@ -151,6 +151,7 @@ export default {
blockquote: `
margin:4px 0;
font-size:15px;
line-height: 1.85;
font-family: ${fontFamily};
font-weight:500;
color: #777777;
@@ -240,16 +241,16 @@ export default {
`,
// 一级标题
h1: `
margin:8px 0;
margin:4px 0;
font-size: 20px;
line-height: 1.6;
line-height: 1.85;
color: ${themeColor};
font-family: ${fontFamily};
font-weight:900;
`,
// 二级标题
h2: `
margin:6px 0;
margin:4px 0;
font-size: 18px;
line-height: 1.85;
color: ${themeColor};
@@ -258,7 +259,7 @@ export default {
`,
// 三级标题
h3: `
margin:6px 0;
margin:4px 0;
font-size: 16px;
line-height: 1.85;
color: ${themeColor};
@@ -269,6 +270,7 @@ export default {
h4: `
margin:4px 0;
font-size: 15px;
line-height: 1.85;
color: ${themeColor};
font-family: ${fontFamily};
font-weight:600;
@@ -277,6 +279,7 @@ export default {
h5: `
margin:4px 0;
font-size: 14px;
line-height: 1.85;
color: ${themeColor};
font-family: ${fontFamily};
font-weight:500;
@@ -285,6 +288,7 @@ export default {
h6: `
margin:4px 0;
font-size: 12px;
line-height: 1.85;
color: ${themeColor};
font-family: ${fontFamily};
font-weight:500;
@@ -293,6 +297,7 @@ export default {
blockquote: `
margin:4px 0;
font-size:14px;
line-height: 1.85;
color: #777777;
border-left: 4px solid #dddddd;
padding: 0 10px;
@@ -320,6 +325,7 @@ export default {
`,
// 加粗
strong: `
line-height: 1.85;
font-weight: bold;
color: ${themeColor};
font-family: ${fontFamily};

View File

@@ -17,6 +17,7 @@ export const LONG_TEXT_KEYS = {
photoList: "photo_list",
aigcComponet: "aigc_componet",
decisionSection: "decision_section",
decisionSuggestionList: "decision_suggestion_list",
routeWarning: "route_warning",
tourRoutes: "tour_routes",
facilitiesAlongTheWay: "facilities_along_the_way",
@@ -42,6 +43,7 @@ export const LONG_TEXT_FIELD_CONFIG = [
{ key: LONG_TEXT_KEYS.photoList },
{ key: LONG_TEXT_KEYS.aigcComponet },
{ key: LONG_TEXT_KEYS.decisionSection },
{ key: LONG_TEXT_KEYS.decisionSuggestionList },
{ key: LONG_TEXT_KEYS.routeWarning },
{ key: LONG_TEXT_KEYS.tourRoutes },
{ key: LONG_TEXT_KEYS.facilitiesAlongTheWay },