@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.
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/dist/assets/images/article/arrow.svg +3 -0
- package/dist/assets/images/article/hot.svg +3 -0
- package/dist/assets/images/article/notice-info-icon.svg +5 -0
- package/dist/assets/images/article/notice-info.svg +3 -0
- package/dist/assets/images/article/notice-warning-icon.svg +5 -0
- package/dist/assets/images/article/notice-warning.svg +10 -0
- package/dist/assets/images/article/question.svg +10 -0
- package/dist/composables/useInlineEdit.d.ts +30 -0
- package/dist/composables/useInlineEdit.mjs +94 -0
- package/dist/composables/useLazyRender.d.ts +18 -0
- package/dist/composables/useLazyRender.mjs +33 -0
- package/dist/composables/useRegistry.d.ts +38 -0
- package/dist/composables/useRegistry.mjs +60 -0
- package/dist/composables/useSectionSEO.d.ts +26 -0
- package/dist/composables/useSectionSEO.mjs +122 -0
- package/dist/composables/useSectionStyle.d.ts +23 -0
- package/dist/composables/useSectionStyle.mjs +111 -0
- package/dist/editor.d.ts +4 -0
- package/dist/editor.mjs +9 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.mjs +14 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.mjs +14 -0
- package/dist/preview/createPreviewApp.d.ts +20 -0
- package/dist/preview/createPreviewApp.mjs +161 -0
- package/dist/renderer/FallbackSection.d.vue.ts +8 -0
- package/dist/renderer/FallbackSection.vue +17 -0
- package/dist/renderer/FallbackSection.vue.d.ts +8 -0
- package/dist/renderer/LazySection.d.vue.ts +60 -0
- package/dist/renderer/LazySection.vue +115 -0
- package/dist/renderer/LazySection.vue.d.ts +60 -0
- package/dist/renderer/SectionErrorBoundary.d.vue.ts +16 -0
- package/dist/renderer/SectionErrorBoundary.vue +38 -0
- package/dist/renderer/SectionErrorBoundary.vue.d.ts +16 -0
- package/dist/renderer/SectionRenderer.d.vue.ts +29 -0
- package/dist/renderer/SectionRenderer.vue +99 -0
- package/dist/renderer/SectionRenderer.vue.d.ts +29 -0
- package/dist/renderer/SectionWrapper.d.vue.ts +24 -0
- package/dist/renderer/SectionWrapper.vue +52 -0
- package/dist/renderer/SectionWrapper.vue.d.ts +24 -0
- package/dist/sections/RichTextSection.d.vue.ts +9 -0
- package/dist/sections/RichTextSection.vue +135 -0
- package/dist/sections/RichTextSection.vue.d.ts +9 -0
- package/dist/sections/article/index.d.ts +2 -0
- package/dist/sections/article/index.mjs +174 -0
- package/dist/sections/article/prosemirror.d.ts +2 -0
- package/dist/sections/article/prosemirror.mjs +65 -0
- package/dist/sections/article/shared/ArticleCustomHtml.d.vue.ts +9 -0
- package/dist/sections/article/shared/ArticleCustomHtml.vue +32 -0
- package/dist/sections/article/shared/ArticleCustomHtml.vue.d.ts +9 -0
- package/dist/sections/article/shared/ArticleImage.d.vue.ts +21 -0
- package/dist/sections/article/shared/ArticleImage.vue +53 -0
- package/dist/sections/article/shared/ArticleImage.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleBulletList.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleBulletList.vue +48 -0
- package/dist/sections/article/vigilkids/ArticleBulletList.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleCta.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleCta.vue +126 -0
- package/dist/sections/article/vigilkids/ArticleCta.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleFaq.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleFaq.vue +62 -0
- package/dist/sections/article/vigilkids/ArticleFaq.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleFaqItem.d.vue.ts +5 -0
- package/dist/sections/article/vigilkids/ArticleFaqItem.vue +24 -0
- package/dist/sections/article/vigilkids/ArticleFaqItem.vue.d.ts +5 -0
- package/dist/sections/article/vigilkids/ArticleFeature.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleFeature.vue +77 -0
- package/dist/sections/article/vigilkids/ArticleFeature.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleHeading.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleHeading.vue +53 -0
- package/dist/sections/article/vigilkids/ArticleHeading.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleNotice.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleNotice.vue +81 -0
- package/dist/sections/article/vigilkids/ArticleNotice.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleProsCons.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleProsCons.vue +74 -0
- package/dist/sections/article/vigilkids/ArticleProsCons.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleQuestion.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleQuestion.vue +58 -0
- package/dist/sections/article/vigilkids/ArticleQuestion.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleQuote.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleQuote.vue +50 -0
- package/dist/sections/article/vigilkids/ArticleQuote.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleStepList.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleStepList.vue +49 -0
- package/dist/sections/article/vigilkids/ArticleStepList.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleSubheading.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleSubheading.vue +56 -0
- package/dist/sections/article/vigilkids/ArticleSubheading.vue.d.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleTable.d.vue.ts +9 -0
- package/dist/sections/article/vigilkids/ArticleTable.vue +75 -0
- package/dist/sections/article/vigilkids/ArticleTable.vue.d.ts +9 -0
- package/dist/sections/article/vigilkids/ArticleToc.d.vue.ts +21 -0
- package/dist/sections/article/vigilkids/ArticleToc.vue +102 -0
- package/dist/sections/article/vigilkids/ArticleToc.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleBulletList.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleBulletList.vue +48 -0
- package/dist/sections/article/visiva/ArticleBulletList.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleCta.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleCta.vue +148 -0
- package/dist/sections/article/visiva/ArticleCta.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleFaq.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleFaq.vue +76 -0
- package/dist/sections/article/visiva/ArticleFaq.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleFeature.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleFeature.vue +79 -0
- package/dist/sections/article/visiva/ArticleFeature.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleHeading.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleHeading.vue +61 -0
- package/dist/sections/article/visiva/ArticleHeading.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleNotice.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleNotice.vue +102 -0
- package/dist/sections/article/visiva/ArticleNotice.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleProsCons.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleProsCons.vue +98 -0
- package/dist/sections/article/visiva/ArticleProsCons.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleQuestion.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleQuestion.vue +80 -0
- package/dist/sections/article/visiva/ArticleQuestion.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleQuote.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleQuote.vue +50 -0
- package/dist/sections/article/visiva/ArticleQuote.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleStepList.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleStepList.vue +48 -0
- package/dist/sections/article/visiva/ArticleStepList.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleSubheading.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleSubheading.vue +91 -0
- package/dist/sections/article/visiva/ArticleSubheading.vue.d.ts +21 -0
- package/dist/sections/article/visiva/ArticleTable.d.vue.ts +9 -0
- package/dist/sections/article/visiva/ArticleTable.vue +140 -0
- package/dist/sections/article/visiva/ArticleTable.vue.d.ts +9 -0
- package/dist/sections/article/visiva/ArticleToc.d.vue.ts +21 -0
- package/dist/sections/article/visiva/ArticleToc.vue +116 -0
- package/dist/sections/article/visiva/ArticleToc.vue.d.ts +21 -0
- package/dist/shims-css.d.ts +4 -0
- package/dist/styles/products/vigilkids.css +1 -0
- package/dist/styles/products/visiva.css +1 -0
- 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,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
|
+
}
|