@vigilkids/section-renderer-vue 0.0.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.
Files changed (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/dist/assets/images/article/arrow.svg +3 -0
  4. package/dist/assets/images/article/hot.svg +3 -0
  5. package/dist/assets/images/article/notice-info-icon.svg +5 -0
  6. package/dist/assets/images/article/notice-info.svg +3 -0
  7. package/dist/assets/images/article/notice-warning-icon.svg +5 -0
  8. package/dist/assets/images/article/notice-warning.svg +10 -0
  9. package/dist/assets/images/article/question.svg +10 -0
  10. package/dist/composables/useInlineEdit.d.ts +30 -0
  11. package/dist/composables/useInlineEdit.mjs +94 -0
  12. package/dist/composables/useLazyRender.d.ts +18 -0
  13. package/dist/composables/useLazyRender.mjs +33 -0
  14. package/dist/composables/useRegistry.d.ts +38 -0
  15. package/dist/composables/useRegistry.mjs +60 -0
  16. package/dist/composables/useSectionSEO.d.ts +26 -0
  17. package/dist/composables/useSectionSEO.mjs +122 -0
  18. package/dist/composables/useSectionStyle.d.ts +23 -0
  19. package/dist/composables/useSectionStyle.mjs +111 -0
  20. package/dist/editor.d.ts +4 -0
  21. package/dist/editor.mjs +9 -0
  22. package/dist/index.d.ts +18 -0
  23. package/dist/index.mjs +14 -0
  24. package/dist/plugin.d.ts +6 -0
  25. package/dist/plugin.mjs +14 -0
  26. package/dist/preview/createPreviewApp.d.ts +20 -0
  27. package/dist/preview/createPreviewApp.mjs +161 -0
  28. package/dist/renderer/FallbackSection.d.vue.ts +8 -0
  29. package/dist/renderer/FallbackSection.vue +17 -0
  30. package/dist/renderer/FallbackSection.vue.d.ts +8 -0
  31. package/dist/renderer/LazySection.d.vue.ts +60 -0
  32. package/dist/renderer/LazySection.vue +115 -0
  33. package/dist/renderer/LazySection.vue.d.ts +60 -0
  34. package/dist/renderer/SectionErrorBoundary.d.vue.ts +16 -0
  35. package/dist/renderer/SectionErrorBoundary.vue +38 -0
  36. package/dist/renderer/SectionErrorBoundary.vue.d.ts +16 -0
  37. package/dist/renderer/SectionRenderer.d.vue.ts +29 -0
  38. package/dist/renderer/SectionRenderer.vue +99 -0
  39. package/dist/renderer/SectionRenderer.vue.d.ts +29 -0
  40. package/dist/renderer/SectionWrapper.d.vue.ts +24 -0
  41. package/dist/renderer/SectionWrapper.vue +52 -0
  42. package/dist/renderer/SectionWrapper.vue.d.ts +24 -0
  43. package/dist/sections/RichTextSection.d.vue.ts +9 -0
  44. package/dist/sections/RichTextSection.vue +135 -0
  45. package/dist/sections/RichTextSection.vue.d.ts +9 -0
  46. package/dist/sections/article/index.d.ts +2 -0
  47. package/dist/sections/article/index.mjs +174 -0
  48. package/dist/sections/article/prosemirror.d.ts +2 -0
  49. package/dist/sections/article/prosemirror.mjs +65 -0
  50. package/dist/sections/article/shared/ArticleCustomHtml.d.vue.ts +9 -0
  51. package/dist/sections/article/shared/ArticleCustomHtml.vue +32 -0
  52. package/dist/sections/article/shared/ArticleCustomHtml.vue.d.ts +9 -0
  53. package/dist/sections/article/shared/ArticleImage.d.vue.ts +21 -0
  54. package/dist/sections/article/shared/ArticleImage.vue +53 -0
  55. package/dist/sections/article/shared/ArticleImage.vue.d.ts +21 -0
  56. package/dist/sections/article/vigilkids/ArticleBulletList.d.vue.ts +21 -0
  57. package/dist/sections/article/vigilkids/ArticleBulletList.vue +48 -0
  58. package/dist/sections/article/vigilkids/ArticleBulletList.vue.d.ts +21 -0
  59. package/dist/sections/article/vigilkids/ArticleCta.d.vue.ts +21 -0
  60. package/dist/sections/article/vigilkids/ArticleCta.vue +126 -0
  61. package/dist/sections/article/vigilkids/ArticleCta.vue.d.ts +21 -0
  62. package/dist/sections/article/vigilkids/ArticleFaq.d.vue.ts +21 -0
  63. package/dist/sections/article/vigilkids/ArticleFaq.vue +62 -0
  64. package/dist/sections/article/vigilkids/ArticleFaq.vue.d.ts +21 -0
  65. package/dist/sections/article/vigilkids/ArticleFaqItem.d.vue.ts +5 -0
  66. package/dist/sections/article/vigilkids/ArticleFaqItem.vue +24 -0
  67. package/dist/sections/article/vigilkids/ArticleFaqItem.vue.d.ts +5 -0
  68. package/dist/sections/article/vigilkids/ArticleFeature.d.vue.ts +21 -0
  69. package/dist/sections/article/vigilkids/ArticleFeature.vue +77 -0
  70. package/dist/sections/article/vigilkids/ArticleFeature.vue.d.ts +21 -0
  71. package/dist/sections/article/vigilkids/ArticleHeading.d.vue.ts +21 -0
  72. package/dist/sections/article/vigilkids/ArticleHeading.vue +53 -0
  73. package/dist/sections/article/vigilkids/ArticleHeading.vue.d.ts +21 -0
  74. package/dist/sections/article/vigilkids/ArticleNotice.d.vue.ts +21 -0
  75. package/dist/sections/article/vigilkids/ArticleNotice.vue +81 -0
  76. package/dist/sections/article/vigilkids/ArticleNotice.vue.d.ts +21 -0
  77. package/dist/sections/article/vigilkids/ArticleProsCons.d.vue.ts +21 -0
  78. package/dist/sections/article/vigilkids/ArticleProsCons.vue +74 -0
  79. package/dist/sections/article/vigilkids/ArticleProsCons.vue.d.ts +21 -0
  80. package/dist/sections/article/vigilkids/ArticleQuestion.d.vue.ts +21 -0
  81. package/dist/sections/article/vigilkids/ArticleQuestion.vue +58 -0
  82. package/dist/sections/article/vigilkids/ArticleQuestion.vue.d.ts +21 -0
  83. package/dist/sections/article/vigilkids/ArticleQuote.d.vue.ts +21 -0
  84. package/dist/sections/article/vigilkids/ArticleQuote.vue +50 -0
  85. package/dist/sections/article/vigilkids/ArticleQuote.vue.d.ts +21 -0
  86. package/dist/sections/article/vigilkids/ArticleStepList.d.vue.ts +21 -0
  87. package/dist/sections/article/vigilkids/ArticleStepList.vue +49 -0
  88. package/dist/sections/article/vigilkids/ArticleStepList.vue.d.ts +21 -0
  89. package/dist/sections/article/vigilkids/ArticleSubheading.d.vue.ts +21 -0
  90. package/dist/sections/article/vigilkids/ArticleSubheading.vue +56 -0
  91. package/dist/sections/article/vigilkids/ArticleSubheading.vue.d.ts +21 -0
  92. package/dist/sections/article/vigilkids/ArticleTable.d.vue.ts +9 -0
  93. package/dist/sections/article/vigilkids/ArticleTable.vue +75 -0
  94. package/dist/sections/article/vigilkids/ArticleTable.vue.d.ts +9 -0
  95. package/dist/sections/article/vigilkids/ArticleToc.d.vue.ts +21 -0
  96. package/dist/sections/article/vigilkids/ArticleToc.vue +102 -0
  97. package/dist/sections/article/vigilkids/ArticleToc.vue.d.ts +21 -0
  98. package/dist/sections/article/visiva/ArticleBulletList.d.vue.ts +21 -0
  99. package/dist/sections/article/visiva/ArticleBulletList.vue +48 -0
  100. package/dist/sections/article/visiva/ArticleBulletList.vue.d.ts +21 -0
  101. package/dist/sections/article/visiva/ArticleCta.d.vue.ts +21 -0
  102. package/dist/sections/article/visiva/ArticleCta.vue +148 -0
  103. package/dist/sections/article/visiva/ArticleCta.vue.d.ts +21 -0
  104. package/dist/sections/article/visiva/ArticleFaq.d.vue.ts +21 -0
  105. package/dist/sections/article/visiva/ArticleFaq.vue +76 -0
  106. package/dist/sections/article/visiva/ArticleFaq.vue.d.ts +21 -0
  107. package/dist/sections/article/visiva/ArticleFeature.d.vue.ts +21 -0
  108. package/dist/sections/article/visiva/ArticleFeature.vue +79 -0
  109. package/dist/sections/article/visiva/ArticleFeature.vue.d.ts +21 -0
  110. package/dist/sections/article/visiva/ArticleHeading.d.vue.ts +21 -0
  111. package/dist/sections/article/visiva/ArticleHeading.vue +61 -0
  112. package/dist/sections/article/visiva/ArticleHeading.vue.d.ts +21 -0
  113. package/dist/sections/article/visiva/ArticleNotice.d.vue.ts +21 -0
  114. package/dist/sections/article/visiva/ArticleNotice.vue +102 -0
  115. package/dist/sections/article/visiva/ArticleNotice.vue.d.ts +21 -0
  116. package/dist/sections/article/visiva/ArticleProsCons.d.vue.ts +21 -0
  117. package/dist/sections/article/visiva/ArticleProsCons.vue +98 -0
  118. package/dist/sections/article/visiva/ArticleProsCons.vue.d.ts +21 -0
  119. package/dist/sections/article/visiva/ArticleQuestion.d.vue.ts +21 -0
  120. package/dist/sections/article/visiva/ArticleQuestion.vue +80 -0
  121. package/dist/sections/article/visiva/ArticleQuestion.vue.d.ts +21 -0
  122. package/dist/sections/article/visiva/ArticleQuote.d.vue.ts +21 -0
  123. package/dist/sections/article/visiva/ArticleQuote.vue +50 -0
  124. package/dist/sections/article/visiva/ArticleQuote.vue.d.ts +21 -0
  125. package/dist/sections/article/visiva/ArticleStepList.d.vue.ts +21 -0
  126. package/dist/sections/article/visiva/ArticleStepList.vue +48 -0
  127. package/dist/sections/article/visiva/ArticleStepList.vue.d.ts +21 -0
  128. package/dist/sections/article/visiva/ArticleSubheading.d.vue.ts +21 -0
  129. package/dist/sections/article/visiva/ArticleSubheading.vue +91 -0
  130. package/dist/sections/article/visiva/ArticleSubheading.vue.d.ts +21 -0
  131. package/dist/sections/article/visiva/ArticleTable.d.vue.ts +9 -0
  132. package/dist/sections/article/visiva/ArticleTable.vue +140 -0
  133. package/dist/sections/article/visiva/ArticleTable.vue.d.ts +9 -0
  134. package/dist/sections/article/visiva/ArticleToc.d.vue.ts +21 -0
  135. package/dist/sections/article/visiva/ArticleToc.vue +116 -0
  136. package/dist/sections/article/visiva/ArticleToc.vue.d.ts +21 -0
  137. package/dist/shims-css.d.ts +4 -0
  138. package/dist/styles/products/vigilkids.css +1 -0
  139. package/dist/styles/products/visiva.css +1 -0
  140. package/package.json +43 -0
@@ -0,0 +1,38 @@
1
+ <script setup lang="ts">
2
+ import { onErrorCaptured, ref } from 'vue'
3
+
4
+ const props = defineProps<{
5
+ sectionId: string
6
+ sectionType: string
7
+ }>()
8
+
9
+ const error = ref<Error | null>(null)
10
+
11
+ onErrorCaptured((err) => {
12
+ error.value = err as Error
13
+ return false
14
+ })
15
+ </script>
16
+
17
+ <template>
18
+ <div v-if="error" class="rounded-lg border border-red-200 bg-red-50 p-6 text-red-700">
19
+ <div class="mb-2 flex items-center gap-2">
20
+ <span class="text-lg">⚠</span>
21
+ <span class="font-medium">组件 "{{ sectionType }}" 无法显示</span>
22
+ </div>
23
+ <p class="mb-3 text-sm text-red-600">
24
+ 可能原因:组件加载失败或数据格式异常。此错误不影响其他组件。
25
+ </p>
26
+ <details class="mb-3">
27
+ <summary class="cursor-pointer text-xs text-red-500">技术详情</summary>
28
+ <code class="mt-1 block whitespace-pre-wrap text-xs">{{ error.message }}</code>
29
+ </details>
30
+ <button
31
+ class="rounded bg-red-100 px-3 py-1 text-sm text-red-700 hover:bg-red-200"
32
+ @click="error = null"
33
+ >
34
+ 重新加载
35
+ </button>
36
+ </div>
37
+ <slot v-else />
38
+ </template>
@@ -0,0 +1,16 @@
1
+ type __VLS_Props = {
2
+ sectionId: string;
3
+ sectionType: string;
4
+ };
5
+ declare var __VLS_1: {};
6
+ type __VLS_Slots = {} & {
7
+ default?: (props: typeof __VLS_1) => any;
8
+ };
9
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
11
+ export default _default;
12
+ type __VLS_WithSlots<T, S> = T & {
13
+ new (): {
14
+ $slots: S;
15
+ };
16
+ };
@@ -0,0 +1,29 @@
1
+ import type { SectionsData } from '@vigilkids/section-core';
2
+ type __VLS_Props = {
3
+ /** 是否处于编辑器预览模式(启用 data-* 属性和高亮) */
4
+ editorMode?: boolean;
5
+ /** 当前选中的 Section ID(编辑器模式下高亮) */
6
+ selectedSectionId?: string | null;
7
+ /** 是否全选所有 Section(编辑器 Ctrl+A) */
8
+ allSectionsSelected?: boolean;
9
+ /** Sections 数据(与 content_json 结构一致) */
10
+ sectionsData: SectionsData;
11
+ /** 产品标识,用于解析产品级 Section 组件覆盖 */
12
+ productCode?: string;
13
+ };
14
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
15
+ "inline-edit-start": (sectionId: string, key: string) => any;
16
+ "inline-edit-end": () => any;
17
+ "section-click": (sectionId: string) => any;
18
+ "setting-update": (sectionId: string, key: string, value: unknown) => any;
19
+ "block-setting-update": (sectionId: string, blockId: string, key: string, value: unknown) => any;
20
+ "inline-edit-undo-redo": (action: "redo" | "undo") => any;
21
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
22
+ "onInline-edit-start"?: ((sectionId: string, key: string) => any) | undefined;
23
+ "onInline-edit-end"?: (() => any) | undefined;
24
+ "onSection-click"?: ((sectionId: string) => any) | undefined;
25
+ "onSetting-update"?: ((sectionId: string, key: string, value: unknown) => any) | undefined;
26
+ "onBlock-setting-update"?: ((sectionId: string, blockId: string, key: string, value: unknown) => any) | undefined;
27
+ "onInline-edit-undo-redo"?: ((action: "redo" | "undo") => any) | undefined;
28
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
29
+ export default _default;
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ import type { SectionsData } from '@vigilkids/section-core'
3
+
4
+ import { computed } from 'vue'
5
+
6
+ import FallbackSection from './FallbackSection.vue'
7
+ import LazySection from './LazySection.vue'
8
+ import SectionErrorBoundary from './SectionErrorBoundary.vue'
9
+ import SectionWrapper from './SectionWrapper.vue'
10
+ import { useRegistry } from '../composables/useRegistry'
11
+
12
+ const props = defineProps<{
13
+ /** 是否处于编辑器预览模式(启用 data-* 属性和高亮) */
14
+ editorMode?: boolean
15
+ /** 当前选中的 Section ID(编辑器模式下高亮) */
16
+ selectedSectionId?: string | null
17
+ /** 是否全选所有 Section(编辑器 Ctrl+A) */
18
+ allSectionsSelected?: boolean
19
+ /** Sections 数据(与 content_json 结构一致) */
20
+ sectionsData: SectionsData
21
+ /** 产品标识,用于解析产品级 Section 组件覆盖 */
22
+ productCode?: string
23
+ }>()
24
+
25
+ const emit = defineEmits<{
26
+ (e: 'section-click', sectionId: string): void
27
+ (e: 'setting-update', sectionId: string, key: string, value: unknown): void
28
+ (e: 'block-setting-update', sectionId: string, blockId: string, key: string, value: unknown): void
29
+ (e: 'inline-edit-start', sectionId: string, key: string): void
30
+ (e: 'inline-edit-end'): void
31
+ (e: 'inline-edit-undo-redo', action: 'redo' | 'undo'): void
32
+ }>()
33
+
34
+ const registry = useRegistry()
35
+
36
+ const orderedSections = computed(() =>
37
+ props.sectionsData.section_order
38
+ .filter(id => id in props.sectionsData.sections)
39
+ .map(id => ({
40
+ id,
41
+ section: props.sectionsData.sections[id]!,
42
+ }))
43
+ .filter(item => !item.section.disabled),
44
+ )
45
+ </script>
46
+
47
+ <template>
48
+ <main class="section-renderer">
49
+ <template v-for="(item, index) in orderedSections" :key="item.id">
50
+ <!-- 前 2 个 Section 直接渲染,保障 LCP/FCP -->
51
+ <SectionErrorBoundary
52
+ v-if="index < 2"
53
+ :section-id="item.id"
54
+ :section-type="item.section.type"
55
+ >
56
+ <SectionWrapper
57
+ :section-id="item.id"
58
+ :section-type="item.section.type"
59
+ :editor-mode="editorMode"
60
+ :is-selected="allSectionsSelected || selectedSectionId === item.id"
61
+ :settings="item.section.settings"
62
+ @click="emit('section-click', item.id)"
63
+ >
64
+ <component
65
+ :is="registry.resolve(item.section.type, productCode) ?? FallbackSection"
66
+ :settings="item.section.settings"
67
+ :blocks="item.section.blocks"
68
+ :block-order="item.section.block_order"
69
+ :editor-mode="editorMode"
70
+ @update:setting="(key: string, value: unknown) => emit('setting-update', item.id, key, value)"
71
+ @update:block-setting="(blockId: string, key: string, value: unknown) => emit('block-setting-update', item.id, blockId, key, value)"
72
+ @inline-edit-start="(key: string) => emit('inline-edit-start', item.id, key)"
73
+ @inline-edit-end="emit('inline-edit-end')"
74
+ @undo-redo="(action: 'redo' | 'undo') => emit('inline-edit-undo-redo', action)"
75
+ />
76
+ </SectionWrapper>
77
+ </SectionErrorBoundary>
78
+
79
+ <!-- 剩余 Section 通过 IntersectionObserver 懒加载 -->
80
+ <LazySection
81
+ v-else
82
+ :section-id="item.id"
83
+ :section-type="item.section.type"
84
+ :settings="item.section.settings"
85
+ :blocks="item.section.blocks"
86
+ :block-order="item.section.block_order"
87
+ :editor-mode="editorMode"
88
+ :is-selected="allSectionsSelected || selectedSectionId === item.id"
89
+ :product-code="productCode"
90
+ @section-click="emit('section-click', $event)"
91
+ @setting-update="(sectionId: string, key: string, value: unknown) => emit('setting-update', sectionId, key, value)"
92
+ @block-setting-update="(sectionId: string, blockId: string, key: string, value: unknown) => emit('block-setting-update', sectionId, blockId, key, value)"
93
+ @inline-edit-start="(sectionId: string, key: string) => emit('inline-edit-start', sectionId, key)"
94
+ @inline-edit-end="emit('inline-edit-end')"
95
+ @inline-edit-undo-redo="(action: 'redo' | 'undo') => emit('inline-edit-undo-redo', action)"
96
+ />
97
+ </template>
98
+ </main>
99
+ </template>
@@ -0,0 +1,29 @@
1
+ import type { SectionsData } from '@vigilkids/section-core';
2
+ type __VLS_Props = {
3
+ /** 是否处于编辑器预览模式(启用 data-* 属性和高亮) */
4
+ editorMode?: boolean;
5
+ /** 当前选中的 Section ID(编辑器模式下高亮) */
6
+ selectedSectionId?: string | null;
7
+ /** 是否全选所有 Section(编辑器 Ctrl+A) */
8
+ allSectionsSelected?: boolean;
9
+ /** Sections 数据(与 content_json 结构一致) */
10
+ sectionsData: SectionsData;
11
+ /** 产品标识,用于解析产品级 Section 组件覆盖 */
12
+ productCode?: string;
13
+ };
14
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
15
+ "inline-edit-start": (sectionId: string, key: string) => any;
16
+ "inline-edit-end": () => any;
17
+ "section-click": (sectionId: string) => any;
18
+ "setting-update": (sectionId: string, key: string, value: unknown) => any;
19
+ "block-setting-update": (sectionId: string, blockId: string, key: string, value: unknown) => any;
20
+ "inline-edit-undo-redo": (action: "redo" | "undo") => any;
21
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
22
+ "onInline-edit-start"?: ((sectionId: string, key: string) => any) | undefined;
23
+ "onInline-edit-end"?: (() => any) | undefined;
24
+ "onSection-click"?: ((sectionId: string) => any) | undefined;
25
+ "onSetting-update"?: ((sectionId: string, key: string, value: unknown) => any) | undefined;
26
+ "onBlock-setting-update"?: ((sectionId: string, blockId: string, key: string, value: unknown) => any) | undefined;
27
+ "onInline-edit-undo-redo"?: ((action: "redo" | "undo") => any) | undefined;
28
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
29
+ export default _default;
@@ -0,0 +1,24 @@
1
+ type __VLS_Props = {
2
+ sectionId: string;
3
+ sectionType: string;
4
+ editorMode?: boolean;
5
+ isSelected?: boolean;
6
+ /** Section settings 值(用于样式应用) */
7
+ settings?: Record<string, unknown>;
8
+ };
9
+ declare var __VLS_1: {};
10
+ type __VLS_Slots = {} & {
11
+ default?: (props: typeof __VLS_1) => any;
12
+ };
13
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
14
+ click: () => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ onClick?: (() => any) | undefined;
17
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };
@@ -0,0 +1,52 @@
1
+ <script setup lang="ts">
2
+ import { useSectionStyle } from '../composables/useSectionStyle'
3
+
4
+ const props = defineProps<{
5
+ sectionId: string
6
+ sectionType: string
7
+ editorMode?: boolean
8
+ isSelected?: boolean
9
+ /** Section settings 值(用于样式应用) */
10
+ settings?: Record<string, unknown>
11
+ }>()
12
+
13
+ defineEmits<{
14
+ (e: 'click'): void
15
+ }>()
16
+
17
+ const { style } = useSectionStyle(
18
+ () => props.sectionId,
19
+ () => props.settings ?? {},
20
+ )
21
+ </script>
22
+
23
+ <template>
24
+ <section
25
+ :id="`section-${sectionId}`"
26
+ :data-section-id="sectionId"
27
+ :data-section-type="sectionType"
28
+ class="section-wrapper"
29
+ :class="[
30
+ {
31
+ 'section-wrapper--selected': isSelected,
32
+ 'section-wrapper--hoverable': editorMode,
33
+ },
34
+ style.wrapperClasses,
35
+ style.customClass,
36
+ ]"
37
+ :style="style.wrapperStyles"
38
+ @click.stop="$emit('click')"
39
+ >
40
+ <slot />
41
+ </section>
42
+
43
+ <!-- Scoped CSS 注入(自定义间距 + custom_css) -->
44
+ <component
45
+ v-if="style.scopedCSS"
46
+ :is="'style'"
47
+ >{{ style.scopedCSS }}</component>
48
+ </template>
49
+
50
+ <style scoped>
51
+ .section-wrapper{position:relative}.section-wrapper--hoverable:hover{outline:2px solid rgba(22,119,255,.3);outline-offset:-2px}.section-wrapper--selected{outline:2px solid #1677ff;outline-offset:-2px}.section-wrapper--hoverable:hover:before{background:#1677ff;border-radius:4px;color:#fff;content:attr(data-section-type);font-size:12px;left:8px;padding:2px 8px;position:absolute;top:-24px;z-index:10}.section-wrapper :deep(.inline-editable){border-radius:2px;cursor:text;transition:outline .15s ease}.section-wrapper :deep(.inline-editable:hover){outline:1px dashed rgba(22,119,255,.4);outline-offset:2px}.section-wrapper :deep(.inline-editing){border-radius:2px;cursor:text;min-height:1em;min-width:20px;outline:2px solid #1677ff;outline-offset:2px}
52
+ </style>
@@ -0,0 +1,24 @@
1
+ type __VLS_Props = {
2
+ sectionId: string;
3
+ sectionType: string;
4
+ editorMode?: boolean;
5
+ isSelected?: boolean;
6
+ /** Section settings 值(用于样式应用) */
7
+ settings?: Record<string, unknown>;
8
+ };
9
+ declare var __VLS_1: {};
10
+ type __VLS_Slots = {} & {
11
+ default?: (props: typeof __VLS_1) => any;
12
+ };
13
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
14
+ click: () => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ onClick?: (() => any) | undefined;
17
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };
@@ -0,0 +1,9 @@
1
+ import type { BlockData } from '@vigilkids/section-core';
2
+ type __VLS_Props = {
3
+ blockOrder: string[];
4
+ blocks: Record<string, BlockData>;
5
+ editorMode?: boolean;
6
+ settings: Record<string, unknown>;
7
+ };
8
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ export default _default;
@@ -0,0 +1,135 @@
1
+ <script setup lang="ts">
2
+ import type { BlockData } from '@vigilkids/section-core'
3
+
4
+ import { computed } from 'vue'
5
+
6
+ import { safeColor } from '@vigilkids/section-core'
7
+
8
+ const props = defineProps<{
9
+ blockOrder: string[]
10
+ blocks: Record<string, BlockData>
11
+ editorMode?: boolean
12
+ settings: Record<string, unknown>
13
+ }>()
14
+
15
+ const s = computed(() => props.settings)
16
+
17
+ const maxWidthClass = computed(() => {
18
+ const map: Record<string, string> = {
19
+ full: 'max-w-none',
20
+ prose: 'max-w-prose',
21
+ wide: 'max-w-screen-lg',
22
+ }
23
+ return map[String(s.value.max_width ?? 'prose')] ?? 'max-w-prose'
24
+ })
25
+
26
+ const paddingClass = computed(() => {
27
+ const map: Record<string, string> = {
28
+ compact: 'py-8',
29
+ none: 'py-0',
30
+ standard: 'py-16',
31
+ }
32
+ return map[String(s.value.padding ?? 'none')] ?? 'py-0'
33
+ })
34
+
35
+ /** 将 ProseMirror JSON 或 HTML 字符串转为渲染 HTML */
36
+ const renderedContent = computed(() => {
37
+ const content = s.value.content
38
+ if (typeof content === 'string') return content
39
+ if (content && typeof content === 'object') {
40
+ return renderProseMirrorBasic(content as ProseMirrorDoc)
41
+ }
42
+ return ''
43
+ })
44
+
45
+ /** ProseMirror 节点类型 */
46
+ interface ProseMirrorNode {
47
+ attrs?: Record<string, unknown>
48
+ content?: ProseMirrorNode[]
49
+ marks?: Array<{ attrs?: Record<string, unknown>; type: string }>
50
+ text?: string
51
+ type: string
52
+ }
53
+
54
+ /** ProseMirror 文档根节点 */
55
+ interface ProseMirrorDoc {
56
+ content?: ProseMirrorNode[]
57
+ type: 'doc'
58
+ }
59
+
60
+ /** 基础 ProseMirror JSON → HTML 转换 */
61
+ function renderProseMirrorBasic(doc: ProseMirrorDoc): string {
62
+ if (!doc.content) return ''
63
+ return doc.content.map(renderNode).join('')
64
+ }
65
+
66
+ function renderNode(node: ProseMirrorNode): string {
67
+ switch (node.type) {
68
+ case 'blockquote':
69
+ return `<blockquote>${renderChildren(node)}</blockquote>`
70
+ case 'bulletList':
71
+ return `<ul>${renderChildren(node)}</ul>`
72
+ case 'codeBlock':
73
+ return `<pre><code>${renderChildren(node)}</code></pre>`
74
+ case 'hardBreak':
75
+ return '<br />'
76
+ case 'heading': {
77
+ const level = (node.attrs?.level as number) ?? 2
78
+ return `<h${level}>${renderChildren(node)}</h${level}>`
79
+ }
80
+ case 'horizontalRule':
81
+ return '<hr />'
82
+ case 'listItem':
83
+ return `<li>${renderChildren(node)}</li>`
84
+ case 'orderedList':
85
+ return `<ol>${renderChildren(node)}</ol>`
86
+ case 'paragraph':
87
+ return `<p>${renderChildren(node)}</p>`
88
+ case 'text':
89
+ return applyMarks(node.text ?? '', node.marks)
90
+ default:
91
+ return renderChildren(node)
92
+ }
93
+ }
94
+
95
+ function renderChildren(node: ProseMirrorNode): string {
96
+ if (!node.content) return node.text ?? ''
97
+ return node.content.map(renderNode).join('')
98
+ }
99
+
100
+ function applyMarks(text: string, marks?: ProseMirrorNode['marks']): string {
101
+ if (!marks) return text
102
+ let result = text
103
+ for (const mark of marks) {
104
+ switch (mark.type) {
105
+ case 'bold':
106
+ result = `<strong>${result}</strong>`
107
+ break
108
+ case 'code':
109
+ result = `<code>${result}</code>`
110
+ break
111
+ case 'italic':
112
+ result = `<em>${result}</em>`
113
+ break
114
+ case 'link':
115
+ result = `<a href="${mark.attrs?.href ?? '#'}">${result}</a>`
116
+ break
117
+ }
118
+ }
119
+ return result
120
+ }
121
+ </script>
122
+
123
+ <template>
124
+ <article
125
+ class="px-6"
126
+ :class="paddingClass"
127
+ :style="{ backgroundColor: safeColor(String(s.bg_color ?? '#ffffff')) }"
128
+ >
129
+ <div
130
+ class="prose prose-lg mx-auto"
131
+ :class="maxWidthClass"
132
+ v-html="renderedContent"
133
+ />
134
+ </article>
135
+ </template>
@@ -0,0 +1,9 @@
1
+ import type { BlockData } from '@vigilkids/section-core';
2
+ type __VLS_Props = {
3
+ blockOrder: string[];
4
+ blocks: Record<string, BlockData>;
5
+ editorMode?: boolean;
6
+ settings: Record<string, unknown>;
7
+ };
8
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ export default _default;
@@ -0,0 +1,2 @@
1
+ /** 注册所有文章类 Section 组件(article-*),按产品隔离 */
2
+ export declare function registerArticleSections(): void;
@@ -0,0 +1,174 @@
1
+ import { registerSection } from "../../composables/useRegistry.mjs";
2
+ export function registerArticleSections() {
3
+ registerSection({
4
+ name: "article-image",
5
+ componentName: "ArticleImage",
6
+ component: () => import("./shared/ArticleImage.vue")
7
+ });
8
+ registerSection({
9
+ name: "article-custom-html",
10
+ componentName: "ArticleCustomHtml",
11
+ component: () => import("./shared/ArticleCustomHtml.vue")
12
+ });
13
+ registerSection({
14
+ name: "article-prose",
15
+ componentName: "ArticleCustomHtml",
16
+ component: () => import("./shared/ArticleCustomHtml.vue")
17
+ });
18
+ registerSection({
19
+ name: "article-heading",
20
+ productCode: "vigilkids",
21
+ componentName: "ArticleHeading",
22
+ component: () => import("./vigilkids/ArticleHeading.vue")
23
+ });
24
+ registerSection({
25
+ name: "article-subheading",
26
+ productCode: "vigilkids",
27
+ componentName: "ArticleSubheading",
28
+ component: () => import("./vigilkids/ArticleSubheading.vue")
29
+ });
30
+ registerSection({
31
+ name: "article-bullet-list",
32
+ productCode: "vigilkids",
33
+ componentName: "ArticleBulletList",
34
+ component: () => import("./vigilkids/ArticleBulletList.vue")
35
+ });
36
+ registerSection({
37
+ name: "article-step-list",
38
+ productCode: "vigilkids",
39
+ componentName: "ArticleStepList",
40
+ component: () => import("./vigilkids/ArticleStepList.vue")
41
+ });
42
+ registerSection({
43
+ name: "article-notice",
44
+ productCode: "vigilkids",
45
+ componentName: "ArticleNotice",
46
+ component: () => import("./vigilkids/ArticleNotice.vue")
47
+ });
48
+ registerSection({
49
+ name: "article-quote",
50
+ productCode: "vigilkids",
51
+ componentName: "ArticleQuote",
52
+ component: () => import("./vigilkids/ArticleQuote.vue")
53
+ });
54
+ registerSection({
55
+ name: "article-question",
56
+ productCode: "vigilkids",
57
+ componentName: "ArticleQuestion",
58
+ component: () => import("./vigilkids/ArticleQuestion.vue")
59
+ });
60
+ registerSection({
61
+ name: "article-pros-cons",
62
+ productCode: "vigilkids",
63
+ componentName: "ArticleProsCons",
64
+ component: () => import("./vigilkids/ArticleProsCons.vue")
65
+ });
66
+ registerSection({
67
+ name: "article-feature",
68
+ productCode: "vigilkids",
69
+ componentName: "ArticleFeature",
70
+ component: () => import("./vigilkids/ArticleFeature.vue")
71
+ });
72
+ registerSection({
73
+ name: "article-cta",
74
+ productCode: "vigilkids",
75
+ componentName: "ArticleCta",
76
+ component: () => import("./vigilkids/ArticleCta.vue")
77
+ });
78
+ registerSection({
79
+ name: "article-faq",
80
+ productCode: "vigilkids",
81
+ componentName: "ArticleFaq",
82
+ component: () => import("./vigilkids/ArticleFaq.vue")
83
+ });
84
+ registerSection({
85
+ name: "article-table",
86
+ productCode: "vigilkids",
87
+ componentName: "ArticleTable",
88
+ component: () => import("./vigilkids/ArticleTable.vue")
89
+ });
90
+ registerSection({
91
+ name: "article-toc",
92
+ productCode: "vigilkids",
93
+ componentName: "ArticleToc",
94
+ component: () => import("./vigilkids/ArticleToc.vue")
95
+ });
96
+ registerSection({
97
+ name: "article-heading",
98
+ productCode: "visiva",
99
+ componentName: "ArticleHeading",
100
+ component: () => import("./visiva/ArticleHeading.vue")
101
+ });
102
+ registerSection({
103
+ name: "article-subheading",
104
+ productCode: "visiva",
105
+ componentName: "ArticleSubheading",
106
+ component: () => import("./visiva/ArticleSubheading.vue")
107
+ });
108
+ registerSection({
109
+ name: "article-bullet-list",
110
+ productCode: "visiva",
111
+ componentName: "ArticleBulletList",
112
+ component: () => import("./visiva/ArticleBulletList.vue")
113
+ });
114
+ registerSection({
115
+ name: "article-step-list",
116
+ productCode: "visiva",
117
+ componentName: "ArticleStepList",
118
+ component: () => import("./visiva/ArticleStepList.vue")
119
+ });
120
+ registerSection({
121
+ name: "article-notice",
122
+ productCode: "visiva",
123
+ componentName: "ArticleNotice",
124
+ component: () => import("./visiva/ArticleNotice.vue")
125
+ });
126
+ registerSection({
127
+ name: "article-quote",
128
+ productCode: "visiva",
129
+ componentName: "ArticleQuote",
130
+ component: () => import("./visiva/ArticleQuote.vue")
131
+ });
132
+ registerSection({
133
+ name: "article-question",
134
+ productCode: "visiva",
135
+ componentName: "ArticleQuestion",
136
+ component: () => import("./visiva/ArticleQuestion.vue")
137
+ });
138
+ registerSection({
139
+ name: "article-pros-cons",
140
+ productCode: "visiva",
141
+ componentName: "ArticleProsCons",
142
+ component: () => import("./visiva/ArticleProsCons.vue")
143
+ });
144
+ registerSection({
145
+ name: "article-feature",
146
+ productCode: "visiva",
147
+ componentName: "ArticleFeature",
148
+ component: () => import("./visiva/ArticleFeature.vue")
149
+ });
150
+ registerSection({
151
+ name: "article-cta",
152
+ productCode: "visiva",
153
+ componentName: "ArticleCta",
154
+ component: () => import("./visiva/ArticleCta.vue")
155
+ });
156
+ registerSection({
157
+ name: "article-faq",
158
+ productCode: "visiva",
159
+ componentName: "ArticleFaq",
160
+ component: () => import("./visiva/ArticleFaq.vue")
161
+ });
162
+ registerSection({
163
+ name: "article-table",
164
+ productCode: "visiva",
165
+ componentName: "ArticleTable",
166
+ component: () => import("./visiva/ArticleTable.vue")
167
+ });
168
+ registerSection({
169
+ name: "article-toc",
170
+ productCode: "visiva",
171
+ componentName: "ArticleToc",
172
+ component: () => import("./visiva/ArticleToc.vue")
173
+ });
174
+ }
@@ -0,0 +1,2 @@
1
+ /** 将 ProseMirror JSON 或 HTML 字符串转为渲染 HTML */
2
+ export declare function renderContent(content: unknown): string;