feat: 拍照攻略组件

This commit is contained in:
2026-06-04 15:39:11 +08:00
parent 881abda55e
commit 41feba7a8b
5 changed files with 136 additions and 3 deletions

View File

@@ -43,11 +43,17 @@ for (const snippet of forbiddenCompatibilitySnippets) {
const requiredStrictRenderingSnippets = [
"LONG_TEXT_KEYS.sceneImage",
"LONG_TEXT_KEYS.commodityList",
"LONG_TEXT_KEYS.photoList",
"commodity.commodity_id",
"commodity.commodity_name",
"commodity.commodity_price",
"commodity.commodity_tag",
"commodity.commodity_photo",
"<swiper",
"<swiper-item",
"photo.photo_name",
"photo.photo_description",
"photo.photo_url",
"content-body-list-marker",
"entry.value.length > 1",
"formatListMarker(index)",

View File

@@ -61,6 +61,12 @@ const expectedNewKeys = {
pitfallSectionTitle: "pitfall_section_title",
pitfallSectionItems: "pitfall_section_items",
commodityList: "commodity_list",
photoSpotSectionTitle: "photo_spot_section_title",
photoSpotSectionItems: "photo_spot_section_items",
bestTimeSuggestion: "best_time_suggestion",
phoneSectionTitle: "phone_section_title",
phoneSectionItems: "phone_section_items",
photoList: "photo_list",
};
for (const [keyName, keyValue] of Object.entries(expectedNewKeys)) {

View File

@@ -67,6 +67,38 @@
</view>
</template>
<template v-else-if="shouldRenderPhotoList">
<view class="detail-action-zone">
<view class="detail-action-label mt-12">查看机位图</view>
<swiper
class="photo-card-swiper"
:indicator-dots="photoItems.length > 1"
indicator-color="#FFFFFF"
indicator-active-color="#00A6FF"
circular
:duration="250"
>
<swiper-item
v-for="photo in photoItems"
:key="photo.photo_url || photo.photo_name"
>
<view class="photo-card" @click="handlePreviewClick(photo.photo_url, photoItems.map(p => p.photo_url))">
<image class="photo-card-image" :src="photo.photo_url" mode="aspectFill" />
<view class="photo-card-caption">
<view v-if="photo.photo_name" class="photo-card-title">
{{ photo.photo_name }}
</view>
<view v-if="photo.photo_description" class="photo-card-subtitle">
{{ photo.photo_description }}
</view>
</view>
<view class="photo-card-expand">↗</view>
</view>
</swiper-item>
</swiper>
</view>
</template>
<template v-else>
<template v-for="entry in renderFieldEntries" :key="entry.key">
<view v-if="entry.type === 'text'" class="content-body-text">
@@ -193,6 +225,7 @@ const isContentImageField = computed(() =>
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 isPhotoListField = computed(() => props.fieldKey === LONG_TEXT_KEYS.photoList);
const displayValue = computed(() => sanitizeValue(props.value));
@@ -235,6 +268,19 @@ const commodityItems = computed(() => {
.filter((commodity) => hasDisplayValue(commodity));
});
const photoItems = computed(() => {
if (!isArrayValue(displayValue.value)) return [];
return displayValue.value
.filter((item) => isObjectValue(item))
.map((photo) => ({
photo_name: toTrimmedText(photo.photo_name),
photo_description: toTrimmedText(photo.photo_description),
photo_url: toTrimmedText(photo.photo_url),
}))
.filter((photo) => !!photo.photo_url);
});
const shouldRenderContentImage = computed(() => {
return isContentImageField.value && !!contentImageUrl.value;
});
@@ -251,6 +297,10 @@ const shouldRenderCommodityList = computed(() => {
return isCommodityListField.value && commodityItems.value.length > 0;
});
const shouldRenderPhotoList = computed(() => {
return isPhotoListField.value && photoItems.value.length > 0;
});
/// 其他字段走通用渲染逻辑
const renderFieldEntries = computed(() => {
if (isIgnoredField.value || !hasDisplayValue(props.value)) return [];
@@ -305,7 +355,8 @@ const shouldRenderField = computed(() => {
shouldRenderContentImage.value ||
shouldRenderSpotLocate.value ||
shouldRenderQuestionSuggest.value ||
shouldRenderCommodityList.value
shouldRenderCommodityList.value ||
shouldRenderPhotoList.value
) {
return true;
}
@@ -351,10 +402,10 @@ const openCommodityDetail = (commodity) => {
});
};
const handlePreviewClick = (imageUrl) => {
const handlePreviewClick = (imageUrl, imageUrls) => {
uni.previewImage({
current: imageUrl,
urls: [imageUrl],
urls: imageUrls && imageUrls.length > 0 ? imageUrls : [imageUrl],
});
};

View File

@@ -226,6 +226,64 @@
border: 0;
}
.photo-card-swiper {
width: 100%;
height: 228px;
}
.photo-card {
position: relative;
height: 200px;
overflow: hidden;
border-radius: 16px;
background: #f8fafc;
}
.photo-card-image {
width: 100%;
height: 200px;
background: #f1f5f9;
}
.photo-card-caption {
position: absolute;
right: 0;
bottom: 0;
left: 0;
padding: 28px 14px 12px;
background: linear-gradient(to top, rgba(15, 23, 42, 0.58), rgba(15, 23, 42, 0));
}
.photo-card-title {
color: #fff;
font-size: 13px;
font-weight: 900;
line-height: 18px;
}
.photo-card-subtitle {
margin-top: 2px;
color: rgba(255, 255, 255, 0.78);
font-size: 11px;
font-weight: 700;
line-height: 16px;
}
.photo-card-expand {
position: absolute;
top: 10px;
right: 10px;
width: 28px;
height: 28px;
border-radius: 50%;
color: #fff;
background: rgba(15, 23, 42, 0.42);
font-size: 15px;
font-weight: 900;
line-height: 28px;
text-align: center;
}
.detail-faq-wrap {
margin: 0;
}

View File

@@ -7,6 +7,12 @@ export const LONG_TEXT_KEYS = {
guideConclusion: "guide_conclusion",
keyFacts: "key_facts",
sceneImage: "scene_image",
photoSpotSectionTitle: "photo_spot_section_title",
photoSpotSectionItems: "photo_spot_section_items",
bestTimeSuggestion: "best_time_suggestion",
phoneSectionTitle: "phone_section_title",
phoneSectionItems: "phone_section_items",
photoList: "photo_list",
preparationSectionTitle: "preparation_section_title",
preparationSectionItems: "preparation_section_items",
sectionSuggestionTitle: "section_suggestion_title",
@@ -62,6 +68,12 @@ 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.photoSpotSectionTitle },
{ key: LONG_TEXT_KEYS.photoSpotSectionItems },
{ key: LONG_TEXT_KEYS.bestTimeSuggestion },
{ key: LONG_TEXT_KEYS.phoneSectionTitle },
{ key: LONG_TEXT_KEYS.phoneSectionItems },
{ key: LONG_TEXT_KEYS.photoList },
{ key: LONG_TEXT_KEYS.preparationSectionTitle },
{ key: LONG_TEXT_KEYS.preparationSectionItems },
{ key: LONG_TEXT_KEYS.sectionSuggestionTitle },