mirror of
https://gitee.com/ShopeX/ECShopX_admin-frontend
synced 2026-08-08 05:35:33 +08:00
Merge branch 'agent/fs/lzm-92-decoration-dsl-contract' into 'main'
LZM-92: schema-drive decoration field contracts See merge request ecshopx/ecshopx-admin!2
This commit is contained in:
@@ -19,7 +19,9 @@
|
||||
"commit:comment": "引导设置规范化的提交信息",
|
||||
"commit": "git-cz",
|
||||
"husky": "npx husky install",
|
||||
"husky:add": "npx husky add .husky/pre-commit 'npx --no-install lint-staged' & npx husky add .husky/commit-msg 'npx --no-install commitlint --edit \"$1\"'"
|
||||
"husky:add": "npx husky add .husky/pre-commit 'npx --no-install lint-staged' & npx husky add .husky/commit-msg 'npx --no-install commitlint --edit \"$1\"'",
|
||||
"export:decoration-schema": "node scripts/export-decoration-schema.mjs ../ecshopx-web/app/decoration-engine/schema/decoration-schema.json",
|
||||
"lint:decoration-contract": "node scripts/check-decoration-contract.mjs && npm run export:decoration-schema"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g2plot": "^2.4.31",
|
||||
|
||||
118
scripts/check-decoration-contract.mjs
Normal file
118
scripts/check-decoration-contract.mjs
Normal file
@@ -0,0 +1,118 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { copyFileSync, mkdirSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
const ROOT = process.cwd()
|
||||
const tempDir = path.join(tmpdir(), 'ecx-decoration-contract')
|
||||
const tempSchemaPath = path.join(tempDir, `schema-${process.pid}.mjs`)
|
||||
mkdirSync(tempDir, { recursive: true })
|
||||
copyFileSync(
|
||||
path.resolve(ROOT, 'src/components/sp-web-decoration/definitions/schema.js'),
|
||||
tempSchemaPath
|
||||
)
|
||||
const schemaUrl = pathToFileURL(tempSchemaPath).href
|
||||
|
||||
const {
|
||||
blockFields,
|
||||
blockTypeAliases,
|
||||
sectionFields,
|
||||
sectionTypeAliases,
|
||||
toDefaultSettings
|
||||
} = await import(schemaUrl)
|
||||
rmSync(tempSchemaPath, { force: true })
|
||||
|
||||
const EXPECTED_SECTIONS = [
|
||||
'header',
|
||||
'footer',
|
||||
'announcement-bar',
|
||||
'main-carousel',
|
||||
'image-hotspot',
|
||||
'product-shelf',
|
||||
'product-tab-shelf',
|
||||
'native-product-list'
|
||||
]
|
||||
|
||||
const EXPECTED_BLOCKS = [
|
||||
'announcement',
|
||||
'header_product_list',
|
||||
'header_collection_product_list',
|
||||
'mega_menu',
|
||||
'image',
|
||||
'video',
|
||||
'hotspot',
|
||||
'product-tab',
|
||||
'footer-link',
|
||||
'footer-menu',
|
||||
'footer-image',
|
||||
'footer-text'
|
||||
]
|
||||
|
||||
function hasOwn(object, key) {
|
||||
return Object.prototype.hasOwnProperty.call(object || {}, key)
|
||||
}
|
||||
|
||||
function assertFields(groupName, schemas, expectedTypes) {
|
||||
assert.deepEqual(
|
||||
Object.keys(schemas).sort(),
|
||||
expectedTypes.slice().sort(),
|
||||
`${groupName} schema type list drifted`
|
||||
)
|
||||
|
||||
expectedTypes.forEach((type) => {
|
||||
const fields = schemas[type]
|
||||
assert.ok(fields && typeof fields === 'object', `${groupName} ${type} missing fields`)
|
||||
Object.entries(fields).forEach(([name, spec]) => {
|
||||
assert.ok(spec && typeof spec === 'object', `${groupName} ${type}.${name} missing spec`)
|
||||
assert.ok(typeof spec.type === 'string', `${groupName} ${type}.${name} missing type`)
|
||||
assert.ok(hasOwn(spec, 'default'), `${groupName} ${type}.${name} missing default`)
|
||||
if (spec.type === 'enum') {
|
||||
assert.ok(Array.isArray(spec.values), `${groupName} ${type}.${name} enum missing values`)
|
||||
assert.ok(
|
||||
spec.values.includes(spec.default),
|
||||
`${groupName} ${type}.${name} default is not in enum values`
|
||||
)
|
||||
}
|
||||
;(spec.aliases || []).forEach((alias) => {
|
||||
assert.notEqual(alias, name, `${groupName} ${type}.${name} has self alias`)
|
||||
assert.ok(
|
||||
!hasOwn(fields, alias),
|
||||
`${groupName} ${type}.${name} alias ${alias} is also a canonical field`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function assertNoAliasDefaults(groupName, schemas) {
|
||||
Object.entries(schemas).forEach(([type, fields]) => {
|
||||
const defaults = toDefaultSettings(fields)
|
||||
Object.entries(fields).forEach(([name, spec]) => {
|
||||
assert.ok(hasOwn(defaults, name), `${groupName} ${type}.${name} missing default output`)
|
||||
;(spec.aliases || []).forEach((alias) => {
|
||||
assert.ok(
|
||||
!hasOwn(defaults, alias),
|
||||
`${groupName} ${type} defaultSettings contains alias ${alias}`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
assertFields('section', sectionFields, EXPECTED_SECTIONS)
|
||||
assertFields('block', blockFields, EXPECTED_BLOCKS)
|
||||
assertNoAliasDefaults('section', sectionFields)
|
||||
assertNoAliasDefaults('block', blockFields)
|
||||
|
||||
assert.deepEqual(sectionTypeAliases, { carousel: 'main-carousel' })
|
||||
assert.deepEqual(blockTypeAliases, {})
|
||||
assert.deepEqual(sectionFields['product-shelf'].displayMode.aliases, ['sourceMode'])
|
||||
assert.deepEqual(blockFields.image.pc_image.aliases, ['imageUrl'])
|
||||
assert.deepEqual(blockFields.image.mobile_image.aliases, ['imageUrl'])
|
||||
assert.ok(blockFields['footer-menu'].menu)
|
||||
assert.ok(blockFields['footer-menu'].menu_items)
|
||||
assert.equal(blockFields['footer-menu'].menu.aliases, undefined)
|
||||
assert.ok(!blockFields['footer-link'].url.aliases?.includes('link'))
|
||||
|
||||
console.log('Decoration contract checks passed')
|
||||
26
scripts/export-decoration-schema.mjs
Normal file
26
scripts/export-decoration-schema.mjs
Normal file
@@ -0,0 +1,26 @@
|
||||
import { copyFileSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
const root = process.cwd()
|
||||
const tempDir = path.join(tmpdir(), 'ecx-decoration-contract')
|
||||
const tempSchemaPath = path.join(tempDir, `schema-${process.pid}.mjs`)
|
||||
mkdirSync(tempDir, { recursive: true })
|
||||
copyFileSync(
|
||||
path.resolve(root, 'src/components/sp-web-decoration/definitions/schema.js'),
|
||||
tempSchemaPath
|
||||
)
|
||||
const schemaUrl = pathToFileURL(tempSchemaPath).href
|
||||
const { createDecorationSchema } = await import(schemaUrl)
|
||||
rmSync(tempSchemaPath, { force: true })
|
||||
|
||||
const outputPath = process.argv[2]
|
||||
if (!outputPath) {
|
||||
throw new Error('Usage: node scripts/export-decoration-schema.mjs <output-path>')
|
||||
}
|
||||
|
||||
const resolvedOutput = path.resolve(root, outputPath)
|
||||
mkdirSync(path.dirname(resolvedOutput), { recursive: true })
|
||||
writeFileSync(resolvedOutput, `${JSON.stringify(createDecorationSchema(), null, 2)}\n`, 'utf8')
|
||||
console.log(`Exported decoration schema to ${resolvedOutput}`)
|
||||
@@ -1,9 +1,12 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'announcement',
|
||||
name: i18n.t('d65dbcac.fa86f1'),
|
||||
defaultSettings: {
|
||||
fields: blockFields.announcement,
|
||||
defaultSettings: toDefaultSettings(blockFields.announcement),
|
||||
legacyDefaultSettings: {
|
||||
text: ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'footer-image',
|
||||
name: '图片',
|
||||
defaultSettings: {
|
||||
fields: blockFields['footer-image'],
|
||||
defaultSettings: toDefaultSettings(blockFields['footer-image']),
|
||||
legacyDefaultSettings: {
|
||||
image: '',
|
||||
alignment: 'left',
|
||||
width: '100',
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
export default {
|
||||
type: 'footer-link',
|
||||
name: i18n.t('d65dbcac.406baf'),
|
||||
defaultSettings: {
|
||||
fields: blockFields['footer-link'],
|
||||
defaultSettings: toDefaultSettings(blockFields['footer-link']),
|
||||
legacyDefaultSettings: {
|
||||
label: '',
|
||||
url: '/help'
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'footer-menu',
|
||||
name: '菜单',
|
||||
defaultSettings: {
|
||||
fields: blockFields['footer-menu'],
|
||||
defaultSettings: toDefaultSettings(blockFields['footer-menu']),
|
||||
legacyDefaultSettings: {
|
||||
title: '',
|
||||
menu: null,
|
||||
size: 'small',
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'footer-text',
|
||||
name: '文本',
|
||||
defaultSettings: {
|
||||
fields: blockFields['footer-text'],
|
||||
defaultSettings: toDefaultSettings(blockFields['footer-text']),
|
||||
legacyDefaultSettings: {
|
||||
title: '',
|
||||
content: '',
|
||||
title_size: 'medium',
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'header_collection_product_list',
|
||||
name: '产品系列菜单',
|
||||
defaultSettings: {
|
||||
fields: blockFields.header_collection_product_list,
|
||||
defaultSettings: toDefaultSettings(blockFields.header_collection_product_list),
|
||||
legacyDefaultSettings: {
|
||||
title: '',
|
||||
link: '',
|
||||
columns_desktop: 4,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'header_product_list',
|
||||
name: '产品菜单',
|
||||
defaultSettings: {
|
||||
fields: blockFields.header_product_list,
|
||||
defaultSettings: toDefaultSettings(blockFields.header_product_list),
|
||||
legacyDefaultSettings: {
|
||||
title: '',
|
||||
link: '',
|
||||
columns_desktop: 4,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'hotspot',
|
||||
name: i18n.t('d65dbcac.50da72'),
|
||||
defaultSettings: {
|
||||
fields: blockFields.hotspot,
|
||||
defaultSettings: toDefaultSettings(blockFields.hotspot),
|
||||
legacyDefaultSettings: {
|
||||
label: '',
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'image',
|
||||
name: '图片',
|
||||
defaultSettings: {
|
||||
fields: blockFields.image,
|
||||
defaultSettings: toDefaultSettings(blockFields.image),
|
||||
legacyDefaultSettings: {
|
||||
pc_image: '',
|
||||
mobile_image: '',
|
||||
introduction: '提供背景或介绍,吸引用户注意',
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'mega_menu',
|
||||
name: '超级菜单',
|
||||
defaultSettings: {
|
||||
fields: blockFields.mega_menu,
|
||||
defaultSettings: toDefaultSettings(blockFields.mega_menu),
|
||||
legacyDefaultSettings: {
|
||||
mega_menu_item: '',
|
||||
alignment: 'page',
|
||||
image_position: 'right',
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'product-tab',
|
||||
name: '产品系列',
|
||||
defaultSettings: {
|
||||
fields: blockFields['product-tab'],
|
||||
defaultSettings: toDefaultSettings(blockFields['product-tab']),
|
||||
legacyDefaultSettings: {
|
||||
tab_label: '产品系列',
|
||||
product_ids: [],
|
||||
limit: 8
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { blockFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'video',
|
||||
name: i18n.t('bdd52c70.7fcf42'),
|
||||
defaultSettings: {
|
||||
fields: blockFields.video,
|
||||
defaultSettings: toDefaultSettings(blockFields.video),
|
||||
legacyDefaultSettings: {
|
||||
videoUrl: '',
|
||||
posterUrl: '',
|
||||
mobileVideoUrl: '',
|
||||
|
||||
@@ -19,6 +19,14 @@ import footerLinkBlock from './blocks/footer-link.js'
|
||||
import footerMenuBlock from './blocks/footer-menu.js'
|
||||
import footerImageBlock from './blocks/footer-image.js'
|
||||
import footerTextBlock from './blocks/footer-text.js'
|
||||
import {
|
||||
blockTypeAliases,
|
||||
getBlockFields,
|
||||
getSectionFields,
|
||||
resolveBlockType,
|
||||
resolveSectionType,
|
||||
sectionTypeAliases
|
||||
} from './schema.js'
|
||||
|
||||
const sectionDefinitions = {
|
||||
[headerSection.type]: headerSection,
|
||||
@@ -53,11 +61,11 @@ function getSectionTypesByScope(scope) {
|
||||
}
|
||||
|
||||
export function getTypedSectionDefinition(type) {
|
||||
return sectionDefinitions[type] || null
|
||||
return sectionDefinitions[resolveSectionType(type)] || null
|
||||
}
|
||||
|
||||
export function getTypedBlockDefinition(type) {
|
||||
return blockDefinitions[type] || null
|
||||
return blockDefinitions[resolveBlockType(type)] || null
|
||||
}
|
||||
|
||||
export function getSectionDefaultSettings(type) {
|
||||
@@ -68,6 +76,14 @@ export function getBlockDefaultSettings(type) {
|
||||
return getTypedBlockDefinition(type)?.defaultSettings || null
|
||||
}
|
||||
|
||||
export function getSectionSchemaFields(type) {
|
||||
return getSectionFields(type)
|
||||
}
|
||||
|
||||
export function getBlockSchemaFields(type) {
|
||||
return getBlockFields(type)
|
||||
}
|
||||
|
||||
export function getAllowedBlockTypes(sectionType) {
|
||||
return getTypedSectionDefinition(sectionType)?.blocks?.allowed?.slice() || []
|
||||
}
|
||||
@@ -80,7 +96,15 @@ export function getSectionBlockTypes(sectionType) {
|
||||
return getAllowedBlockTypes(sectionType)
|
||||
}
|
||||
|
||||
export { sectionDefinitions, blockDefinitions, getSectionTypesByScope }
|
||||
export {
|
||||
sectionDefinitions,
|
||||
blockDefinitions,
|
||||
sectionTypeAliases,
|
||||
blockTypeAliases,
|
||||
getSectionTypesByScope,
|
||||
resolveSectionType,
|
||||
resolveBlockType
|
||||
}
|
||||
|
||||
export default {
|
||||
sectionDefinitions,
|
||||
@@ -89,8 +113,12 @@ export default {
|
||||
getTypedBlockDefinition,
|
||||
getSectionDefaultSettings,
|
||||
getBlockDefaultSettings,
|
||||
getSectionSchemaFields,
|
||||
getBlockSchemaFields,
|
||||
getAllowedBlockTypes,
|
||||
getSectionTypes,
|
||||
getSectionBlockTypes,
|
||||
getSectionTypesByScope
|
||||
getSectionTypesByScope,
|
||||
resolveSectionType,
|
||||
resolveBlockType
|
||||
}
|
||||
|
||||
296
src/components/sp-web-decoration/definitions/schema.js
Normal file
296
src/components/sp-web-decoration/definitions/schema.js
Normal file
@@ -0,0 +1,296 @@
|
||||
function field(type, defaultValue, extra = {}) {
|
||||
return {
|
||||
type,
|
||||
default: defaultValue,
|
||||
...extra
|
||||
}
|
||||
}
|
||||
|
||||
export const sectionTypeAliases = {
|
||||
carousel: 'main-carousel'
|
||||
}
|
||||
|
||||
export const blockTypeAliases = {}
|
||||
|
||||
export const sectionFields = {
|
||||
header: {
|
||||
color_mode: field('enum', 'dark', { values: ['light', 'dark'] }),
|
||||
full_width: field('boolean', false),
|
||||
sticky_header_type: field('string', 'none'),
|
||||
padding_top: field('string', 'xxs'),
|
||||
menu_color_style: field('string', 'pure'),
|
||||
menu_type_desktop: field('string', 'dropdown'),
|
||||
show_line_separator: field('boolean', true),
|
||||
menu: field('object', null),
|
||||
color_scheme: field('string', 'scheme-1'),
|
||||
layout: field('string', 'middle'),
|
||||
margin_bottom: field('number', 0),
|
||||
enable_country_selector: field('boolean', true),
|
||||
mobile_logo_position: field('string', 'center'),
|
||||
menu_color_mode: field('enum', 'dark', { values: ['light', 'dark'] }),
|
||||
logo_position: field('string', 'center'),
|
||||
enable_customer_avatar: field('boolean', true),
|
||||
padding_bottom: field('string', 'xxs'),
|
||||
menu_color_scheme: field('string', 'scheme-1'),
|
||||
enable_language_selector: field('boolean', true)
|
||||
},
|
||||
footer: {
|
||||
content_alignment: field('string', 'center'),
|
||||
title: field('string', ''),
|
||||
copyright: field('string', ''),
|
||||
newsletter_heading: field('string', ''),
|
||||
color_mode: field('enum', 'dark', { values: ['light', 'dark'] }),
|
||||
margin_top: field('number', 48),
|
||||
full_width: field('boolean', false),
|
||||
padding_top: field('string', 'm'),
|
||||
color_scheme: field('string', 'scheme-3'),
|
||||
show_social: field('boolean', true),
|
||||
payment_enable: field('boolean', true),
|
||||
newsletter_enable: field('boolean', false),
|
||||
enable_country_selector: field('boolean', true),
|
||||
show_policy: field('boolean', true),
|
||||
padding_bottom: field('string', 'xs'),
|
||||
enable_brand_information: field('boolean', true),
|
||||
enable_language_selector: field('boolean', true)
|
||||
},
|
||||
'announcement-bar': {
|
||||
full_width: field('boolean', false),
|
||||
auto_rotate: field('boolean', false),
|
||||
enable_country_selector: field('boolean', false),
|
||||
color_mode: field('enum', 'light', { values: ['light', 'dark'] }),
|
||||
show_line_separator: field('boolean', true),
|
||||
change_slides_speed: field('number', 5),
|
||||
color_scheme: field('string', 'scheme-1'),
|
||||
padding_top: field('string', 'none'),
|
||||
padding_bottom: field('string', 'none'),
|
||||
show_social: field('boolean', false),
|
||||
enable_language_selector: field('boolean', false)
|
||||
},
|
||||
'main-carousel': {
|
||||
image_height: field('enum', 'medium', { values: ['small', 'medium', 'large', 'adapt'] }),
|
||||
height: field('string', ''),
|
||||
paginate_type: field('enum', 'point', { values: ['point', 'counter', 'number'] }),
|
||||
paginate_size: field('enum', 'medium', { values: ['small', 'medium', 'large'] }),
|
||||
enable_auto_play: field('boolean', false),
|
||||
enable_arrow: field('boolean', true),
|
||||
showDots: field('boolean', true),
|
||||
full_width: field('boolean', false),
|
||||
interval: field('number', 5),
|
||||
show_text_below: field('boolean', false),
|
||||
color_mode: field('enum', 'light', { values: ['light', 'dark'] }),
|
||||
color_scheme: field('string', 'scheme-1'),
|
||||
padding_top: field('string', 'm'),
|
||||
padding_bottom: field('string', 'm')
|
||||
},
|
||||
'image-hotspot': {
|
||||
pc_image: field('string', '', { aliases: ['imageUrl', 'image_url', 'image'] }),
|
||||
mobile_image: field('string', '', { aliases: ['mobileImage', 'mobile_image_url'] }),
|
||||
pc_hotspots: field('array', []),
|
||||
mobile_hotspots: field('array', []),
|
||||
full_width: field('boolean', false),
|
||||
color_mode: field('enum', 'light', { values: ['light', 'dark'] }),
|
||||
color_scheme: field('string', 'scheme-1'),
|
||||
padding_top: field('string', 'm'),
|
||||
padding_bottom: field('string', 'm')
|
||||
},
|
||||
'product-shelf': {
|
||||
title: field('string', ''),
|
||||
displayMode: field('enum', 'manual', {
|
||||
values: ['category', 'manual'],
|
||||
aliases: ['sourceMode']
|
||||
}),
|
||||
itemIds: field('string[]', []),
|
||||
categoryId: field('string', ''),
|
||||
columns: field('number', 4),
|
||||
limit: field('number', 8),
|
||||
showPrice: field('boolean', true),
|
||||
showAddCart: field('boolean', true),
|
||||
color_mode: field('enum', 'light', { values: ['light', 'dark'] }),
|
||||
color_scheme: field('string', 'scheme-1'),
|
||||
padding_top: field('string', 'm'),
|
||||
padding_bottom: field('string', 'm'),
|
||||
full_width: field('boolean', false)
|
||||
},
|
||||
'product-tab-shelf': {
|
||||
title: field('string', ''),
|
||||
intro: field('string', '新品'),
|
||||
size: field('enum', 'medium', {
|
||||
values: ['xsmall', 'small', 'medium', 'large', 'xlarge']
|
||||
}),
|
||||
alignment: field('enum', 'center', { values: ['left', 'center', 'right'] }),
|
||||
columns: field('number', 4),
|
||||
spacing: field('enum', 'medium', { values: ['none', 'small', 'medium', 'large'] }),
|
||||
full_width: field('boolean', false),
|
||||
show_price: field('boolean', true),
|
||||
show_add_cart: field('boolean', true),
|
||||
color_mode: field('enum', 'light', { values: ['light', 'dark'] }),
|
||||
color_scheme: field('string', 'scheme-1'),
|
||||
padding_top: field('string', 'm'),
|
||||
padding_bottom: field('string', 'm')
|
||||
},
|
||||
'native-product-list': {
|
||||
locked: field('boolean', true)
|
||||
}
|
||||
}
|
||||
|
||||
export const blockFields = {
|
||||
announcement: {
|
||||
text: field('string', '')
|
||||
},
|
||||
header_product_list: {
|
||||
title: field('string', ''),
|
||||
link: field('string', ''),
|
||||
columns_desktop: field('number', 4),
|
||||
full_width: field('boolean', false),
|
||||
show_discount: field('boolean', false),
|
||||
image_ratio: field('string', 'square'),
|
||||
show_secondary_image: field('boolean', false),
|
||||
color_mode: field('enum', 'light', { values: ['light', 'dark'] }),
|
||||
color_style: field('string', 'pure')
|
||||
},
|
||||
header_collection_product_list: {
|
||||
title: field('string', ''),
|
||||
link: field('string', ''),
|
||||
columns_desktop: field('number', 4),
|
||||
full_width: field('boolean', false),
|
||||
show_discount: field('boolean', false),
|
||||
image_ratio: field('string', 'adapt'),
|
||||
show_secondary_image: field('boolean', false),
|
||||
color_mode: field('enum', 'light', { values: ['light', 'dark'] }),
|
||||
color_style: field('string', 'pure')
|
||||
},
|
||||
mega_menu: {
|
||||
mega_menu_item: field('string', ''),
|
||||
alignment: field('string', 'page'),
|
||||
image_position: field('string', 'right'),
|
||||
image_length: field('string', 'one'),
|
||||
enable_image_1_link: field('boolean', false),
|
||||
enable_image_2_link: field('boolean', false),
|
||||
enable_image_3_link: field('boolean', false),
|
||||
image_layout: field('string', 'outside'),
|
||||
image_content_alignment: field('string', 'center'),
|
||||
image_ratio: field('number', 1),
|
||||
image_shape: field('string', 'none'),
|
||||
color_mode: field('enum', 'light', { values: ['light', 'dark'] }),
|
||||
color_style: field('string', 'pure')
|
||||
},
|
||||
image: {
|
||||
pc_image: field('string', '', { aliases: ['imageUrl'] }),
|
||||
mobile_image: field('string', '', { aliases: ['imageUrl'] }),
|
||||
introduction: field('string', '提供背景或介绍,吸引用户注意'),
|
||||
introduction_size: field('string', 's'),
|
||||
heading: field('string', '关键主题或焦点,简明直接'),
|
||||
heading_size: field('string', 'm'),
|
||||
description: field('string', '简要概述幻灯片内容,突出重点'),
|
||||
description_size: field('string', 's'),
|
||||
button_text: field('string', '按钮文字'),
|
||||
button_size: field('string', 'large'),
|
||||
button_style: field('string', 'primary'),
|
||||
button_scheme: field('string', 'brand'),
|
||||
content_alignment: field('string', 'left'),
|
||||
content_layout: field('string', 'top-left'),
|
||||
image_opacity: field('number', 20),
|
||||
color_mode: field('enum', 'light', { values: ['light', 'dark'] }),
|
||||
enable_card: field('boolean', false),
|
||||
color_style: field('string', 'pure')
|
||||
},
|
||||
video: {
|
||||
videoUrl: field('string', ''),
|
||||
posterUrl: field('string', ''),
|
||||
mobileVideoUrl: field('string', ''),
|
||||
introduction: field('string', '<div>1231231</div>'),
|
||||
introduction_size: field('string', 'l'),
|
||||
heading: field('string', '关键主题或焦点,简明直接'),
|
||||
heading_size: field('string', 'm'),
|
||||
description: field('string', '简要概述幻灯片内容,突出重点'),
|
||||
description_size: field('string', 'm'),
|
||||
button_text: field('string', '按钮文字'),
|
||||
button_size: field('string', 'small'),
|
||||
button_style: field('string', 'primary'),
|
||||
button_scheme: field('string', 'brand'),
|
||||
content_alignment: field('string', 'left'),
|
||||
content_layout: field('string', 'middle-left'),
|
||||
image_opacity: field('number', 15),
|
||||
color_mode: field('enum', 'light', { values: ['light', 'dark'] }),
|
||||
enable_card: field('boolean', true),
|
||||
color_style: field('string', 'pure')
|
||||
},
|
||||
hotspot: {
|
||||
label: field('string', ''),
|
||||
x: field('number', 0),
|
||||
y: field('number', 0),
|
||||
link: field('string', ''),
|
||||
shape: field('enum', 'circle', { values: ['circle', 'rect'] })
|
||||
},
|
||||
'product-tab': {
|
||||
tab_label: field('string', '产品系列'),
|
||||
product_ids: field('string[]', []),
|
||||
product_snapshots: field('array', []),
|
||||
limit: field('number', 8)
|
||||
},
|
||||
'footer-link': {
|
||||
label: field('string', ''),
|
||||
url: field('string', '/help')
|
||||
},
|
||||
'footer-menu': {
|
||||
title: field('string', ''),
|
||||
menu: field('object', null),
|
||||
menu_items: field('array', []),
|
||||
size: field('string', 'small'),
|
||||
alignment: field('string', 'left'),
|
||||
column_span: field('number', 3)
|
||||
},
|
||||
'footer-image': {
|
||||
image: field('string', ''),
|
||||
alignment: field('string', 'left'),
|
||||
width: field('string', '100'),
|
||||
radius: field('string', 'medium'),
|
||||
column_span: field('number', 4)
|
||||
},
|
||||
'footer-text': {
|
||||
title: field('string', ''),
|
||||
content: field('string', ''),
|
||||
title_size: field('string', 'medium'),
|
||||
content_size: field('string', 'small'),
|
||||
alignment: field('string', 'left'),
|
||||
column_span: field('number', 6)
|
||||
}
|
||||
}
|
||||
|
||||
export function cloneValue(value) {
|
||||
return value === undefined ? undefined : JSON.parse(JSON.stringify(value))
|
||||
}
|
||||
|
||||
export function toDefaultSettings(fields = {}) {
|
||||
return Object.entries(fields).reduce((settings, [name, spec]) => {
|
||||
settings[name] = cloneValue(spec.default)
|
||||
return settings
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function resolveSectionType(type) {
|
||||
return sectionTypeAliases[type] || type
|
||||
}
|
||||
|
||||
export function resolveBlockType(type) {
|
||||
return blockTypeAliases[type] || type
|
||||
}
|
||||
|
||||
export function getSectionFields(type) {
|
||||
return sectionFields[resolveSectionType(type)] || null
|
||||
}
|
||||
|
||||
export function getBlockFields(type) {
|
||||
return blockFields[resolveBlockType(type)] || null
|
||||
}
|
||||
|
||||
export function createDecorationSchema() {
|
||||
return {
|
||||
sections: cloneValue(sectionFields),
|
||||
blocks: cloneValue(blockFields),
|
||||
aliases: {
|
||||
sections: cloneValue(sectionTypeAliases),
|
||||
blocks: cloneValue(blockTypeAliases)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { sectionFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'announcement-bar',
|
||||
scope: 'header',
|
||||
name: i18n.t('d65dbcac.3921b6'),
|
||||
defaultSettings: {
|
||||
fields: sectionFields['announcement-bar'],
|
||||
defaultSettings: toDefaultSettings(sectionFields['announcement-bar']),
|
||||
legacyDefaultSettings: {
|
||||
full_width: false,
|
||||
auto_rotate: false,
|
||||
enable_country_selector: false,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { sectionFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'main-carousel',
|
||||
scope: 'template',
|
||||
name: i18n.t('d65dbcac.0c0180'),
|
||||
defaultSettings: {
|
||||
fields: sectionFields['main-carousel'],
|
||||
defaultSettings: toDefaultSettings(sectionFields['main-carousel']),
|
||||
legacyDefaultSettings: {
|
||||
image_height: 'medium',
|
||||
paginate_type: 'point',
|
||||
paginate_size: 'medium',
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { sectionFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'footer',
|
||||
scope: 'footer',
|
||||
fixedId: 'footer',
|
||||
name: i18n.t('d65dbcac.4eb88f'),
|
||||
defaultSettings: {
|
||||
fields: sectionFields.footer,
|
||||
defaultSettings: toDefaultSettings(sectionFields.footer),
|
||||
legacyDefaultSettings: {
|
||||
content_alignment: 'center',
|
||||
title: '',
|
||||
copyright: '',
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { sectionFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'header',
|
||||
scope: 'header',
|
||||
fixedId: 'header',
|
||||
name: i18n.t('d65dbcac.917f14'),
|
||||
defaultSettings: {
|
||||
fields: sectionFields.header,
|
||||
defaultSettings: toDefaultSettings(sectionFields.header),
|
||||
legacyDefaultSettings: {
|
||||
color_mode: 'dark',
|
||||
full_width: false,
|
||||
sticky_header_type: 'none',
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { sectionFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'image-hotspot',
|
||||
scope: 'template',
|
||||
name: i18n.t('d65dbcac.081a81'),
|
||||
defaultSettings: {
|
||||
fields: sectionFields['image-hotspot'],
|
||||
defaultSettings: toDefaultSettings(sectionFields['image-hotspot']),
|
||||
legacyDefaultSettings: {
|
||||
pc_image: '',
|
||||
mobile_image: '',
|
||||
pc_hotspots: [],
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { sectionFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'native-product-list',
|
||||
scope: 'template',
|
||||
@@ -6,7 +8,9 @@ export default {
|
||||
native: true,
|
||||
locked: true,
|
||||
addable: false,
|
||||
defaultSettings: {
|
||||
fields: sectionFields['native-product-list'],
|
||||
defaultSettings: toDefaultSettings(sectionFields['native-product-list']),
|
||||
legacyDefaultSettings: {
|
||||
locked: true
|
||||
},
|
||||
defaultBlocks: [],
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import { sectionFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'product-shelf',
|
||||
scope: 'template',
|
||||
name: i18n.t('d65dbcac.4a02ad'),
|
||||
defaultSettings: {
|
||||
fields: sectionFields['product-shelf'],
|
||||
defaultSettings: toDefaultSettings(sectionFields['product-shelf']),
|
||||
legacyDefaultSettings: {
|
||||
title: '',
|
||||
sourceMode: 'manual',
|
||||
itemIds: [],
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { sectionFields, toDefaultSettings } from '../schema.js'
|
||||
|
||||
export default {
|
||||
type: 'product-tab-shelf',
|
||||
scope: 'template',
|
||||
name: 'Tab产品系列',
|
||||
defaultSettings: {
|
||||
fields: sectionFields['product-tab-shelf'],
|
||||
defaultSettings: toDefaultSettings(sectionFields['product-tab-shelf']),
|
||||
legacyDefaultSettings: {
|
||||
title: '',
|
||||
intro: '新品',
|
||||
size: 'medium',
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">图片</div>
|
||||
<div class="rounded-xl border border-border bg-card p-3">
|
||||
<SpImagePicker :value="value.image" @input="updateField('image', $event)" />
|
||||
<SpImagePicker :value="settings.image" @input="updateField('image', $event)" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">对齐方式</div>
|
||||
<el-radio-group
|
||||
:value="value.alignment || 'left'"
|
||||
:value="settings.alignment"
|
||||
size="small"
|
||||
@input="updateField('alignment', $event)"
|
||||
>
|
||||
@@ -23,7 +23,7 @@
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">图片宽度</div>
|
||||
<el-radio-group
|
||||
:value="value.width || '100'"
|
||||
:value="settings.width"
|
||||
size="small"
|
||||
@input="updateField('width', $event)"
|
||||
>
|
||||
@@ -37,7 +37,7 @@
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">圆角</div>
|
||||
<el-radio-group
|
||||
:value="value.radius || 'medium'"
|
||||
:value="settings.radius"
|
||||
size="small"
|
||||
@input="updateField('radius', $event)"
|
||||
>
|
||||
@@ -53,7 +53,7 @@
|
||||
<div class="text-sm text-muted-foreground">占列宽度</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<el-input-number
|
||||
:value="value.column_span || 4"
|
||||
:value="settings.column_span"
|
||||
:min="1"
|
||||
:max="12"
|
||||
size="small"
|
||||
@@ -62,7 +62,7 @@
|
||||
@change="updateField('column_span', $event)"
|
||||
/>
|
||||
<el-slider
|
||||
:value="value.column_span || 4"
|
||||
:value="settings.column_span"
|
||||
:min="1"
|
||||
:max="12"
|
||||
:step="1"
|
||||
@@ -77,6 +77,7 @@
|
||||
|
||||
<script>
|
||||
import SpImagePicker from '@/components/sp-image-picker/index.vue'
|
||||
import { normalizeTypedBlockSettings } from '../utils/panelState.js'
|
||||
|
||||
export default {
|
||||
name: 'FooterImageBlockPanel',
|
||||
@@ -87,6 +88,11 @@ export default {
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return normalizeTypedBlockSettings('footer-image', this.value)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateField(field, fieldValue) {
|
||||
this.$emit('change', {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<template>
|
||||
<el-form label-position="top" size="small" class="space-y-3">
|
||||
<el-form-item :label="$t('5702d2d6.14d342')">
|
||||
<el-input :value="value.label" @input="updateField('label', $event)" />
|
||||
<el-input :value="settings.label" @input="updateField('label', $event)" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('5702d2d6.dec2eb')">
|
||||
<el-input :value="value.url" @input="updateField('url', $event)" />
|
||||
<el-input :value="settings.url" @input="updateField('url', $event)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { normalizeTypedBlockSettings } from '../utils/panelState.js'
|
||||
|
||||
export default {
|
||||
name: 'FooterLinkBlockPanel',
|
||||
props: {
|
||||
@@ -19,6 +21,11 @@ export default {
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return normalizeTypedBlockSettings('footer-link', this.value)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateField(field, fieldValue) {
|
||||
this.$emit('change', {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">标题</div>
|
||||
<SpRichTextEditor
|
||||
:value="value.title"
|
||||
:value="settings.title"
|
||||
placeholder="请输入标题"
|
||||
@input="updateField('title', $event)"
|
||||
/>
|
||||
@@ -46,7 +46,7 @@
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">规格</div>
|
||||
<el-select
|
||||
:value="value.size || 'small'"
|
||||
:value="settings.size"
|
||||
size="small"
|
||||
class="w-full"
|
||||
@change="updateField('size', $event)"
|
||||
@@ -62,7 +62,7 @@
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">对齐方式</div>
|
||||
<el-radio-group
|
||||
:value="value.alignment || 'left'"
|
||||
:value="settings.alignment"
|
||||
size="small"
|
||||
@input="updateField('alignment', $event)"
|
||||
>
|
||||
@@ -76,7 +76,7 @@
|
||||
<div class="text-sm text-muted-foreground">占列宽度</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<el-input-number
|
||||
:value="value.column_span || 3"
|
||||
:value="settings.column_span"
|
||||
:min="1"
|
||||
:max="12"
|
||||
size="small"
|
||||
@@ -85,7 +85,7 @@
|
||||
@change="updateField('column_span', $event)"
|
||||
/>
|
||||
<el-slider
|
||||
:value="value.column_span || 3"
|
||||
:value="settings.column_span"
|
||||
:min="1"
|
||||
:max="12"
|
||||
:step="1"
|
||||
@@ -103,6 +103,7 @@ import { Menu as MenuIcon, Trash2 } from 'lucide-vue'
|
||||
import WebNavPicker from '@/components/sp-picker-plus/WebNavPicker.vue'
|
||||
import SpRichTextEditor from '@/components/sp-rich-text-editor/index.vue'
|
||||
import { normalizeSelectedMenu, pickMenuValue } from '../utils/menuSelection.js'
|
||||
import { normalizeTypedBlockSettings } from '../utils/panelState.js'
|
||||
|
||||
export default {
|
||||
name: 'FooterMenuBlockPanel',
|
||||
@@ -123,8 +124,11 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return normalizeTypedBlockSettings('footer-menu', this.value)
|
||||
},
|
||||
currentMenu() {
|
||||
return normalizeSelectedMenu(pickMenuValue(this.value))
|
||||
return normalizeSelectedMenu(pickMenuValue(this.settings))
|
||||
},
|
||||
currentMenuLabel() {
|
||||
return this.currentMenu?.name || this.menuNameCache || '未选择菜单'
|
||||
|
||||
@@ -2,25 +2,29 @@
|
||||
<div class="space-y-5 text-foreground">
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">{{ $t('6c727f2c.5a02fa') }}</div>
|
||||
<el-input :value="value.title" size="small" @input="updateField('title', $event)" />
|
||||
<el-input :value="settings.title" size="small" @input="updateField('title', $event)" />
|
||||
</section>
|
||||
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">{{ $t('6c727f2c.f0c7e9') }}</div>
|
||||
<el-select
|
||||
:value="value.sourceMode"
|
||||
:value="settings.displayMode"
|
||||
size="small"
|
||||
class="w-full"
|
||||
@input="updateField('sourceMode', $event)"
|
||||
@input="updateField('displayMode', $event)"
|
||||
>
|
||||
<el-option :label="$t('6c727f2c.68924b')" value="manual" />
|
||||
<el-option :label="$t('6c727f2c.d282eb')" value="category" />
|
||||
</el-select>
|
||||
</section>
|
||||
|
||||
<section v-if="value.sourceMode === 'category'" class="space-y-2">
|
||||
<section v-if="settings.displayMode === 'category'" class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">{{ $t('6c727f2c.ae8c70') }}</div>
|
||||
<el-input :value="value.categoryId" size="small" @input="updateField('categoryId', $event)" />
|
||||
<el-input
|
||||
:value="settings.categoryId"
|
||||
size="small"
|
||||
@input="updateField('categoryId', $event)"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section v-else class="space-y-2">
|
||||
@@ -38,7 +42,7 @@
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">{{ $t('6c727f2c.eb22d4') }}</div>
|
||||
<el-input-number
|
||||
:value="value.columns"
|
||||
:value="settings.columns"
|
||||
:min="2"
|
||||
:max="6"
|
||||
size="small"
|
||||
@@ -51,7 +55,7 @@
|
||||
<section class="space-y-2">
|
||||
<div class="text-sm text-muted-foreground">{{ $t('6c727f2c.d57936') }}</div>
|
||||
<el-input-number
|
||||
:value="value.limit"
|
||||
:value="settings.limit"
|
||||
:min="1"
|
||||
:max="50"
|
||||
size="small"
|
||||
@@ -62,12 +66,13 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<SectionAppearancePanel :value="value" @change="updateField" />
|
||||
<SectionAppearancePanel :value="settings" @change="updateField" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SectionAppearancePanel from './SectionAppearancePanel.vue'
|
||||
import { normalizeTypedSectionSettings } from '../utils/panelState.js'
|
||||
|
||||
export default {
|
||||
name: 'ProductShelfPanel',
|
||||
@@ -79,8 +84,11 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return normalizeTypedSectionSettings('product-shelf', this.value)
|
||||
},
|
||||
itemIdsText() {
|
||||
return Array.isArray(this.value.itemIds) ? this.value.itemIds.join(',') : ''
|
||||
return Array.isArray(this.settings.itemIds) ? this.settings.itemIds.join(',') : ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createFooterDocumentDsl } from '../definitions/documents/footer.js'
|
||||
import { createGlobalSections } from '../definitions/documents/global.js'
|
||||
import { createTypedSection } from '../definitions/factory.js'
|
||||
import { generateBlockId } from './nanoid.js'
|
||||
import { normalizeTypedBlockSettings, normalizeTypedSectionSettings } from './panelState.js'
|
||||
|
||||
function cloneValue(value) {
|
||||
return JSON.parse(JSON.stringify(value))
|
||||
@@ -367,8 +368,12 @@ export function serializeDsl(dsl) {
|
||||
const nextDsl = cloneValue(dsl)
|
||||
Object.keys(nextDsl.sections || {}).forEach((sectionId) => {
|
||||
const section = nextDsl.sections[sectionId]
|
||||
section.settings = normalizeTypedSectionSettings(section.type, section.settings)
|
||||
Object.keys(section.blocks || {}).forEach((blockId) => {
|
||||
const block = section.blocks[blockId]
|
||||
if (block?.type) {
|
||||
block.settings = normalizeTypedBlockSettings(block.type, block.settings)
|
||||
}
|
||||
if (block?.type === 'product-tab' && block.settings) {
|
||||
delete block.settings.size
|
||||
delete block.settings.size_override
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {
|
||||
getTypedBlockDefinition,
|
||||
getBlockDefaultSettings,
|
||||
getBlockSchemaFields,
|
||||
getSectionDefaultSettings,
|
||||
getBlockDefaultSettings
|
||||
getSectionSchemaFields
|
||||
} from '../definitions/registry.js'
|
||||
import { normalizeBySchema } from './schemaNormalize.js'
|
||||
|
||||
function cloneValue(value) {
|
||||
return JSON.parse(JSON.stringify(value))
|
||||
@@ -26,7 +29,6 @@ function clampCoordinate(value) {
|
||||
function normalizeProductShelfCompatibleSettings(settings) {
|
||||
const nextSettings = { ...settings }
|
||||
|
||||
nextSettings.displayMode = nextSettings.displayMode || nextSettings.sourceMode || 'category'
|
||||
nextSettings.itemIds = Array.isArray(nextSettings.itemIds)
|
||||
? nextSettings.itemIds.filter(Boolean).slice(0, nextSettings.limit || 8)
|
||||
: []
|
||||
@@ -73,7 +75,10 @@ function normalizeHotspotCompatibleSettings(settings) {
|
||||
}
|
||||
|
||||
function normalizeSectionSettings(type, settings = {}) {
|
||||
const nextSettings = mergeWithDefaults(getSectionDefaultSettings(type), settings)
|
||||
const fields = getSectionSchemaFields(type)
|
||||
const nextSettings = fields
|
||||
? normalizeBySchema(fields, settings)
|
||||
: mergeWithDefaults(getSectionDefaultSettings(type), settings)
|
||||
|
||||
if (type === 'product-shelf') return normalizeProductShelfCompatibleSettings(nextSettings)
|
||||
if (type === 'product-tab-shelf') return normalizeProductTabShelfSettings(nextSettings)
|
||||
@@ -81,7 +86,10 @@ function normalizeSectionSettings(type, settings = {}) {
|
||||
}
|
||||
|
||||
function normalizeBlockSettingsValue(type, settings = {}) {
|
||||
const nextSettings = mergeWithDefaults(getBlockDefaultSettings(type), settings)
|
||||
const fields = getBlockSchemaFields(type)
|
||||
const nextSettings = fields
|
||||
? normalizeBySchema(fields, settings)
|
||||
: mergeWithDefaults(getBlockDefaultSettings(type), settings)
|
||||
|
||||
if (type === 'hotspot') return normalizeHotspotCompatibleSettings(nextSettings)
|
||||
if (type === 'product-tab') return normalizeProductTabBlockSettings(nextSettings)
|
||||
|
||||
27
src/components/sp-web-decoration/utils/schemaNormalize.js
Normal file
27
src/components/sp-web-decoration/utils/schemaNormalize.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { cloneValue } from '../definitions/schema.js'
|
||||
|
||||
export function normalizeBySchema(fields, settings = {}) {
|
||||
if (!fields) return cloneValue(settings || {})
|
||||
|
||||
const source = settings && typeof settings === 'object' ? settings : {}
|
||||
const out = {}
|
||||
|
||||
Object.entries(fields).forEach(([name, spec]) => {
|
||||
let value = source[name]
|
||||
if (value === undefined && Array.isArray(spec.aliases)) {
|
||||
for (const alias of spec.aliases) {
|
||||
if (source[alias] !== undefined) {
|
||||
value = source[alias]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
out[name] = value === undefined ? cloneValue(spec.default) : cloneValue(value)
|
||||
})
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
export default {
|
||||
normalizeBySchema
|
||||
}
|
||||
Reference in New Issue
Block a user