@vigilkids/section-renderer-vue 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/sections/article/prosemirror.d.ts +2 -0
- package/dist/sections/article/prosemirror.mjs +8 -0
- package/dist/sections/article/vigilkids/ArticleProductCard.vue +0 -6
- package/dist/sections/article/vigilkids/ArticleRelatedArticles.vue +27 -21
- package/dist/sections/article/vigilkids/ArticleRetentionBanner.vue +4 -3
- package/dist/sections/article/vigilkids/ArticleSectionIntro.vue +12 -5
- package/dist/sections/article/vigilkids/ArticleStyledHeading.vue +1 -1
- package/dist/sections/article/vigilkids/ArticleTipNote.vue +2 -1
- package/dist/sections/article/vigilkids/ArticleTocList.vue +11 -4
- package/dist/sections/article/vigilkids/ArticleTopAd.vue +53 -44
- package/dist/sections/article/vigilkids/product-card/ProductCardCtaGroup.vue +2 -2
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantD.vue +10 -8
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantE.vue +7 -2
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantF.vue +18 -9
- package/dist/sections/article/vigilkids/product-card/ProductCardVariantG.vue +7 -4
- package/package.json +10 -10
|
@@ -67,3 +67,11 @@ export function renderContent(content) {
|
|
|
67
67
|
}
|
|
68
68
|
return "";
|
|
69
69
|
}
|
|
70
|
+
const singleParagraphWrapperPattern = /^\s*<p(?:\s[^>]*)?>([\s\S]*)<\/p>\s*$/i;
|
|
71
|
+
export function renderInlineContent(content) {
|
|
72
|
+
const html = renderContent(content);
|
|
73
|
+
const matched = html.match(singleParagraphWrapperPattern);
|
|
74
|
+
if (!matched)
|
|
75
|
+
return html;
|
|
76
|
+
return matched[1] ?? "";
|
|
77
|
+
}
|
|
@@ -11,10 +11,6 @@ import ProductCardVariantE from './product-card/ProductCardVariantE.vue'
|
|
|
11
11
|
import ProductCardVariantF from './product-card/ProductCardVariantF.vue'
|
|
12
12
|
import ProductCardVariantG from './product-card/ProductCardVariantG.vue'
|
|
13
13
|
|
|
14
|
-
// ArticleProductCard 分发器(Shopify Hydrogen section variants 模式)
|
|
15
|
-
// 根据 variant 选择子组件 + 根 class,内联 HTML 全部下沉到 variant 子组件
|
|
16
|
-
// 子组件各自持有完整 useInlineEdit 契约,保证可独立替换和单测
|
|
17
|
-
|
|
18
14
|
const props = defineProps<{
|
|
19
15
|
blockOrder: string[]
|
|
20
16
|
blocks: Record<string, BlockData>
|
|
@@ -32,7 +28,6 @@ const emit = defineEmits<{
|
|
|
32
28
|
|
|
33
29
|
const variant = computed(() => String(props.settings.variant || 'A').toUpperCase())
|
|
34
30
|
|
|
35
|
-
// variant 到 { 根 class, 子组件 } 的映射(对齐 vigilkids.css 1494-1500)
|
|
36
31
|
const VARIANT_MAP = {
|
|
37
32
|
A: { class: 'product-card', component: ProductCardVariantA },
|
|
38
33
|
B: { class: 'product-card-compact', component: ProductCardVariantB },
|
|
@@ -43,7 +38,6 @@ const VARIANT_MAP = {
|
|
|
43
38
|
G: { class: 'product-card-checklist', component: ProductCardVariantG },
|
|
44
39
|
} as const
|
|
45
40
|
|
|
46
|
-
// 未知 variant 时回退到 A,保证数据异常不会渲染空节点
|
|
47
41
|
const resolved = computed(() => VARIANT_MAP[variant.value as keyof typeof VARIANT_MAP] || VARIANT_MAP.A)
|
|
48
42
|
</script>
|
|
49
43
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { BlockData } from '@vigilkids/section-core'
|
|
2
|
+
import type { ArticleReferenceSnapshot, BlockData } from '@vigilkids/section-core'
|
|
3
3
|
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
|
|
@@ -25,12 +25,28 @@ const s = computed(() => props.settings)
|
|
|
25
25
|
const items = computed(() =>
|
|
26
26
|
props.blockOrder
|
|
27
27
|
.filter(id => props.blocks[id]?.type === 'related_item')
|
|
28
|
-
.map(id =>
|
|
28
|
+
.map((id) => {
|
|
29
|
+
const block = props.blocks[id]!
|
|
30
|
+
const article = block.settings.article as ArticleReferenceSnapshot | null | undefined
|
|
31
|
+
const overrideImageURL = String(block.settings.cover_image_url || '')
|
|
32
|
+
const overrideImageAlt = String(block.settings.cover_image_alt || '')
|
|
33
|
+
const featuredImageAlt = article?.featured_image_alt || ''
|
|
34
|
+
const title = article?.title || ''
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
id,
|
|
38
|
+
href: article?.url_path || '#',
|
|
39
|
+
title,
|
|
40
|
+
description: article?.excerpt || '',
|
|
41
|
+
imageURL: overrideImageURL || article?.featured_image_url || '',
|
|
42
|
+
imageAlt: overrideImageAlt || featuredImageAlt || title,
|
|
43
|
+
}
|
|
44
|
+
}),
|
|
29
45
|
)
|
|
30
46
|
|
|
31
47
|
const showDots = computed(() => !!s.value.show_dots)
|
|
32
48
|
|
|
33
|
-
const { editableAttrs
|
|
49
|
+
const { editableAttrs } = useInlineEdit({
|
|
34
50
|
editorMode: () => !!props.editorMode,
|
|
35
51
|
onSettingUpdate: (key, value) => emit('update:setting', key, value),
|
|
36
52
|
onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
|
|
@@ -42,39 +58,29 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
|
42
58
|
|
|
43
59
|
<template>
|
|
44
60
|
<section class="article-recommend" data-component="related-articles-carousel">
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
</h2>
|
|
61
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
62
|
+
<h2 v-if="s.title" class="article-recommend-title" v-bind="editableAttrs('title')" v-html="s.title" />
|
|
48
63
|
<div class="recommend-grid">
|
|
49
64
|
<a
|
|
50
65
|
v-for="(item, i) in items"
|
|
51
66
|
:key="item.id"
|
|
52
|
-
:href="
|
|
67
|
+
:href="item.href"
|
|
53
68
|
class="recommend-card"
|
|
54
69
|
>
|
|
55
70
|
<div class="recommend-card-image">
|
|
56
71
|
<img
|
|
57
|
-
:src="
|
|
58
|
-
:alt="
|
|
72
|
+
:src="item.imageURL"
|
|
73
|
+
:alt="item.imageAlt"
|
|
59
74
|
>
|
|
60
75
|
</div>
|
|
61
76
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
62
|
-
<p
|
|
63
|
-
class="recommend-card-title"
|
|
64
|
-
v-bind="blockEditableAttrs(item.id, 'title')"
|
|
65
|
-
v-html="item.block.settings.title"
|
|
66
|
-
/>
|
|
77
|
+
<p class="recommend-card-title" v-html="item.title" />
|
|
67
78
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
68
|
-
<p
|
|
69
|
-
class="recommend-card-desc"
|
|
70
|
-
v-bind="blockEditableAttrs(item.id, 'description')"
|
|
71
|
-
v-html="item.block.settings.description"
|
|
72
|
-
/>
|
|
79
|
+
<p class="recommend-card-desc" v-html="item.description" />
|
|
73
80
|
<span class="sr-only">{{ i + 1 }}</span>
|
|
74
81
|
</a>
|
|
75
82
|
</div>
|
|
76
|
-
<div v-if="showDots && items.length >
|
|
77
|
-
<!-- 首屏先给第一个 dot 标 active 作为 init 前视觉提示;embla 初始化后由 interactions/vigilkids.ts 的 syncActive 接管 -->
|
|
83
|
+
<div v-if="showDots && items.length > 1" class="recommend-dots">
|
|
78
84
|
<span
|
|
79
85
|
v-for="(item, i) in items"
|
|
80
86
|
:key="item.id"
|
|
@@ -80,8 +80,8 @@ const { editableAttrs } = useInlineEdit({
|
|
|
80
80
|
class="retention-banner-btn"
|
|
81
81
|
:href="(s.cta_url as string) || '#'"
|
|
82
82
|
v-bind="editableAttrs('cta_label')"
|
|
83
|
+
v-html="s.cta_label"
|
|
83
84
|
>
|
|
84
|
-
{{ s.cta_label }}
|
|
85
85
|
</a>
|
|
86
86
|
</div>
|
|
87
87
|
</div>
|
|
@@ -91,8 +91,9 @@ const { editableAttrs } = useInlineEdit({
|
|
|
91
91
|
aria-label="Close"
|
|
92
92
|
@click.stop="handleClose"
|
|
93
93
|
>
|
|
94
|
-
<svg width="
|
|
95
|
-
<path d="
|
|
94
|
+
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
95
|
+
<path d="M12 1L1 12" stroke="#999999" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
96
|
+
<path d="M12 12L1 1" stroke="#999999" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
96
97
|
</svg>
|
|
97
98
|
</button>
|
|
98
99
|
</div>
|
|
@@ -4,6 +4,7 @@ import type { BlockData } from '@vigilkids/section-core'
|
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
|
|
6
6
|
import { useInlineEdit } from '../../../composables/useInlineEdit'
|
|
7
|
+
import { renderInlineContent } from '../prosemirror'
|
|
7
8
|
|
|
8
9
|
const props = defineProps<{
|
|
9
10
|
blockOrder: string[]
|
|
@@ -25,7 +26,11 @@ const s = computed(() => props.settings)
|
|
|
25
26
|
const items = computed(() =>
|
|
26
27
|
props.blockOrder
|
|
27
28
|
.filter(id => props.blocks[id]?.type === 'intro_item')
|
|
28
|
-
.map(id => ({
|
|
29
|
+
.map(id => ({
|
|
30
|
+
id,
|
|
31
|
+
block: props.blocks[id]!,
|
|
32
|
+
renderedText: renderInlineContent(props.blocks[id]!.settings.text),
|
|
33
|
+
})),
|
|
29
34
|
)
|
|
30
35
|
|
|
31
36
|
const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
@@ -42,15 +47,17 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
|
42
47
|
<section>
|
|
43
48
|
<p class="section-intro">
|
|
44
49
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
45
|
-
<span class="section-intro-bold" v-bind="editableAttrs('bold_text')" v-html="s.bold_text" />
|
|
50
|
+
<span v-if="s.bold_text" class="section-intro-bold" v-bind="editableAttrs('bold_text')" v-html="s.bold_text" />
|
|
46
51
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
47
|
-
<span v-bind="editableAttrs('intro_text')" v-html="s.intro_text" />
|
|
52
|
+
<span v-if="s.intro_text" v-bind="editableAttrs('intro_text')" v-html="s.intro_text" />
|
|
48
53
|
</p>
|
|
49
|
-
<ul class="section-intro-list">
|
|
54
|
+
<ul v-if="items.length" class="section-intro-list">
|
|
50
55
|
<li v-for="item in items" :key="item.id">
|
|
51
56
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
52
|
-
<span v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.
|
|
57
|
+
<span v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.renderedText" />
|
|
53
58
|
</li>
|
|
54
59
|
</ul>
|
|
60
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
61
|
+
<p v-if="s.trailing_text" class="section-intro" v-bind="editableAttrs('trailing_text')" v-html="s.trailing_text" />
|
|
55
62
|
</section>
|
|
56
63
|
</template>
|
|
@@ -41,7 +41,7 @@ const { editableAttrs } = useInlineEdit({
|
|
|
41
41
|
<template>
|
|
42
42
|
<h3 :class="headingClass">
|
|
43
43
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
44
|
-
<span :class="iconClass" v-bind="editableAttrs('
|
|
44
|
+
<span :class="iconClass" v-bind="editableAttrs('badge_text')" v-html="s.badge_text" />
|
|
45
45
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
46
46
|
<span v-bind="editableAttrs('text')" v-html="s.text" />
|
|
47
47
|
</h3>
|
|
@@ -50,7 +50,8 @@ const { editableAttrs } = useInlineEdit({
|
|
|
50
50
|
<div :class="containerClass">
|
|
51
51
|
<div :class="labelClass">
|
|
52
52
|
<img v-if="s.icon_url" :src="(s.icon_url as string)" :alt="(s.label_text as string) || ''">
|
|
53
|
-
|
|
53
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
54
|
+
<span v-bind="editableAttrs('label_text')" v-html="s.label_text" />
|
|
54
55
|
</div>
|
|
55
56
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
56
57
|
<p :class="contentClass" v-bind="editableAttrs('content')" v-html="s.content" />
|
|
@@ -4,6 +4,7 @@ import type { BlockData } from '@vigilkids/section-core'
|
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
|
|
6
6
|
import { useInlineEdit } from '../../../composables/useInlineEdit'
|
|
7
|
+
import { renderInlineContent } from '../prosemirror'
|
|
7
8
|
|
|
8
9
|
const props = defineProps<{
|
|
9
10
|
blockOrder: string[]
|
|
@@ -31,10 +32,14 @@ const isOrdered = computed(() => variant.value === 'ordered')
|
|
|
31
32
|
const items = computed(() =>
|
|
32
33
|
props.blockOrder
|
|
33
34
|
.filter(id => props.blocks[id]?.type === 'toc_item')
|
|
34
|
-
.map(id => ({
|
|
35
|
+
.map(id => ({
|
|
36
|
+
id,
|
|
37
|
+
block: props.blocks[id]!,
|
|
38
|
+
renderedText: renderInlineContent(props.blocks[id]!.settings.text),
|
|
39
|
+
})),
|
|
35
40
|
)
|
|
36
41
|
|
|
37
|
-
const { blockEditableAttrs } = useInlineEdit({
|
|
42
|
+
const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
38
43
|
editorMode: () => !!props.editorMode,
|
|
39
44
|
onSettingUpdate: (key, value) => emit('update:setting', key, value),
|
|
40
45
|
onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
|
|
@@ -45,12 +50,14 @@ const { blockEditableAttrs } = useInlineEdit({
|
|
|
45
50
|
</script>
|
|
46
51
|
|
|
47
52
|
<template>
|
|
53
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
54
|
+
<p v-if="s.title" class="toc-title" v-bind="editableAttrs('title')" v-html="s.title" />
|
|
48
55
|
<ol v-if="isOrdered" :class="listClass">
|
|
49
56
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
50
|
-
<li v-for="item in items" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.
|
|
57
|
+
<li v-for="item in items" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.renderedText" />
|
|
51
58
|
</ol>
|
|
52
59
|
<ul v-else :class="listClass">
|
|
53
60
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
54
|
-
<li v-for="item in items" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.
|
|
61
|
+
<li v-for="item in items" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.renderedText" />
|
|
55
62
|
</ul>
|
|
56
63
|
</template>
|
|
@@ -4,6 +4,7 @@ import type { BlockData } from '@vigilkids/section-core'
|
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
|
|
6
6
|
import { useInlineEdit } from '../../../composables/useInlineEdit'
|
|
7
|
+
import { renderInlineContent } from '../prosemirror'
|
|
7
8
|
|
|
8
9
|
const props = defineProps<{
|
|
9
10
|
blockOrder: string[]
|
|
@@ -25,7 +26,11 @@ const s = computed(() => props.settings)
|
|
|
25
26
|
const features = computed(() =>
|
|
26
27
|
props.blockOrder
|
|
27
28
|
.filter(id => props.blocks[id]?.type === 'feature')
|
|
28
|
-
.map(id => ({
|
|
29
|
+
.map(id => ({
|
|
30
|
+
id,
|
|
31
|
+
block: props.blocks[id]!,
|
|
32
|
+
renderedText: renderInlineContent(props.blocks[id]!.settings.text),
|
|
33
|
+
})),
|
|
29
34
|
)
|
|
30
35
|
|
|
31
36
|
const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
@@ -39,52 +44,56 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
|
39
44
|
</script>
|
|
40
45
|
|
|
41
46
|
<template>
|
|
42
|
-
<section
|
|
43
|
-
<div class="top-banner
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<p
|
|
52
|
-
v-if="s.description"
|
|
53
|
-
class="top-banner-desc"
|
|
54
|
-
v-bind="editableAttrs('description')"
|
|
55
|
-
v-html="s.description"
|
|
56
|
-
/>
|
|
57
|
-
<ul v-if="features.length" class="top-banner-features">
|
|
47
|
+
<section>
|
|
48
|
+
<div class="top-banner">
|
|
49
|
+
<div class="top-banner-info">
|
|
50
|
+
<img
|
|
51
|
+
v-if="s.header_image_url"
|
|
52
|
+
class="top-banner-header"
|
|
53
|
+
:src="(s.header_image_url as string)"
|
|
54
|
+
:alt="(s.header_alt as string) || ''"
|
|
55
|
+
>
|
|
58
56
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
59
|
-
<
|
|
60
|
-
v-
|
|
61
|
-
|
|
62
|
-
v-bind="
|
|
63
|
-
v-html="
|
|
57
|
+
<p
|
|
58
|
+
v-if="s.description"
|
|
59
|
+
class="top-banner-desc"
|
|
60
|
+
v-bind="editableAttrs('description')"
|
|
61
|
+
v-html="s.description"
|
|
64
62
|
/>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
63
|
+
<ul v-if="features.length" class="top-banner-features">
|
|
64
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
65
|
+
<li
|
|
66
|
+
v-for="item in features"
|
|
67
|
+
:key="item.id"
|
|
68
|
+
v-bind="blockEditableAttrs(item.id, 'text')"
|
|
69
|
+
v-html="item.renderedText"
|
|
70
|
+
/>
|
|
71
|
+
</ul>
|
|
72
|
+
<div class="action-buttons">
|
|
73
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
74
|
+
<a
|
|
75
|
+
v-if="s.cta_primary_label"
|
|
76
|
+
class="action-btn-primary"
|
|
77
|
+
:href="(s.cta_primary_url as string) || '#'"
|
|
78
|
+
v-bind="editableAttrs('cta_primary_label')"
|
|
79
|
+
v-html="s.cta_primary_label"
|
|
80
|
+
/>
|
|
81
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
82
|
+
<a
|
|
83
|
+
v-if="s.cta_outline_label"
|
|
84
|
+
class="action-btn-outline"
|
|
85
|
+
:href="(s.cta_outline_url as string) || '#'"
|
|
86
|
+
v-bind="editableAttrs('cta_outline_label')"
|
|
87
|
+
v-html="s.cta_outline_label"
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<img
|
|
92
|
+
v-if="s.mockup_image_url"
|
|
93
|
+
class="top-banner-mockup"
|
|
94
|
+
:src="(s.mockup_image_url as string)"
|
|
95
|
+
:alt="(s.mockup_alt as string) || ''"
|
|
79
96
|
>
|
|
80
|
-
{{ s.cta_outline_label }}
|
|
81
|
-
</a>
|
|
82
97
|
</div>
|
|
83
|
-
<img
|
|
84
|
-
v-if="s.mockup_image_url"
|
|
85
|
-
class="top-banner-mockup"
|
|
86
|
-
:src="(s.mockup_image_url as string)"
|
|
87
|
-
:alt="(s.mockup_alt as string) || ''"
|
|
88
|
-
>
|
|
89
98
|
</section>
|
|
90
99
|
</template>
|
|
@@ -14,8 +14,8 @@ const { s, editableAttrs } = props
|
|
|
14
14
|
<template>
|
|
15
15
|
<div class="action-buttons">
|
|
16
16
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
17
|
-
<a :href="(s.cta_primary_url as string) || '#'" class="
|
|
17
|
+
<a :href="(s.cta_primary_url as string) || '#'" class="action-btn-primary" v-bind="editableAttrs('cta_primary_label')" v-html="s.cta_primary_label" />
|
|
18
18
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
19
|
-
<a :href="(s.cta_outline_url as string) || '#'" class="
|
|
19
|
+
<a :href="(s.cta_outline_url as string) || '#'" class="action-btn-outline" v-bind="editableAttrs('cta_outline_label')" v-html="s.cta_outline_label" />
|
|
20
20
|
</div>
|
|
21
21
|
</template>
|
|
@@ -4,10 +4,9 @@ import type { BlockData } from '@vigilkids/section-core'
|
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
|
|
6
6
|
import { useInlineEdit } from '../../../../composables/useInlineEdit'
|
|
7
|
+
import { renderInlineContent } from '../../prosemirror'
|
|
7
8
|
import ProductCardCtaGroup from './ProductCardCtaGroup.vue'
|
|
8
9
|
|
|
9
|
-
// Variant D: 功能列表 + 手机图(product-card-features)
|
|
10
|
-
// 消费 feature_item 类型的 blocks 渲染功能列表项
|
|
11
10
|
const props = defineProps<{
|
|
12
11
|
blockOrder: string[]
|
|
13
12
|
blocks: Record<string, BlockData>
|
|
@@ -25,11 +24,14 @@ const emit = defineEmits<{
|
|
|
25
24
|
|
|
26
25
|
const s = computed(() => props.settings)
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
const featureItems = computed(() =>
|
|
27
|
+
const featureDots = computed(() =>
|
|
30
28
|
props.blockOrder
|
|
31
|
-
.filter(id => props.blocks[id]?.type === '
|
|
32
|
-
.map(id => ({
|
|
29
|
+
.filter(id => props.blocks[id]?.type === 'feature_dot')
|
|
30
|
+
.map(id => ({
|
|
31
|
+
id,
|
|
32
|
+
block: props.blocks[id]!,
|
|
33
|
+
renderedText: renderInlineContent(props.blocks[id]!.settings.text),
|
|
34
|
+
})),
|
|
33
35
|
)
|
|
34
36
|
|
|
35
37
|
const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
@@ -47,9 +49,9 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
|
47
49
|
<div>
|
|
48
50
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
49
51
|
<p class="product-name" v-bind="editableAttrs('product_name')" v-html="s.product_name" />
|
|
50
|
-
<ul class="
|
|
52
|
+
<ul class="feature-dot-list">
|
|
51
53
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
52
|
-
<li v-for="item in
|
|
54
|
+
<li v-for="item in featureDots" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.renderedText" />
|
|
53
55
|
</ul>
|
|
54
56
|
<ProductCardCtaGroup :s="s" :editable-attrs="editableAttrs" />
|
|
55
57
|
</div>
|
|
@@ -4,6 +4,7 @@ import type { BlockData } from '@vigilkids/section-core'
|
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
|
|
6
6
|
import { useInlineEdit } from '../../../../composables/useInlineEdit'
|
|
7
|
+
import { renderInlineContent } from '../../prosemirror'
|
|
7
8
|
import ProductCardCtaGroup from './ProductCardCtaGroup.vue'
|
|
8
9
|
|
|
9
10
|
// Variant E: 两侧装饰 + feature-dot(product-card-split)
|
|
@@ -28,7 +29,11 @@ const s = computed(() => props.settings)
|
|
|
28
29
|
const featureDots = computed(() =>
|
|
29
30
|
props.blockOrder
|
|
30
31
|
.filter(id => props.blocks[id]?.type === 'feature_dot')
|
|
31
|
-
.map(id => ({
|
|
32
|
+
.map(id => ({
|
|
33
|
+
id,
|
|
34
|
+
block: props.blocks[id]!,
|
|
35
|
+
renderedText: renderInlineContent(props.blocks[id]!.settings.text),
|
|
36
|
+
})),
|
|
32
37
|
)
|
|
33
38
|
|
|
34
39
|
const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
@@ -48,7 +53,7 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
|
48
53
|
<p class="split-product-name product-name" v-bind="editableAttrs('product_name')" v-html="s.product_name" />
|
|
49
54
|
<ul class="feature-dot-list">
|
|
50
55
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
51
|
-
<li v-for="item in featureDots" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.
|
|
56
|
+
<li v-for="item in featureDots" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.renderedText" />
|
|
52
57
|
</ul>
|
|
53
58
|
<ProductCardCtaGroup :s="s" :editable-attrs="editableAttrs" />
|
|
54
59
|
</div>
|
|
@@ -4,11 +4,8 @@ import type { BlockData } from '@vigilkids/section-core'
|
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
|
|
6
6
|
import { useInlineEdit } from '../../../../composables/useInlineEdit'
|
|
7
|
+
import { renderInlineContent } from '../../prosemirror'
|
|
7
8
|
|
|
8
|
-
// Variant F: 按钮纵向排列(product-card-vertical)
|
|
9
|
-
// 左右分栏 + 中间分隔线结构,CTA 按钮垂直堆叠
|
|
10
|
-
// 因结构与其它 variant 的 action-buttons 横向布局差异大,未使用共享 CtaGroup
|
|
11
|
-
// 保留直接渲染 a 标签,保证纵向容器的样式作用于按钮本身
|
|
12
9
|
const props = defineProps<{
|
|
13
10
|
blockOrder: string[]
|
|
14
11
|
blocks: Record<string, BlockData>
|
|
@@ -26,7 +23,17 @@ const emit = defineEmits<{
|
|
|
26
23
|
|
|
27
24
|
const s = computed(() => props.settings)
|
|
28
25
|
|
|
29
|
-
const
|
|
26
|
+
const featureChecks = computed(() =>
|
|
27
|
+
props.blockOrder
|
|
28
|
+
.filter(id => props.blocks[id]?.type === 'feature_check')
|
|
29
|
+
.map(id => ({
|
|
30
|
+
id,
|
|
31
|
+
block: props.blocks[id]!,
|
|
32
|
+
renderedText: renderInlineContent(props.blocks[id]!.settings.text),
|
|
33
|
+
})),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
30
37
|
editorMode: () => !!props.editorMode,
|
|
31
38
|
onSettingUpdate: (key, value) => emit('update:setting', key, value),
|
|
32
39
|
onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
|
|
@@ -43,14 +50,16 @@ const { editableAttrs } = useInlineEdit({
|
|
|
43
50
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
44
51
|
<p class="product-name" v-bind="editableAttrs('product_name')" v-html="s.product_name" />
|
|
45
52
|
</div>
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
<ul class="feature-check-list">
|
|
54
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
55
|
+
<li v-for="item in featureChecks" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.renderedText" />
|
|
56
|
+
</ul>
|
|
48
57
|
</div>
|
|
49
58
|
<div class="card-vertical-divider" />
|
|
50
59
|
<div class="card-vertical-right">
|
|
51
60
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
52
|
-
<a :href="(s.cta_primary_url as string) || '#'" class="
|
|
61
|
+
<a :href="(s.cta_primary_url as string) || '#'" class="action-btn-primary" v-bind="editableAttrs('cta_primary_label')" v-html="s.cta_primary_label" />
|
|
53
62
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
54
|
-
<a :href="(s.cta_outline_url as string) || '#'" class="
|
|
63
|
+
<a :href="(s.cta_outline_url as string) || '#'" class="action-btn-outline" v-bind="editableAttrs('cta_outline_label')" v-html="s.cta_outline_label" />
|
|
55
64
|
</div>
|
|
56
65
|
</template>
|
|
@@ -4,10 +4,9 @@ import type { BlockData } from '@vigilkids/section-core'
|
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
|
|
6
6
|
import { useInlineEdit } from '../../../../composables/useInlineEdit'
|
|
7
|
+
import { renderInlineContent } from '../../prosemirror'
|
|
7
8
|
import ProductCardCtaGroup from './ProductCardCtaGroup.vue'
|
|
8
9
|
|
|
9
|
-
// Variant G: 蓝底 checklist(product-card-checklist)
|
|
10
|
-
// 与 D 相同使用 feature_item blocks,但无图片,根 class 切换为深色主题
|
|
11
10
|
const props = defineProps<{
|
|
12
11
|
blockOrder: string[]
|
|
13
12
|
blocks: Record<string, BlockData>
|
|
@@ -28,7 +27,11 @@ const s = computed(() => props.settings)
|
|
|
28
27
|
const featureItems = computed(() =>
|
|
29
28
|
props.blockOrder
|
|
30
29
|
.filter(id => props.blocks[id]?.type === 'feature_item')
|
|
31
|
-
.map(id => ({
|
|
30
|
+
.map(id => ({
|
|
31
|
+
id,
|
|
32
|
+
block: props.blocks[id]!,
|
|
33
|
+
renderedText: renderInlineContent(props.blocks[id]!.settings.text),
|
|
34
|
+
})),
|
|
32
35
|
)
|
|
33
36
|
|
|
34
37
|
const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
@@ -46,7 +49,7 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
|
|
|
46
49
|
<p class="product-name" v-bind="editableAttrs('product_name')" v-html="s.product_name" />
|
|
47
50
|
<ul class="product-feature-list">
|
|
48
51
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
49
|
-
<li v-for="item in featureItems" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.
|
|
52
|
+
<li v-for="item in featureItems" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.renderedText" />
|
|
50
53
|
</ul>
|
|
51
54
|
<ProductCardCtaGroup :s="s" :editable-attrs="editableAttrs" />
|
|
52
55
|
</template>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vigilkids/section-renderer-vue",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Vue 3 adapter for OneX Section Renderer SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -32,6 +32,12 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"embla-carousel": "^8.3.0"
|
|
34
34
|
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "unbuild",
|
|
37
|
+
"lint": "eslint .",
|
|
38
|
+
"lint:fix": "eslint . --fix",
|
|
39
|
+
"format": "prettier --write src/"
|
|
40
|
+
},
|
|
35
41
|
"publishConfig": {
|
|
36
42
|
"access": "public"
|
|
37
43
|
},
|
|
@@ -41,8 +47,8 @@
|
|
|
41
47
|
"directory": "packages/section-renderer-vue"
|
|
42
48
|
},
|
|
43
49
|
"peerDependencies": {
|
|
44
|
-
"
|
|
45
|
-
"
|
|
50
|
+
"@vigilkids/section-core": "^0.1.2",
|
|
51
|
+
"vue": "^3.4.0"
|
|
46
52
|
},
|
|
47
53
|
"devDependencies": {
|
|
48
54
|
"@antfu/eslint-config": "^4.13.2",
|
|
@@ -51,11 +57,5 @@
|
|
|
51
57
|
"prettier": "^3.5.3",
|
|
52
58
|
"unbuild": "^3.5.0",
|
|
53
59
|
"vue-tsc": "^2.0.21"
|
|
54
|
-
},
|
|
55
|
-
"scripts": {
|
|
56
|
-
"build": "unbuild",
|
|
57
|
-
"lint": "eslint .",
|
|
58
|
-
"lint:fix": "eslint . --fix",
|
|
59
|
-
"format": "prettier --write src/"
|
|
60
60
|
}
|
|
61
|
-
}
|
|
61
|
+
}
|