feat: 增加商品组件

This commit is contained in:
2026-06-04 14:28:47 +08:00
parent afee02de33
commit 881abda55e
5 changed files with 241 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
<template>
<view v-if="shouldRenderField" class="parsed-value">
<image v-if="shouldRenderContentImage" class="content-body-image" :src="contentImageUrl"
<image v-if="shouldRenderContentImage" class="content-body-image content-body-image-bottom" :src="contentImageUrl"
mode="widthFix" @click="handlePreviewClick(contentImageUrl)" />
<template v-else-if="shouldRenderSpotLocate">
@@ -26,6 +26,47 @@
</view>
</template>
<template v-else-if="shouldRenderCommodityList">
<view class="detail-action-zone">
<view class="detail-action-label">相关票务</view>
<scroll-view class="detail-product-scroll" scroll-x>
<view class="detail-product-row">
<view
v-for="commodity in commodityItems"
:key="commodity.commodity_id || commodity.commodity_name"
class="detail-product-card"
>
<image
v-if="commodity.commodity_photo"
class="detail-product-image"
:src="commodity.commodity_photo"
mode="aspectFill"
@click="handlePreviewClick(commodity.commodity_photo)"
/>
<view class="detail-product-body">
<view v-if="commodity.commodity_tag" class="detail-product-tag">
{{ commodity.commodity_tag }}
</view>
<view v-if="commodity.commodity_name" class="detail-product-name">
{{ commodity.commodity_name }}
</view>
<view v-if="commodity.commodity_price" class="detail-product-price">
<text class="detail-product-currency">¥</text>{{ commodity.commodity_price }}
</view>
<button
v-if="commodity.commodity_id"
class="detail-buy-button"
@click.stop="openCommodityDetail(commodity)"
>
立即购买
</button>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<template v-else>
<template v-for="entry in renderFieldEntries" :key="entry.key">
<view v-if="entry.type === 'text'" class="content-body-text">
@@ -42,6 +83,9 @@
<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 v-if="entry.value.length > 1" class="content-body-list-marker" :style="contentBodyListTextStyle">
{{ formatListMarker(index) }}
</view>
<view class="content-body-list-text" :style="contentBodyListTextStyle">
{{ formatLeafValue(item) }}
</view>
@@ -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,