feat: 增加商品组件
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user