@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.
@@ -1,2 +1,4 @@
1
1
  /** 将 ProseMirror JSON 或 HTML 字符串转为渲染 HTML */
2
2
  export declare function renderContent(content: unknown): string;
3
+ /** 将单段落 richtext 规范化为内联 HTML,避免嵌入 li/span 时额外生成块级 p */
4
+ export declare function renderInlineContent(content: unknown): string;
@@ -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 => ({ id, block: props.blocks[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, blockEditableAttrs } = useInlineEdit({
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
- <h2 v-if="s.title" class="article-recommend-title" v-bind="editableAttrs('title')">
46
- {{ s.title }}
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="(item.block.settings.url as string) || '#'"
67
+ :href="item.href"
53
68
  class="recommend-card"
54
69
  >
55
70
  <div class="recommend-card-image">
56
71
  <img
57
- :src="(item.block.settings.image_url as string) || ''"
58
- :alt="(item.block.settings.image_alt as string) || ''"
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 > 0" class="recommend-dots">
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="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
95
- <path d="M4 4L12 12M12 4L4 12" stroke="#000" stroke-width="1.5" stroke-linecap="round" />
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 => ({ id, block: props.blocks[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.block.settings.text" />
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('number')" v-html="s.number" />
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
- <span v-bind="editableAttrs('label_text')">{{ s.label_text }}</span>
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 => ({ id, block: props.blocks[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.block.settings.text" />
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.block.settings.text" />
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 => ({ id, block: props.blocks[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 class="top-banner">
43
- <div class="top-banner-info">
44
- <img
45
- v-if="s.header_image_url"
46
- class="top-banner-header"
47
- :src="(s.header_image_url as string)"
48
- :alt="(s.header_alt as string) || ''"
49
- >
50
- <!-- eslint-disable-next-line vue/no-v-html -->
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
- <li
60
- v-for="item in features"
61
- :key="item.id"
62
- v-bind="blockEditableAttrs(item.id, 'text')"
63
- v-html="item.block.settings.text"
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
- </ul>
66
- <a
67
- v-if="s.cta_primary_label"
68
- class="top-banner-btn-primary"
69
- :href="(s.cta_primary_url as string) || '#'"
70
- v-bind="editableAttrs('cta_primary_label')"
71
- >
72
- {{ s.cta_primary_label }}
73
- </a>
74
- <a
75
- v-if="s.cta_outline_label"
76
- class="top-banner-btn-outline"
77
- :href="(s.cta_outline_url as string) || '#'"
78
- v-bind="editableAttrs('cta_outline_label')"
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="article-btn-primary" v-bind="editableAttrs('cta_primary_label')" v-html="s.cta_primary_label" />
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="article-btn-outline" v-bind="editableAttrs('cta_outline_label')" v-html="s.cta_outline_label" />
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
- // blockOrder 顺序过滤 feature_item,保证管理端拖拽顺序与渲染一致
29
- const featureItems = computed(() =>
27
+ const featureDots = computed(() =>
30
28
  props.blockOrder
31
- .filter(id => props.blocks[id]?.type === 'feature_item')
32
- .map(id => ({ id, block: props.blocks[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="product-feature-list">
52
+ <ul class="feature-dot-list">
51
53
  <!-- eslint-disable-next-line vue/no-v-html -->
52
- <li v-for="item in featureItems" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.block.settings.text" />
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 => ({ id, block: props.blocks[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.block.settings.text" />
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 { editableAttrs } = useInlineEdit({
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
- <!-- eslint-disable-next-line vue/no-v-html -->
47
- <p class="product-desc" v-bind="editableAttrs('product_desc')" v-html="s.product_desc" />
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="article-btn-primary" v-bind="editableAttrs('cta_primary_label')" v-html="s.cta_primary_label" />
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="article-btn-outline" v-bind="editableAttrs('cta_outline_label')" v-html="s.cta_outline_label" />
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 => ({ id, block: props.blocks[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.block.settings.text" />
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.0",
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
- "vue": "^3.4.0",
45
- "@vigilkids/section-core": "0.1.1"
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
+ }