@vigilkids/section-renderer-vue 0.1.0 → 0.2.0

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 (33) hide show
  1. package/dist/interactions/common.d.ts +5 -0
  2. package/dist/interactions/common.mjs +10 -0
  3. package/dist/interactions/vigilkids.d.ts +5 -0
  4. package/dist/interactions/vigilkids.mjs +26 -0
  5. package/dist/preview/createPreviewApp.mjs +23 -3
  6. package/dist/sections/article/shared/ArticleCustomHtml.vue +48 -2
  7. package/dist/sections/article/vigilkids/ArticleBulletList.vue +3 -8
  8. package/dist/sections/article/vigilkids/ArticleCta.vue +1 -42
  9. package/dist/sections/article/vigilkids/ArticleFaq.vue +2 -32
  10. package/dist/sections/article/vigilkids/ArticleFaqItem.vue +8 -8
  11. package/dist/sections/article/vigilkids/ArticleFeature.vue +3 -41
  12. package/dist/sections/article/vigilkids/ArticleHeading.vue +2 -11
  13. package/dist/sections/article/vigilkids/ArticleNotice.vue +5 -64
  14. package/dist/sections/article/vigilkids/ArticleProsCons.vue +5 -13
  15. package/dist/sections/article/vigilkids/ArticleQuestion.vue +5 -23
  16. package/dist/sections/article/vigilkids/ArticleQuote.vue +5 -9
  17. package/dist/sections/article/vigilkids/ArticleStepList.vue +1 -5
  18. package/dist/sections/article/vigilkids/ArticleSubheading.vue +2 -23
  19. package/dist/sections/article/vigilkids/ArticleTable.vue +2 -6
  20. package/dist/sections/article/vigilkids/ArticleToc.vue +2 -6
  21. package/dist/sections/article/visiva/ArticleBulletList.vue +2 -3
  22. package/dist/sections/article/visiva/ArticleFaq.vue +2 -3
  23. package/dist/sections/article/visiva/ArticleFeature.vue +2 -3
  24. package/dist/sections/article/visiva/ArticleNotice.vue +12 -12
  25. package/dist/sections/article/visiva/ArticleProsCons.vue +4 -6
  26. package/dist/sections/article/visiva/ArticleQuestion.vue +3 -4
  27. package/dist/sections/article/visiva/ArticleQuote.vue +1 -1
  28. package/dist/styles/products/vigilkids.css +1 -1
  29. package/dist/styles/products/visiva.css +1 -1
  30. package/dist/styles/utilities.css +1 -0
  31. package/dist/utils/content-variables.d.ts +6 -0
  32. package/dist/utils/content-variables.mjs +14 -0
  33. package/package.json +9 -1
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 通用 DOM 交互 — 所有产品共享
3
+ * 使用事件委托,覆盖 innerHTML 直出内容的基础交互
4
+ */
5
+ export declare function setupCommonInteractions(): void;
@@ -0,0 +1,10 @@
1
+ export function setupCommonInteractions() {
2
+ document.addEventListener("click", (e) => {
3
+ const anchor = e.target.closest?.("a");
4
+ if (!anchor)
5
+ return;
6
+ const href = anchor.getAttribute("href");
7
+ if (!href?.startsWith("#"))
8
+ e.preventDefault();
9
+ });
10
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * vigilkids 产品专属 DOM 交互
3
+ * 使用事件委托,覆盖 innerHTML 直出内容的产品特有交互
4
+ */
5
+ export declare function setupInteractions(): void;
@@ -0,0 +1,26 @@
1
+ export function setupInteractions() {
2
+ document.addEventListener("click", (e) => {
3
+ const faqTitle = e.target.closest?.(".faq-item-title");
4
+ if (!faqTitle)
5
+ return;
6
+ const faqItem = faqTitle.closest(".faq-item");
7
+ if (!faqItem)
8
+ return;
9
+ const faqList = faqItem.closest(".faq-list");
10
+ const isActive = faqItem.classList.contains("active");
11
+ if (faqList) {
12
+ faqList.querySelectorAll(".faq-item.active").forEach((item) => {
13
+ if (item !== faqItem) {
14
+ item.classList.remove("active");
15
+ const body2 = item.querySelector(".faq-item-answer");
16
+ if (body2)
17
+ body2.style.maxHeight = "0";
18
+ }
19
+ });
20
+ }
21
+ faqItem.classList.toggle("active", !isActive);
22
+ const body = faqItem.querySelector(".faq-item-answer");
23
+ if (body)
24
+ body.style.maxHeight = isActive ? "0" : `${body.scrollHeight}px`;
25
+ });
26
+ }
@@ -1,5 +1,6 @@
1
1
  import { detectUndoRedo, isEditableTarget, isModKey } from "@vigilkids/section-core";
2
2
  import { createApp, h, provide, ref } from "vue";
3
+ import { resolveContentVariables } from "../utils/content-variables.mjs";
3
4
  import { SectionRendererPlugin } from "../plugin.mjs";
4
5
  import SectionRenderer from "../renderer/SectionRenderer.vue";
5
6
  const loadedProducts = /* @__PURE__ */ new Set();
@@ -16,6 +17,19 @@ async function loadProductCSS(productCode) {
16
17
  loadedProducts.add(productCode);
17
18
  }
18
19
  }
20
+ const loadedInteractions = /* @__PURE__ */ new Set();
21
+ const productInteractionLoaders = {
22
+ vigilkids: () => import("../interactions/vigilkids.mjs").then((m) => m.setupInteractions())
23
+ };
24
+ async function loadProductInteractions(productCode) {
25
+ if (loadedInteractions.has(productCode))
26
+ return;
27
+ const loader = productInteractionLoaders[productCode];
28
+ if (loader) {
29
+ await loader();
30
+ loadedInteractions.add(productCode);
31
+ }
32
+ }
19
33
  export function createPreviewApp(selector, options) {
20
34
  const sectionsData = ref({
21
35
  sections: {},
@@ -43,7 +57,9 @@ export function createPreviewApp(selector, options) {
43
57
  });
44
58
  options.bridge.on("html:preview", (payload) => {
45
59
  const data = payload;
46
- rawHtmlContent.value = data.html;
60
+ rawHtmlContent.value = resolveContentVariables(data.html);
61
+ const product = currentProduct.value || "vigilkids";
62
+ loadProductInteractions(product);
47
63
  });
48
64
  options.bridge.on("section:select", (payload) => {
49
65
  const data = payload;
@@ -79,6 +95,7 @@ export function createPreviewApp(selector, options) {
79
95
  if (data.productCode) {
80
96
  currentProduct.value = data.productCode;
81
97
  loadProductCSS(data.productCode);
98
+ loadProductInteractions(data.productCode);
82
99
  }
83
100
  });
84
101
  function handleSectionClick(sectionId) {
@@ -87,7 +104,9 @@ export function createPreviewApp(selector, options) {
87
104
  return () => rawHtmlContent.value !== null ? h("div", {
88
105
  class: `article-content article-content--${currentProduct.value || "vigilkids"}`,
89
106
  innerHTML: rawHtmlContent.value
90
- }) : h(SectionRenderer, {
107
+ }) : h("div", {
108
+ class: `article-content article-content--${currentProduct.value || "vigilkids"}`
109
+ }, [h(SectionRenderer, {
91
110
  sectionsData: sectionsData.value,
92
111
  editorMode: true,
93
112
  selectedSectionId: selectedSectionId.value,
@@ -117,11 +136,12 @@ export function createPreviewApp(selector, options) {
117
136
  onInlineEditUndoRedo: (action) => {
118
137
  options.bridge.send({ type: action === "undo" ? "shortcut:undo" : "shortcut:redo" });
119
138
  }
120
- });
139
+ })]);
121
140
  }
122
141
  });
123
142
  app.use(SectionRendererPlugin);
124
143
  app.mount(selector);
144
+ import("../interactions/common.mjs").then((m) => m.setupCommonInteractions());
125
145
  document.addEventListener("keydown", (e) => {
126
146
  if (isEditableTarget(e))
127
147
  return;
@@ -5,6 +5,7 @@ import type { Ref } from 'vue'
5
5
  import { computed, inject } from 'vue'
6
6
 
7
7
  import { renderContent } from '../prosemirror'
8
+ import { resolveContentVariables } from '../../../utils/content-variables'
8
9
 
9
10
  const props = defineProps<{
10
11
  blockOrder: string[]
@@ -23,10 +24,55 @@ const productClass = computed(() => {
23
24
  /* 兼容 article-prose 的 settings.content 字段(可能是 ProseMirror JSON) */
24
25
  const htmlContent = computed(() => {
25
26
  const raw = props.settings.html_content ?? props.settings.content ?? ''
26
- return renderContent(raw)
27
+ return resolveContentVariables(renderContent(raw))
27
28
  })
29
+
30
+ // ── v-html 内容的交互处理 ──
31
+ // SectionWrapper 的 @click.stop 阻止事件冒泡到 document,
32
+ // document 级事件委托无法捕获 v-html 内的点击,
33
+ // 所以直接在组件根元素上用 @click 处理
34
+ function handleContentClick(e: MouseEvent) {
35
+ // FAQ 手风琴
36
+ const faqTitle = (e.target as HTMLElement).closest?.('.faq-item-title')
37
+ if (faqTitle) {
38
+ e.stopPropagation()
39
+ handleFaqToggle(faqTitle)
40
+ return
41
+ }
42
+
43
+ // 链接拦截:阻止非锚点链接在 iframe 内导航
44
+ const anchor = (e.target as HTMLElement).closest?.('a')
45
+ if (anchor) {
46
+ const href = anchor.getAttribute('href')
47
+ if (!href?.startsWith('#')) e.preventDefault()
48
+ }
49
+ }
50
+
51
+ function handleFaqToggle(faqTitle: Element) {
52
+ const faqItem = faqTitle.closest('.faq-item')
53
+ if (!faqItem) return
54
+
55
+ const faqList = faqItem.closest('.faq-list')
56
+ const isActive = faqItem.classList.contains('active')
57
+
58
+ // 手风琴互斥:收起同组其他项
59
+ if (faqList) {
60
+ faqList.querySelectorAll('.faq-item.active').forEach((item) => {
61
+ if (item !== faqItem) {
62
+ item.classList.remove('active')
63
+ const body = item.querySelector('.faq-item-answer') as HTMLElement | null
64
+ if (body) body.style.maxHeight = '0'
65
+ }
66
+ })
67
+ }
68
+
69
+ // 切换当前项
70
+ faqItem.classList.toggle('active', !isActive)
71
+ const body = faqItem.querySelector('.faq-item-answer') as HTMLElement | null
72
+ if (body) body.style.maxHeight = isActive ? '0' : `${body.scrollHeight}px`
73
+ }
28
74
  </script>
29
75
 
30
76
  <template>
31
- <div class="article-custom-html article-content" :class="[productClass]" v-html="htmlContent" />
77
+ <div class="article-custom-html article-content" :class="[productClass]" v-html="htmlContent" @click="handleContentClick" />
32
78
  </template>
@@ -34,15 +34,10 @@ const { blockEditableAttrs } = useInlineEdit({
34
34
  </script>
35
35
 
36
36
  <template>
37
- <ul class="article-bullet-list">
37
+ <ul class="disc-list">
38
38
  <template v-for="blockId in blockOrder" :key="blockId">
39
- <li v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">
40
- {{ blocks[blockId]!.settings.text }}
41
- </li>
39
+ <!-- eslint-disable-next-line vue/no-v-html -->
40
+ <li v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')" v-html="blocks[blockId]!.settings.text" />
42
41
  </template>
43
42
  </ul>
44
43
  </template>
45
-
46
- <style scoped>
47
- .article-bullet-list{color:var(--article-text,#3a4259);list-style-type:disc;margin-top:10px;padding-left:20px}
48
- </style>
@@ -35,7 +35,7 @@ const { editableAttrs } = useInlineEdit({
35
35
  </script>
36
36
 
37
37
  <template>
38
- <!-- button variant: 单个居中按钮 -->
38
+ <!-- button variant: 单个居中按钮 -->
39
39
  <a
40
40
  v-if="variant === 'button'"
41
41
  :href="editorMode ? undefined : safeUrl(String(s.button_url || ''))"
@@ -109,45 +109,4 @@ const { editableAttrs } = useInlineEdit({
109
109
  <img v-if="s.image_src" :src="String(s.image_src)" alt="" loading="lazy">
110
110
  </div>
111
111
 
112
- <!-- button-group-dark variant: 黑底容器+白边主按钮+渐变次按钮 (Visiva) -->
113
- <div v-else-if="variant === 'button-group-dark'" class="btn-group-dark">
114
- <div class="btn-group-dark__buttons">
115
- <div v-if="s.button_text" class="btn-group-dark__item">
116
- <a
117
- :href="editorMode ? undefined : safeUrl(String(s.button_url || ''))"
118
- class="btn btn-outline-white"
119
- v-bind="editableAttrs('button_text')"
120
- >
121
- {{ s.button_text }}
122
- </a>
123
- <p
124
- v-if="s.button_caption"
125
- class="btn-group-dark__caption"
126
- v-bind="editableAttrs('button_caption')"
127
- >
128
- {{ s.button_caption }}
129
- </p>
130
- </div>
131
- <div v-if="s.secondary_button_text" class="btn-group-dark__item">
132
- <a
133
- :href="editorMode ? undefined : safeUrl(String(s.secondary_button_url || ''))"
134
- class="btn btn-gradient"
135
- v-bind="editableAttrs('secondary_button_text')"
136
- >
137
- {{ s.secondary_button_text }}
138
- </a>
139
- <p
140
- v-if="s.secondary_button_caption"
141
- class="btn-group-dark__caption"
142
- v-bind="editableAttrs('secondary_button_caption')"
143
- >
144
- {{ s.secondary_button_caption }}
145
- </p>
146
- </div>
147
- </div>
148
- </div>
149
112
  </template>
150
-
151
- <style scoped>
152
- .btn{align-items:center;border:none;border-radius:var(--article-btn-radius,10px);box-sizing:border-box;cursor:pointer;display:flex;font-size:16px;font-weight:700;height:var(--article-btn-height,52px);justify-content:center;line-height:24px;padding:10px 20px;text-decoration:none;transition:all .3s ease;width:220px}.btn.btn-primary{background:var(--article-primary,#24c790);color:var(--article-btn-color,#fff);margin:20px auto 30px}.btn.btn-primary:active,.btn.btn-primary:hover{background:var(--article-primary-hover,#1ba97a)}.btn.outline-btn{background:#fff;border:2px solid var(--article-primary,#24c790);border-radius:var(--article-btn-radius,10px);color:var(--article-primary-hover,#1ba97a);height:var(--article-btn-height,52px);margin:20px auto 30px}.btn.outline-btn:hover{background:var(--article-primary,#24c790);color:#fff}.btn.outline-btn:active{background:var(--article-primary-hover,#1ba97a);color:#fff}.btn-group{align-items:center;display:flex;gap:40px;justify-content:center;margin-bottom:30px}.btn-group .btn{margin:0}.article-try{align-items:center;background:var(--article-bg-gray,#f3f5f7);border-radius:10px;color:var(--article-text,#3a4259);margin-top:30px;padding:40px 60px}.article-try,.article-try-content{display:flex;justify-content:space-between}.article-try-content{flex-direction:column}.article-try-title{font-size:20px;font-weight:700;line-height:23px;margin-bottom:10px;margin-top:0}.article-try-description{line-height:32px;margin-bottom:0;margin-top:0}.article-try .btn{margin:0 0 0 10px;min-width:220px}.article-show{align-items:center;background:var(--article-bg-gray,#f3f5f7);border-radius:10px;color:var(--article-text,#3a4259);margin-top:30px;padding:40px 30px 40px 60px}.article-show,.article-show-content{display:flex;justify-content:space-between}.article-show-content{flex-direction:column}.article-show-title{font-size:24px;font-weight:700;line-height:28px;margin-bottom:14px;margin-top:0}.article-show-description{line-height:30px;margin-bottom:0;margin-top:0}.article-show .btn{margin-left:0;margin-top:20px;min-width:220px}.article-show img{border-radius:10px;height:auto;margin-left:32px;max-width:368px;-o-object-fit:cover;object-fit:cover;width:100%}.btn-group-dark{background:#000;border-radius:20px;margin-top:40px;padding:16px 40px}.btn-group-dark__buttons{align-items:center;display:flex;flex-direction:column;gap:16px;justify-content:center}.btn-group-dark__item{text-align:center}.btn-group-dark__caption{color:#9ca3af;font-size:14px;font-weight:500;margin:16px 0 0}.btn.btn-outline-white{background:transparent;border:1px solid #fff;color:#fff;margin:0}.btn.btn-outline-white:hover{background:#fff;color:#000}.btn.btn-gradient{background:linear-gradient(to right,var(--article-gradient-start,#24c790),var(--article-gradient-end,#1ba97a));border:none;color:#000;margin:0}.btn.btn-gradient:hover{opacity:.9}@media (min-width:769px){.btn-group-dark{padding:40px}.btn-group-dark__buttons{flex-direction:row;gap:24px}}@media (max-width:768px){.btn-group{flex-direction:column;gap:10px}.article-try{align-items:center;flex-direction:column;padding:20px 16px;text-align:center}.article-try-description{line-height:21px}.article-try .btn{margin:20px 0}.article-show{flex-direction:column;padding:20px 16px;text-align:center}.article-show,.article-show-content{align-items:center}.article-show-description{line-height:21px}.article-show .btn,.article-show img{margin:20px 0}.btn-group-dark__buttons{gap:16px}}
153
- </style>
@@ -1,8 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { BlockData } from '@vigilkids/section-core'
3
3
 
4
- import { computed } from 'vue'
5
-
6
4
  import { useInlineEdit } from '../../../composables/useInlineEdit'
7
5
  import ArticleFaqItem from './ArticleFaqItem.vue'
8
6
 
@@ -21,9 +19,7 @@ const emit = defineEmits<{
21
19
  (e: 'undo-redo', action: 'redo' | 'undo'): void
22
20
  }>()
23
21
 
24
- const s = computed(() => props.settings)
25
-
26
- const { editableAttrs, blockEditableAttrs } = useInlineEdit({
22
+ useInlineEdit({
27
23
  editorMode: () => !!props.editorMode,
28
24
  onSettingUpdate: (key, value) => emit('update:setting', key, value),
29
25
  onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
@@ -34,35 +30,9 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
34
30
  </script>
35
31
 
36
32
  <template>
37
- <!-- Visiva 风格:浅绿卡片 + 绿色标题 + 静态列表 -->
38
- <div v-if="s.title" class="faq-card">
39
- <div class="faq-card__header">
40
- <h2 class="faq-card__title" v-bind="editableAttrs('title')">
41
- {{ s.title }}
42
- </h2>
43
- </div>
44
- <div class="faq-card__list">
45
- <template v-for="blockId in blockOrder" :key="blockId">
46
- <div v-if="blocks[blockId]" class="faq-card__item">
47
- <h3 class="faq-card__question" v-bind="blockEditableAttrs(blockId, 'question')">
48
- {{ blocks[blockId]!.settings.question }}
49
- </h3>
50
- <p class="faq-card__answer" v-bind="blockEditableAttrs(blockId, 'answer')">
51
- {{ blocks[blockId]!.settings.answer }}
52
- </p>
53
- </div>
54
- </template>
55
- </div>
56
- </div>
57
-
58
- <!-- VigilKids 风格:手风琴折叠 -->
59
- <div v-else class="faq-list">
33
+ <div class="faq-list">
60
34
  <template v-for="blockId in blockOrder" :key="blockId">
61
35
  <ArticleFaqItem v-if="blocks[blockId]" :settings="blocks[blockId]!.settings" />
62
36
  </template>
63
37
  </div>
64
38
  </template>
65
-
66
- <style scoped>
67
- .faq-list{background:var(--article-bg-gray,#f3f5f7);border-radius:10px;color:#333;margin-bottom:50px;margin-top:20px;padding:26px 20px}.faq-list :deep(.faq-item:not(:last-child)){margin-bottom:10px}.faq-card{background:var(--article-bg-faq,#f9fafb);border-radius:10px;font-size:14px;margin-top:40px;padding:28px 16px}.faq-card__header{align-items:center;display:flex;gap:8px;margin-bottom:16px}.faq-card__title{color:var(--article-primary,#24c790);font-size:18px;font-weight:700;margin:0}.faq-card__list{display:flex;flex-direction:column;gap:16px}.faq-card__question{font-size:14px;font-weight:700;margin:0 0 6px}.faq-card__answer{line-height:1.6;margin:0}@media (min-width:769px){.faq-card{border-radius:15px;font-size:16px;padding:28px 20px}.faq-card__title{font-size:20px}.faq-card__list{gap:24px}.faq-card__question{font-size:16px;margin-bottom:12px}}
68
- </style>
@@ -6,23 +6,23 @@ defineProps<{
6
6
  }>()
7
7
 
8
8
  const active = ref(false)
9
+ const answer = ref<HTMLElement>()
9
10
 
10
11
  function toggle() {
12
+ const el = answer.value
13
+ if (el) {
14
+ el.style.maxHeight = active.value ? '0' : `${el.scrollHeight}px`
15
+ }
11
16
  active.value = !active.value
12
17
  }
13
18
  </script>
14
19
 
15
20
  <template>
16
- <div class="faq-item" :class="{ active }" @click="toggle">
21
+ <div class="faq-item" :class="{ active }" @click="toggle">
17
22
  <p class="faq-item-title">
18
23
  {{ settings.question }}
19
24
  </p>
20
- <p class="faq-item-answer">
21
- {{ settings.answer }}
22
- </p>
25
+ <!-- eslint-disable-next-line vue/no-v-html -->
26
+ <div ref="answer" class="faq-item-answer" v-html="settings.answer" />
23
27
  </div>
24
28
  </template>
25
-
26
- <style scoped>
27
- .faq-item{background:#fff;border-radius:10px;cursor:pointer;padding:32px 26px 32px 18px;position:relative;transition:all .3s ease}.faq-item:hover{transform:translateY(-2px)}.faq-item-title{cursor:pointer;font-size:18px;font-weight:700;line-height:26px;margin-bottom:0;margin-top:0;padding-right:74px;transition:margin-bottom .3s ease}.faq-item-title:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23333' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:cover;content:"";height:26px;position:absolute;right:26px;top:30px;transition:transform .3s ease;width:26px}.faq-item.active .faq-item-title{margin-bottom:18px}.faq-item.active .faq-item-title:before{transform:rotate(180deg)}.faq-item-answer{color:#666;font-size:18px;line-height:27px;margin:0;max-height:0;opacity:0;overflow:hidden;padding:0 74px 0 22px;transition:max-height .4s ease,opacity .3s ease,padding .3s ease}.faq-item.active .faq-item-answer{max-height:500px;opacity:1;padding-bottom:0;padding-top:0}@media (max-width:768px){.faq-item{padding:20px 16px}.faq-item-title{font-size:14px;line-height:16px;padding-right:40px}.faq-item-title:before{height:16px;right:16px;top:20px;width:16px}.faq-item-answer{font-size:14px;line-height:16px;padding-left:0;padding-right:0}.faq-item.active .faq-item-title{margin-bottom:10px}}
28
- </style>
@@ -1,7 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { BlockData } from '@vigilkids/section-core'
3
3
 
4
- import { safeUrl } from '@vigilkids/section-core'
5
4
  import { computed } from 'vue'
6
5
 
7
6
  import { useInlineEdit } from '../../../composables/useInlineEdit'
@@ -22,7 +21,6 @@ const emit = defineEmits<{
22
21
  }>()
23
22
 
24
23
  const s = computed(() => props.settings)
25
- const variant = computed(() => String(s.value.variant || 'default'))
26
24
 
27
25
  const { editableAttrs, blockEditableAttrs } = useInlineEdit({
28
26
  editorMode: () => !!props.editorMode,
@@ -35,51 +33,15 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
35
33
  </script>
36
34
 
37
35
  <template>
38
- <!-- light / dark 变体:图片+特性列表+CTA (Visiva) -->
39
- <div
40
- v-if="variant === 'light' || variant === 'dark'"
41
- class="feature-card" :class="[{ 'feature-card--dark': variant === 'dark' }]"
42
- >
43
- <div class="feature-card__text">
44
- <h2 v-if="s.title" class="feature-card__title" v-bind="editableAttrs('title')">
45
- {{ s.title }}
46
- </h2>
47
- <ul class="feature-card__list">
48
- <template v-for="blockId in blockOrder" :key="blockId">
49
- <li v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">
50
- {{ blocks[blockId]!.settings.text }}
51
- </li>
52
- </template>
53
- </ul>
54
- <a
55
- v-if="s.button_text"
56
- :href="safeUrl(String(s.button_url || ''))"
57
- class="feature-card__btn"
58
- v-bind="editableAttrs('button_text')"
59
- >
60
- {{ s.button_text }}
61
- </a>
62
- </div>
63
- <div v-if="s.image_src" class="feature-card__image">
64
- <img :src="String(s.image_src)" alt="" loading="lazy">
65
- </div>
66
- </div>
67
-
68
- <!-- default 变体:原有灰色特性框 (VigilKids,完全不动) -->
69
- <div v-else class="article-feature">
36
+ <div class="article-feature">
70
37
  <p v-if="s.title" class="article-feature-title" v-bind="editableAttrs('title')">
71
38
  {{ s.title }}
72
39
  </p>
73
40
  <ul class="article-feature-list">
74
41
  <template v-for="blockId in blockOrder" :key="blockId">
75
- <li v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">
76
- {{ blocks[blockId]!.settings.text }}
77
- </li>
42
+ <!-- eslint-disable-next-line vue/no-v-html -->
43
+ <li v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')" v-html="blocks[blockId]!.settings.text" />
78
44
  </template>
79
45
  </ul>
80
46
  </div>
81
47
  </template>
82
-
83
- <style scoped>
84
- .article-feature{background:var(--article-bg-gray,#f3f5f7);border-radius:10px;color:var(--article-text,#3a4259);margin-top:30px;padding:30px 60px}.article-feature-title{font-size:20px;font-weight:700;line-height:23px;margin-bottom:20px;margin-top:0}.article-feature-list{line-height:32px;list-style-type:disc;padding-left:20px}@media (max-width:768px){.article-feature{padding:20px 16px}}.feature-card{align-items:center;background:var(--article-bg-light,#f3f4f6);border-radius:10px;display:flex;flex-direction:column;gap:20px;margin-top:40px;padding:28px 12px}.feature-card--dark{background:var(--article-bg-dark,#1f2937);color:#fff}.feature-card__text{max-width:426px}.feature-card__title{font-size:18px;font-weight:700;margin:0 0 16px}.feature-card__list{font-size:14px;font-weight:300;line-height:1.8;list-style-type:disc;margin:0 0 20px;padding-left:16px}.feature-card--dark .feature-card__list{color:#d1d5db}.feature-card:not(.feature-card--dark) .feature-card__list{color:var(--article-text-muted,#6b7280)}.feature-card__btn{align-items:center;background:linear-gradient(to right,var(--article-gradient-start,#24c790),var(--article-gradient-end,#1ba97a));border-radius:40px;color:#000;display:inline-flex;font-size:16px;font-weight:700;height:60px;justify-content:center;min-width:260px;text-decoration:none;transition:opacity .2s}.feature-card__btn:hover{opacity:.9}.feature-card__image img{border-radius:10px;height:auto;max-width:411px;-o-object-fit:cover;object-fit:cover;width:100%}@media (min-width:769px){.feature-card{border-radius:20px;flex-direction:row;justify-content:space-between;padding:40px}.feature-card__title{font-size:20px}.feature-card__list{margin-bottom:28px}.feature-card__image img{border-radius:20px}}
85
- </style>
@@ -34,19 +34,10 @@ const { editableAttrs } = useInlineEdit({
34
34
  </script>
35
35
 
36
36
  <template>
37
- <h2
37
+ <h2
38
38
  :id="String(s.anchor || '')"
39
- class="article-heading" :class="[
40
- {
41
- 'article-heading--plain': variant === 'plain',
42
- 'article-heading--accent': variant === 'accent',
43
- },
44
- ]"
39
+ :class="{ 'black-h2': variant === 'plain' }"
45
40
  >
46
41
  <span v-bind="editableAttrs('title')">{{ s.title }}</span>
47
42
  </h2>
48
43
  </template>
49
-
50
- <style scoped>
51
- .article-heading{background:var(--article-bg-light,#f5f9fc);font-size:28px;font-weight:var(--article-heading-weight,700);line-height:36px;margin-bottom:30px;margin-top:var(--article-heading-mt,30px);padding:14px 30px;position:relative}.article-heading:not(.article-heading--plain):not(.article-heading--accent):before{background:var(--article-primary,#24c790);content:"";height:100%;left:0;position:absolute;top:0;width:10px}.article-heading--plain{font-size:28px;line-height:36px;padding:0}.article-heading--accent,.article-heading--plain{background:transparent;color:var(--article-text,#3a4259)}.article-heading--accent{font-size:30px;font-weight:var(--article-heading-weight,700);line-height:1.2;padding:0 0 0 16px}.article-heading--accent:before{background:var(--article-primary,#24c790);border-radius:0;content:"";height:25px;left:0;position:absolute;top:4px;width:5px}.article-heading[id]{scroll-margin-top:80px}@media (max-width:768px){.article-heading{font-size:20px;line-height:23px;margin-bottom:20px;margin-top:20px;padding:16px}.article-heading:not(.article-heading--plain):not(.article-heading--accent):before{height:100%;width:4px}.article-heading--plain{font-size:20px;line-height:23px}.article-heading--accent{font-size:22px;line-height:1.2;padding-left:10px}.article-heading--accent:before{height:18px;width:4px}}
52
- </style>
@@ -21,11 +21,9 @@ const emit = defineEmits<{
21
21
  }>()
22
22
 
23
23
  const s = computed(() => props.settings)
24
- const variant = computed(() => String(s.value.variant || 'default'))
25
- const isWarning = computed(() => variant.value === 'warning')
26
- const isRow = computed(() => String(s.value.layout) === 'row')
24
+ const isWarning = computed(() => String(s.value.variant) === 'warning')
27
25
 
28
- const { editableAttrs, blockEditableAttrs } = useInlineEdit({
26
+ const { blockEditableAttrs } = useInlineEdit({
29
27
  editorMode: () => !!props.editorMode,
30
28
  onSettingUpdate: (key, value) => emit('update:setting', key, value),
31
29
  onBlockSettingUpdate: (blockId, key, value) => emit('update:block-setting', blockId, key, value),
@@ -36,70 +34,13 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
36
34
  </script>
37
35
 
38
36
  <template>
39
- <!-- tips / note 变体:角标样式 (Visiva) -->
40
- <div v-if="variant === 'tips' || variant === 'note'" class="notice-badge">
41
- <div class="notice-badge__tag">
42
- <span class="notice-badge__label" v-bind="editableAttrs('title')">{{
43
- s.title || 'Tips'
44
- }}</span>
45
- </div>
46
- <div class="notice-badge__content">
47
- <template v-for="blockId in blockOrder" :key="blockId">
48
- <p v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">
49
- {{ blocks[blockId]!.settings.text }}
50
- </p>
51
- </template>
52
- </div>
53
- </div>
54
-
55
- <!-- info 变体:圆角卡片+图标标题 (Visiva) -->
56
- <div v-else-if="variant === 'info'" class="notice-card">
57
- <div v-if="s.title" class="notice-card__header">
58
- <svg
59
- xmlns="http://www.w3.org/2000/svg"
60
- width="22"
61
- height="22"
62
- viewBox="0 0 24 24"
63
- fill="none"
64
- stroke="currentColor"
65
- stroke-width="2"
66
- stroke-linecap="round"
67
- stroke-linejoin="round"
68
- >
69
- <circle cx="12" cy="12" r="10" />
70
- <path d="M12 16v-4" />
71
- <path d="M12 8h.01" />
72
- </svg>
73
- <h3 class="notice-card__title" v-bind="editableAttrs('title')">
74
- {{ s.title }}
75
- </h3>
76
- </div>
77
- <div class="notice-card__content">
78
- <template v-for="blockId in blockOrder" :key="blockId">
79
- <p v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">
80
- {{ blocks[blockId]!.settings.text }}
81
- </p>
82
- </template>
83
- </div>
84
- </div>
85
-
86
- <!-- default / warning 变体:虚线框样式 (VigilKids 原有,完全不动) -->
87
37
  <div
88
- v-else
89
38
  class="notice-info"
90
- :class="{
91
- 'warning': isWarning,
92
- 'notice-info--row-child': isRow,
93
- }"
39
+ :class="{ 'warning': isWarning }"
94
40
  >
95
41
  <template v-for="blockId in blockOrder" :key="blockId">
96
- <p v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')">
97
- {{ blocks[blockId]!.settings.text }}
98
- </p>
42
+ <!-- eslint-disable-next-line vue/no-v-html -->
43
+ <p v-if="blocks[blockId]" v-bind="blockEditableAttrs(blockId, 'text')" v-html="blocks[blockId]!.settings.text" />
99
44
  </template>
100
45
  </div>
101
46
  </template>
102
-
103
- <style scoped>
104
- .notice-info{background:color-mix(in srgb,var(--article-primary-hover,#1ba97a) 10%,transparent);border:2px dashed var(--article-primary-hover,#1ba97a);border-radius:10px;font-size:18px;line-height:21px;margin-bottom:20px;margin-top:30px;padding:60px 40px 40px 108px;position:relative}.notice-info:before{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='142' height='40' fill='none' viewBox='0 0 142 40'%3E%3Cpath fill='%2324c790' d='M0 0h142l-9.079 27.62a18 18 0 0 1-17.1 12.38H26.179a18 18 0 0 1-17.1-12.38z'/%3E%3Cpath fill='%23fff' d='M51.153 30.957h-.033c-1.017-.011-2.037-.011-3.057-.011h-1.556c-1.039 0-2.078 0-3.12-.014a4.8 4.8 0 0 1-1.681-.355c-.91-.356-1.43-1.149-1.428-2.18l.003-3.866c0-2.141 0-4.285.02-6.426.002-.582.377-1.129.697-1.416 1.238-1.132 2.584-2.543 3.147-4.446.156-.53.216-1.116.279-1.734.126-1.23.924-2.031 1.99-2.031q.63.002 1.247.369c.823.495 1.373 1.271 1.676 2.379.487 1.758.238 3.464-.035 4.927v.005a.555.555 0 0 0 .544.66h3.555c.612 0 1.49.076 2.031.732.394.479.506 1.124.34 1.977-.51 2.622-1.133 5.267-1.729 7.73-.186.769-.495 1.48-.793 2.169l-.128.295c-.34.798-1.04 1.236-1.969 1.236M35.91 30.7h-.325a2.145 2.145 0 0 1-2.139-2.139v-9.529c0-1.176.963-2.138 2.139-2.138h.325c1.176 0 2.138.962 2.138 2.138v9.527a2.14 2.14 0 0 1-2.138 2.14M76.807 21.932h-3.623v-2.285h3.623q.84 0 1.367-.274.527-.284.771-.781.245-.498.245-1.123 0-.634-.245-1.182a1.97 1.97 0 0 0-.771-.879q-.528-.332-1.367-.332h-2.608V27h-2.93V12.781h5.538q1.67 0 2.861.606 1.2.595 1.836 1.65.635 1.054.635 2.412 0 1.377-.635 2.383-.634 1.005-1.836 1.553-1.191.547-2.861.547m9.99-3.194V27h-2.813V16.434h2.647zm3.184-2.373-.05 2.608a7 7 0 0 0-.497-.05 5 5 0 0 0-.518-.028 3.1 3.1 0 0 0-1.035.156 1.85 1.85 0 0 0-.723.44 1.9 1.9 0 0 0-.43.712q-.136.42-.156.957l-.566-.176q0-1.025.205-1.884.205-.87.596-1.514.4-.645.976-.996a2.5 2.5 0 0 1 1.319-.352q.234 0 .478.04.244.028.4.087m.585 5.46v-.206q0-1.162.332-2.139a4.95 4.95 0 0 1 .967-1.709 4.3 4.3 0 0 1 1.563-1.123q.927-.41 2.129-.41 1.2 0 2.138.41.938.4 1.573 1.124a4.9 4.9 0 0 1 .976 1.709q.332.975.332 2.138v.205q0 1.152-.332 2.139a5 5 0 0 1-.976 1.709 4.3 4.3 0 0 1-1.563 1.123q-.927.4-2.129.4-1.2 0-2.138-.4a4.4 4.4 0 0 1-1.573-1.123 5.1 5.1 0 0 1-.967-1.71 6.7 6.7 0 0 1-.332-2.138m2.813-.206v.205q0 .664.117 1.24.117.577.371 1.016.264.43.684.674t1.025.244q.585 0 1.006-.244.42-.245.674-.674.255-.44.371-1.015.127-.577.127-1.24v-.206q0-.645-.127-1.21a3.2 3.2 0 0 0-.38-1.016q-.255-.45-.675-.704t-1.015-.253-1.016.253q-.41.255-.674.704a3.4 3.4 0 0 0-.37 1.015 6 6 0 0 0-.118 1.211m14.639 2.461a.9.9 0 0 0-.176-.547q-.175-.244-.654-.449-.469-.215-1.358-.39a10.6 10.6 0 0 1-1.494-.44 5.2 5.2 0 0 1-1.191-.654 2.8 2.8 0 0 1-.782-.899 2.5 2.5 0 0 1-.283-1.201q0-.665.283-1.25.294-.586.83-1.035a3.9 3.9 0 0 1 1.328-.713 5.6 5.6 0 0 1 1.778-.264q1.377 0 2.363.44.996.44 1.524 1.21.537.762.537 1.739h-2.813q0-.41-.176-.732a1.16 1.16 0 0 0-.527-.518q-.351-.195-.918-.195-.469 0-.81.166a1.25 1.25 0 0 0-.528.43 1.03 1.03 0 0 0-.176.585q0 .245.098.44.108.185.342.341t.605.294q.381.126.938.234a10.8 10.8 0 0 1 2.041.615q.898.372 1.426 1.016.527.635.527 1.67 0 .702-.313 1.289-.312.585-.898 1.025a4.6 4.6 0 0 1-1.406.674 6.6 6.6 0 0 1-1.826.234q-1.475 0-2.5-.527-1.016-.528-1.543-1.338-.518-.82-.518-1.68h2.666q.02.577.293.928.283.352.713.508.44.156.947.156.548 0 .908-.146.362-.157.547-.41a1 1 0 0 0 .196-.606'/%3E%3C/svg%3E") no-repeat 50%;background-size:cover;content:"";height:40px;left:40px;position:absolute;top:0;width:142px}.notice-info :deep(p){color:var(--article-text,#3a4259);margin-top:0;position:relative}.notice-info :deep(p:not(:last-child)){margin-bottom:16px}.notice-info :deep(p:before){background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' fill='none' viewBox='0 0 30 30'%3E%3Cpath fill='%2324c790' d='M14.947 1.897C7.722 1.897 1.844 7.775 1.844 15S7.72 28.103 14.947 28.103c7.225 0 13.103-5.878 13.103-13.103S22.172 1.897 14.946 1.897m6.323 10.942-7.528 7.609-.007.005q-.003.004-.006.008c-.06.058-.134.094-.204.133-.034.02-.063.05-.1.064a.95.95 0 0 1-.706-.002c-.038-.016-.068-.048-.104-.067-.07-.04-.142-.075-.202-.134l-.005-.007-.007-.006-3.702-3.805a.94.94 0 1 1 1.348-1.31l3.034 3.117 6.853-6.928a.94.94 0 1 1 1.336 1.322'/%3E%3C/svg%3E") no-repeat 50%;content:"";height:30px;left:-38px;position:absolute;top:-4.5px;width:30px}.notice-info.warning{background:color-mix(in srgb,var(--article-warning,#f1631c) 10%,transparent);border-color:var(--article-warning,#f1631c)}.notice-info.warning:before{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='142' height='40' fill='none' viewBox='0 0 142 40'%3E%3Cpath fill='%23f1631c' d='M0 0h142l-9.079 27.62a18 18 0 0 1-17.1 12.38H26.179a18 18 0 0 1-17.1-12.38z'/%3E%3Cpath fill='%23fff' d='M38.14 8.478h.033c1.017.01 2.037.01 3.057.01h1.556c1.039 0 2.078 0 3.12.014a4.8 4.8 0 0 1 1.681.356c.91.355 1.43 1.148 1.427 2.18l-.002 3.865c0 2.141 0 4.285-.02 6.426-.002.583-.377 1.13-.697 1.417-1.238 1.132-2.584 2.543-3.147 4.446-.156.53-.216 1.115-.279 1.733-.126 1.23-.924 2.032-1.99 2.032-.419 0-.837-.126-1.247-.37-.823-.494-1.373-1.27-1.676-2.378-.487-1.758-.238-3.465.035-4.927v-.006a.555.555 0 0 0-.544-.659h-3.555c-.612 0-1.49-.076-2.032-.733-.393-.478-.505-1.123-.338-1.977.508-2.622 1.132-5.266 1.728-7.73.185-.768.495-1.479.793-2.168l.128-.295c.34-.799 1.04-1.236 1.969-1.236m15.244.257h.325c1.176 0 2.139.962 2.139 2.138v9.53a2.145 2.145 0 0 1-2.139 2.138h-.325a2.145 2.145 0 0 1-2.138-2.139v-9.526a2.14 2.14 0 0 1 2.138-2.141M79.512 22.283h2.92q-.089 1.436-.791 2.549-.694 1.114-1.944 1.738-1.24.625-2.988.625-1.367 0-2.451-.468a5.2 5.2 0 0 1-1.856-1.368q-.762-.888-1.162-2.148t-.4-2.822v-.987q0-1.562.41-2.822.42-1.27 1.191-2.158a5.3 5.3 0 0 1 1.866-1.367q1.084-.48 2.421-.479 1.778 0 2.999.645 1.23.645 1.904 1.777.684 1.133.82 2.578h-2.93q-.048-.86-.341-1.455a1.94 1.94 0 0 0-.889-.908q-.585-.313-1.562-.313-.733 0-1.28.274-.546.273-.918.83-.371.557-.556 1.406-.177.84-.176 1.973v1.006q0 1.103.166 1.943.166.83.508 1.406.351.567.898.86.556.283 1.338.283.918 0 1.514-.293t.908-.87q.323-.575.38-1.435m4.219-.459v-.205q0-1.162.332-2.139a4.95 4.95 0 0 1 .966-1.709 4.3 4.3 0 0 1 1.563-1.123q.927-.41 2.129-.41 1.2 0 2.138.41.938.4 1.573 1.124.645.721.976 1.709.332.975.332 2.138v.205q0 1.152-.332 2.139a5 5 0 0 1-.976 1.709 4.3 4.3 0 0 1-1.563 1.123q-.927.4-2.129.4-1.2 0-2.138-.4a4.4 4.4 0 0 1-1.573-1.123 5.1 5.1 0 0 1-.966-1.71 6.7 6.7 0 0 1-.332-2.138m2.812-.205v.205q0 .664.117 1.24.117.577.371 1.016.264.43.684.674t1.025.244q.585 0 1.006-.244.42-.245.674-.674a3.4 3.4 0 0 0 .371-1.015q.127-.577.127-1.24v-.206q0-.645-.127-1.21a3.2 3.2 0 0 0-.38-1.016 1.9 1.9 0 0 0-.675-.704q-.42-.253-1.015-.253-.596 0-1.016.253-.41.255-.674.704-.254.44-.37 1.015a6 6 0 0 0-.118 1.211m11.68-2.93V27H95.41V16.434h2.637zm-.41 2.657h-.762q0-1.173.302-2.11.304-.947.85-1.611a3.65 3.65 0 0 1 1.299-1.025 3.9 3.9 0 0 1 1.699-.362q.742 0 1.358.215.615.214 1.054.684.45.468.684 1.24.244.771.244 1.885V27h-2.832v-6.748q0-.703-.195-1.094a1.1 1.1 0 0 0-.576-.547q-.372-.166-.918-.166-.567 0-.987.225-.41.225-.683.625a3 3 0 0 0-.4.918q-.138.527-.138 1.133m14.58 2.734a.9.9 0 0 0-.176-.547q-.175-.244-.655-.449-.468-.215-1.357-.39a10.6 10.6 0 0 1-1.494-.44 5.2 5.2 0 0 1-1.191-.654 2.85 2.85 0 0 1-.782-.899 2.5 2.5 0 0 1-.283-1.201q0-.665.283-1.25.294-.586.83-1.035a3.9 3.9 0 0 1 1.328-.713 5.6 5.6 0 0 1 1.778-.264q1.377 0 2.363.44.996.44 1.524 1.21.537.762.537 1.739h-2.813q0-.41-.176-.732a1.16 1.16 0 0 0-.527-.518q-.351-.195-.918-.195-.469 0-.81.166a1.25 1.25 0 0 0-.528.43 1.03 1.03 0 0 0-.176.585q0 .245.098.44.108.185.342.341t.605.294q.381.126.938.234a10.8 10.8 0 0 1 2.041.615q.898.372 1.426 1.016.527.635.527 1.67 0 .702-.313 1.289-.312.585-.898 1.025a4.6 4.6 0 0 1-1.406.674 6.6 6.6 0 0 1-1.826.234q-1.475 0-2.5-.527-1.016-.528-1.543-1.338-.518-.82-.518-1.68h2.666q.02.577.293.928.283.352.713.508.44.156.947.156.548 0 .908-.146.362-.157.547-.41a1 1 0 0 0 .196-.606'/%3E%3C/svg%3E") no-repeat 50%;background-size:cover}.notice-info.warning :deep(p:before){background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' fill='none' viewBox='0 0 30 30'%3E%3Cg clip-path='url(%23a)'%3E%3Cpath fill='%23f1631c' d='M15 1.875C7.751 1.875 1.875 7.751 1.875 15S7.751 28.125 15 28.125 28.125 22.249 28.125 15 22.249 1.875 15 1.875m6.299 17.931a1.06 1.06 0 0 1 0 1.493 1.06 1.06 0 0 1-1.492 0L15 16.49 10.195 21.3a1.06 1.06 0 0 1-1.494 0 1.06 1.06 0 0 1 0-1.492l4.805-4.804L8.7 10.195a1.058 1.058 0 0 1 .747-1.803c.28 0 .549.111.747.31L15 13.508l4.805-4.805a1.06 1.06 0 0 1 1.494-.003 1.06 1.06 0 0 1 0 1.494L16.49 15z'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='a'%3E%3Cpath fill='%23fff' d='M0 0h30v30H0z'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E") no-repeat 50%}@media (max-width:768px){.notice-info{line-height:19px;padding:44px 16px 30px 44px}.notice-info:before{background-size:cover!important;height:32px;left:16px;width:114px}.notice-info :deep(p:before){background-size:cover;height:24px;left:-30px;top:-2px;width:24px}}.notice-badge{background:var(--article-bg-light,#f3f4f6);border-radius:0 0 5px 5px;margin-top:40px;padding:36px 16px 20px;position:relative}.notice-badge__tag{align-items:center;background:var(--article-badge,#24c790);border-radius:5px 0 10px 0;display:flex;gap:4px;height:24px;left:0;padding:0 8px;position:absolute;top:0}.notice-badge__label{color:#fff;font-size:14px;font-weight:700}.notice-badge__content{color:var(--article-text-muted,#6b7280);font-size:14px;font-weight:300;line-height:1.6}.notice-badge__content p{margin:0 0 8px}.notice-badge__content p:last-child{margin-bottom:0}.notice-card{background:var(--article-bg-light,#f3f4f6);border-radius:10px;margin-top:40px;padding:20px 16px}.notice-card__header{align-items:center;display:flex;gap:12px;margin-bottom:12px}.notice-card__title{color:var(--article-text,#1f2937);font-size:16px;font-weight:600;margin:0}.notice-card__content{color:var(--article-text-muted,#6b7280);font-size:14px;font-weight:300;line-height:1.6}.notice-card__content p{margin:0 0 8px}.notice-card__content p:last-child{margin-bottom:0}@media (min-width:769px){.notice-badge{padding:36px 16px 20px}.notice-card{padding:24px}.notice-badge__content,.notice-card__content{font-size:16px}}
105
- </style>
@@ -47,28 +47,20 @@ const { editableAttrs, blockEditableAttrs } = useInlineEdit({
47
47
  </script>
48
48
 
49
49
  <template>
50
- <div :class="isDashed ? 'props-dashed' : 'props-solid'">
51
- <!-- Pros -->
50
+ <div :class="isDashed ? 'props-dashed' : 'props-solid'">
52
51
  <div class="props-left">
53
52
  <span v-bind="editableAttrs('pros_title')">{{ s.pros_title || 'Pros' }}</span>
54
53
  <ul>
55
- <li v-for="(item, idx) in pros" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')">
56
- {{ idx + 1 }}. {{ item.block.settings.text }}
57
- </li>
54
+ <!-- eslint-disable-next-line vue/no-v-html -->
55
+ <li v-for="item in pros" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.block.settings.text" />
58
56
  </ul>
59
57
  </div>
60
- <!-- Cons -->
61
58
  <div class="props-right">
62
59
  <span v-bind="editableAttrs('cons_title')">{{ s.cons_title || 'Cons' }}</span>
63
60
  <ul>
64
- <li v-for="(item, idx) in cons" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')">
65
- {{ idx + 1 }}. {{ item.block.settings.text }}
66
- </li>
61
+ <!-- eslint-disable-next-line vue/no-v-html -->
62
+ <li v-for="item in cons" :key="item.id" v-bind="blockEditableAttrs(item.id, 'text')" v-html="item.block.settings.text" />
67
63
  </ul>
68
64
  </div>
69
65
  </div>
70
66
  </template>
71
-
72
- <style scoped>
73
- .props-dashed,.props-solid{background:var(--article-bg-light,#f5f9fc);border-radius:10px;display:grid;gap:60px;grid-template-columns:1fr 1fr;margin:20px 0;padding:20px 30px}.props-dashed{background:#fff;border:1px dashed #ccc}.props-dashed span,.props-solid span{display:block;font-size:20px;font-weight:700;line-height:28px;padding-left:14px;position:relative}.props-dashed span:before,.props-solid span:before{background:var(--article-primary,#24c790);content:"";height:14px;left:0;position:absolute;top:50%;transform:translateY(-50%);width:6px}.props-dashed ul,.props-solid ul{color:var(--article-text,#3a4259);font-size:16px;line-height:32px;margin-top:14px;padding-left:14px}.props-dashed .props-right span:before,.props-solid .props-right span:before{background:var(--article-cons,#f44242)}@media (max-width:768px){.props-dashed,.props-solid{gap:20px;grid-template-columns:1fr;margin:16px 0;padding:16px}.props-dashed span,.props-solid span{font-size:18px;line-height:21px;padding-left:10px}.props-dashed span:before,.props-solid span:before{height:10px;width:4px}.props-dashed ul,.props-solid ul{font-size:14px;line-height:21px;margin-top:10px;padding-left:10px}}
74
- </style>