产品组样式联动,文字引用联动

main
一杯沧海 12 months ago
parent a635eead2a
commit 7666f0a378
  1. 691
      src/App.vue
  2. 26
      src/engineer/components/ConfigCurrentView.vue
  3. 36
      src/engineer/context.ts
  4. 18
      src/engineer/styles/NumberStyle.vue
  5. 3
      src/engineer/types.ts
  6. 3
      src/engineer/views/RenderTemplate.vue
  7. 1
      src/engineer/views/TextView.vue

@ -132,382 +132,409 @@ const categories = ref([{ icon: 'trash', text: '媒体' }, { icon: 'trash', text
categories.value.unshift({ categories.value.unshift({
icon: 'trash', icon: 'trash',
text: '基础', text: '基础',
modules: [{ modules: [
vid: hash(`${++nextId}`), {
mid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
title: '轮播图', mid: hash(`${++nextId}`),
maxReferenceCount: -1, title: '轮播图',
referenceCount: 0, maxReferenceCount: -1,
image: undefined, referenceCount: 0,
configs: [ image: undefined,
{ configs: [
type: 'list', {
field: 'items', type: 'list',
label: '轮播图设置', field: 'items',
help: '最多可添加10张图片,建议宽度750px;鼠标拖拽左侧圆点可调整图片顺序', label: '轮播图设置',
addable: true, help: '最多可添加10张图片,建议宽度750px;鼠标拖拽左侧圆点可调整图片顺序',
configs: [{ addable: true,
type: 'image', configs: [{
field: 'image', type: 'image',
label: '图片', field: 'image',
required: true, label: '图片',
// required: true,
inlines: [{ //
type: 'text', inlines: [{
field: 'title', type: 'text',
label: '标题', field: 'title',
help: '选填,不超过 4 个字', label: '标题',
help: '选填,不超过 4 个字',
}, {
type: 'text',
field: 'link',
label: '链接',
help: '请输入链接', // "${label}"
}],
}],
},
{
type: 'object',
field: 'indicator',
label: '指示器',
configs: [{
type: 'mark',
field: 'style',
label: '指示器样式',
values: [
{ label: '圆形', value: '#circle' },
{ label: '直线', value: '#line' },
{ label: '数字', value: '#number' },
],
}, { }, {
type: 'text', type: 'mark',
field: 'link', field: 'position',
label: '链接', label: '指示器位置',
help: '请输入链接', // "${label}" values: [
{ label: '居左', value: 'start' },
{ label: '居中', value: 'center' },
{ label: '居右', value: 'end' },
],
},
{
type: 'color',
field: 'color',
label: '指示器颜色',
}], }],
}],
},
{
type: 'object',
field: 'indicator',
label: '指示器',
configs: [{
type: 'mark',
field: 'style',
label: '指示器样式',
values: [
{ label: '圆形', value: '#circle' },
{ label: '直线', value: '#line' },
{ label: '数字', value: '#number' },
],
}, {
type: 'mark',
field: 'position',
label: '指示器位置',
values: [
{ label: '居左', value: 'start' },
{ label: '居中', value: 'center' },
{ label: '居右', value: 'end' },
],
}, },
{ {
type: 'color', type: 'object',
field: 'color', field: 'background',
label: '指示器颜色',
}],
},
{
type: 'object',
field: 'background',
label: '背景',
configs: [{
type: 'boolean',
field: 'enabled',
label: '是否显示背景色',
}, {
type: 'background',
field: 'value',
// image
//
features: ['color', 'gradient'],
label: '背景', label: '背景',
}], configs: [{
} type: 'boolean',
], field: 'enabled',
init: { label: '是否显示背景色',
items: [ }, {
{ type: 'background',
image: '', field: 'value',
title: '123', // image
link: '123', //
features: ['color', 'gradient'],
label: '背景',
}],
} }
], ],
indicator: { init: {
style: '#circle', items: [
color: 'green', {
position: 'start', image: '',
}, title: '123',
background: { link: '123',
enabled: true,
value: "#ffffff",
},
theme: {
color: 'white',
radius: 24
}
},
templates: {
'#circle': {
theme: {
gap: 20,
},
children: {
type: 'each',
key: 'items',
handle: {
theme: {
width: 20,
height: 20,
radius: 10,
color: '@@indicator.color',
},
} }
],
indicator: {
style: '#circle',
color: 'green',
position: 'start',
},
background: {
enabled: true,
value: "#ffffff",
},
theme: {
color: 'white',
radius: 24
} }
}, },
'#line': { templates: {
theme: { '#circle': {
gap: 20, theme: {
gap: 20,
},
children: {
type: 'each',
key: 'items',
handle: {
theme: {
width: 20,
height: 20,
radius: 10,
color: '@@indicator.color',
},
}
}
}, },
children: { '#line': {
type: 'each', theme: {
key: 'items', gap: 20,
handle: { },
theme: { children: {
width: 40, type: 'each',
height: 20, key: 'items',
color: '@@indicator.color', handle: {
}, theme: {
width: 40,
height: 20,
color: '@@indicator.color',
},
}
} }
},
"#number": {
theme: {
color: 'rgba(0,0,0,0.4)',
textColor: '#fff'
},
children: '1/len(items)'
} }
}, },
"#number": { theme: {
theme: { position: 'relative',
color: 'rgba(0,0,0,0.4)', height: 200,
textColor: '#fff' padding: {
horizontal: 12.0,
vertical: 12,
}, },
children: '1/len(items)' color: 'cyan',
}
},
theme: {
position: 'relative',
height: 200,
padding: {
horizontal: 12.0,
vertical: 12,
}, },
color: 'cyan', children: [
{
vid: hash(`${++nextId}`),
theme: {
width: '100%',
height: '100%',
color: '@@theme.color',
radius: "@@theme.radius",
textAlign: 'center',
},
children: '设置轮播图',
},
{
theme: {
position: 'absolute',
flexible: true,
mainAlign: '@@indicator.position',
bottom: 20,
left: 20,
right: 20,
height: 20,
color: 'red',
},
children: {
type: 'template',
key: 'indicator.style'
}
}],
}, },
children: [{ {
title: '产品组',
vid: hash(`${++nextId}`), vid: hash(`${++nextId}`),
theme: { mid: hash(`${++nextId}`),
width: '100%', maxReferenceCount: -1,
height: '100%', referenceCount: 0,
color: '@@theme.color', image: undefined,
radius: "@@theme.radius", configs: [
textAlign: 'center', {
}, type: 'object',
children: '设置轮播图', field: 'titleAndDesc',
}, { label: '产品标题及说明',
theme: { configs: [
position: 'absolute', {
flexible: true,
mainAlign: '@@indicator.position',
bottom: 20,
left: 20,
right: 20,
height: 20,
color: 'red',
},
// children: {
// type: 'each',
// key: 'indicator.position',
// handle: {
// theme: {
// width: 20,
// height: 20,
// radius: 10,
// color: 'black',
// }
// },
// }
children: {
type: 'template',
key: 'indicator.style'
}
}],
},
{
vid: hash(`${++nextId}`),
mid: hash(`${++nextId}`),
title: '产品组',
maxReferenceCount: -1,
referenceCount: 0,
image: undefined,
configs: [
{
type: 'object',
field: 'titleAndDesc',
label: '产品标题及说明',
configs: [
{
type: 'text',
label: '产品标题',
field: 'mainTitle'
},
{
type: 'text',
label: '说明',
field: 'content',
},
]
},
{
help: '最多可添加10张图片,建议宽度750px;鼠标拖拽左侧圆点可调整图片顺序',
addable: true,
type: 'list',
field: 'groups',
label: '产品组列表',
configs: [
{
type: 'image',
label: '产品图片',
field: 'productPhoto',
//
inlines: [{
type: 'text', type: 'text',
field: 'link', label: '产品标题',
label: '链接', field: 'mainTitle'
help: '请输入链接', // "${label}"
}, },
{ {
type: 'text', type: 'text',
label: '自定义标题', label: '说明',
field: 'productTitle', field: 'content',
}, },
{ {
type: 'text', type: 'text',
label: '自定义说明', label: '提示',
field: 'productDesc', field: 'more',
}, },
] ]
}, },
]
},
],
init: {
titleAndDesc: {
mainTitle: '标题',
content: '说明',
},
groups: [
{ {
help: '帮助',
addable: true,
type: 'list',
field: 'groups',
label: '产品组列表',
configs: [
{
type: 'image',
label: '产品图片',
field: 'productPhoto',
//
inlines: [{
type: 'text',
field: 'link',
label: '链接',
help: '请输入链接', // "${label}"
},
{
type: 'text',
label: '自定义标题',
field: 'productTitle',
},
{
type: 'text',
label: '自定义说明',
field: 'productDesc',
},
]
},
productTitle: '111', ]
} },
], ],
init: {
}, titleAndDesc: {
theme: { mainTitle: '标题123',
height: 200, content: '说明123',
padding: { more: '查看更多',
horizontal: 12.0, },
groups: [
{
productTitle: '111',
}
],
theme: {
color: 'blue',
},
children:{
theme: {
radius: 12
},
}
}, },
color: 'pink',
},
children: {
vid: hash(`${++nextId}`),
theme: { theme: {
width: '100%', height: 200,
height: '100%', padding: 12,
color: 'white', color: '@@theme.color',
radius: 12.0,
textAlign: 'center',
}, },
children: [ children: {
{ vid: hash(`${++nextId}`),
theme: { theme: {
flexible: true, width: '100%',
gap: 12, height: '100%',
crossAlign: 'center', color: 'white',
}, clip: 'hidden',
children: [ radius: '@@children.theme.radius',
{ textAlign: 'center',
theme: { },
padding: { children: [
horizontal: 5 {
}, theme: {
}, flexible: true,
children: [ gap: 12,
{ crossAlign: 'center',
theme: { },
flexible: true, children: [
gap: 5, {
crossAlign: 'center', theme: {
padding: {
horizontal: 5
}, },
children: [ },
{ children: [
theme: { {
fontSize: 16, theme: {
}, flexible: true,
children: '左侧标题', gap: 5,
crossAlign: 'center',
}, },
{ children: [
theme: { {
fontSize: 16, theme: {
fontSize: 16,
},
children: {
type: 'text',
key: 'titleAndDesc.mainTitle'
},
}, },
children: '左侧说明', {
} theme: {
] fontSize: 16,
}, },
] children: {
}, type: 'text',
{ key: 'titleAndDesc.content'
theme: { },
grow: 1, }
} ]
}, },
{ ]
vid: hash(`${++nextId}`), },
theme: { {
width: 'auto', theme: {
margin: { grow: 1,
horizontal: 5, }
},
{
vid: hash(`${++nextId}`),
theme: {
width: 'auto',
margin: {
horizontal: 5,
},
fontSize: 12,
padding: {
vertical: 1,
horizontal: 5,
},
color: '@@children.children.children.children.children.2.color',
textColor: '#fff',
radius: 16,
}, },
fontSize: 12, children: {
padding: { type: 'text',
vertical: 1, key: 'titleAndDesc.more'
horizontal: 5,
}, },
color: 'pink',
radius: 16,
}, },
children: '查看更多',
},
], ],
},
{
vid: hash(`${++nextId}`),
theme: {
width: "94%",
height: "70%",
color: 'pink',
radius: 12.0,
margin: {
top: '1%',
left: '3%',
right: '3%',
bottom: '2%',
},
padding: {
horizontal: 3,
}
}, },
children: [{ {
vid: hash(`${++nextId}`),
theme: { theme: {
width: '35%', width: "94%",
height: '100%', height: "70%",
mainAlign: "center", color: 'pink',
color: '#fff', radius: 12.0,
crossAlign: "center" margin: {
top: '1%',
left: '3%',
right: '3%',
bottom: '2%',
},
padding: {
horizontal: 3,
},
gap: 3,
clip: 'autoX',
}, },
children: '产品' children:
{
type: 'each',
key: 'groups',
handle: {
theme: {
width: '100px',
height: '100%',
radius: 10,
color: '#fff',
},
}
},
// [{
// theme: {
// width: '35%',
// height: '100%',
// mainAlign: "center",
// color: '#fff',
// crossAlign: "center"
//
// },
// children: { }
// },
// ]
}, },
] ]
}, }
]
} }
}
], ],
}) })

@ -3,6 +3,7 @@ import type { View } from '../types';
import { computed } from 'vue'; import { computed } from 'vue';
import { useContext } from '../context'; import { useContext } from '../context';
import ColorConfig from '../configs/ColorConfig.vue'; import ColorConfig from '../configs/ColorConfig.vue';
import NumberStyle from '../styles/NumberStyle.vue'
defineOptions({ defineOptions({
name: 'ConfigCurrentView', name: 'ConfigCurrentView',
@ -17,17 +18,30 @@ const styles = computed(() => {
return undefined return undefined
} }
return Object.entries(view.value.theme).filter(([, value]) => { return Object.entries(view.value.theme).filter(([, value]) => {
return typeof value === 'string' && value.startsWith('@@theme.') return typeof value === 'string' && value.startsWith('@@')
}) as Array<[string, string]> }) as Array<[string, string]>
}) })
const label = computed(()=>{
if(!view.value?.title) {
return undefined
}
return view.value.title
})
</script> </script>
<template> <template>
{{ ctx.activeViewId }} {{ ctx.activeViewId }}
<hr/> <hr/>
<template v-for="([key, value]) in styles" :key="key"> <fieldset>
<ColorConfig v-if="key === 'color'" :field="value.slice(2)" label="背景颜色" /> <legend>{{ label }}设置</legend>
<div v-else>{{ key }}:{{ value }}</div> <template v-for="([key, value]) in styles" :key="key">
</template> <ColorConfig v-if="key === 'color'" :field="value.slice(2)" label="背景颜色" />
<pre><code>{{ view }}</code></pre> <NumberStyle v-if="key === 'radius'" :field="value.slice(2)" label="圆角设置" />
<!-- <div v-else>{{ key }}:{{ value }}</div>-->
</template>
</fieldset>
<!--<pre><code>{{ view }}</code></pre>-->
</template> </template>

@ -51,7 +51,7 @@ export interface EngineContextBase {
nextId: number nextId: number
/** 数据源 */ /** 数据源 */
sources: Record<string, Record<string, unknown>> sources: Record<string, Record<string, unknown>>
/** 到处数据 */ /** 导出数据 */
export: () => Exported export: () => Exported
/** 获取动态数据 */ /** 获取动态数据 */
value: <T>(blockId: string, key: string) => T | undefined value: <T>(blockId: string, key: string) => T | undefined
@ -62,7 +62,7 @@ export type EngineContext = UnwrapNestedRefs<EngineContextBase>
export const contextKey: InjectionKey<EngineContext> = Symbol.for('ddd:engine') export const contextKey: InjectionKey<EngineContext> = Symbol.for('ddd:engine')
const must = <T>(v: T | undefined, error: string): T => { const must = <T>(v: T | undefined, error: string): T => {
if (v == null) { if(v==null){
throw new Error(error) throw new Error(error)
} }
return v return v
@ -74,8 +74,8 @@ export function useContext(): EngineContext {
export const parentViewIdKey: InjectionKey<string> = Symbol.for('ddd:view:parent:id') export const parentViewIdKey: InjectionKey<string> = Symbol.for('ddd:view:parent:id')
export function useParentViewId(): string | undefined { export function useParentViewId(): string {
return inject(parentViewIdKey) return must(inject(parentViewIdKey), 'no parentViewId no found')
} }
export function provideParentViewId(id: string | undefined): void { export function provideParentViewId(id: string | undefined): void {
@ -86,7 +86,7 @@ export function provideParentViewId(id: string | undefined): void {
const blockIdKey: InjectionKey<string> = Symbol.for('ddd:block:id') const blockIdKey: InjectionKey<string> = Symbol.for('ddd:block:id')
export function useBlockId(): string { export const useBlockId = (): string => {
return must(inject(blockIdKey), "no block found") return must(inject(blockIdKey), "no block found")
} }
@ -94,8 +94,9 @@ export function provideBlockId(id: string): void {
provide(blockIdKey, id) provide(blockIdKey, id)
} }
export const currentViewDOMRectKey: InjectionKey<UnwrapNestedRefs<Partial<DOMRect>>> = Symbol.for('ddd:view:current:domrect') type ReactingDomRect = InjectionKey<UnwrapNestedRefs<Partial<DOMRect>>>
export const parentViewDOMRectKey: InjectionKey<UnwrapNestedRefs<Partial<DOMRect>>> = Symbol.for('ddd:view:parent:domrect') export const currentViewDOMRectKey: ReactingDomRect = Symbol.for('ddd:view:current:domrect')
export const parentViewDOMRectKey: ReactingDomRect = Symbol.for('ddd:view:parent:domrect')
export function useCurrentViewDOMRect(): UnwrapNestedRefs<Partial<DOMRect>> { export function useCurrentViewDOMRect(): UnwrapNestedRefs<Partial<DOMRect>> {
return must(inject(currentViewDOMRectKey), 'no DOMReact found') return must(inject(currentViewDOMRectKey), 'no DOMReact found')
@ -106,9 +107,9 @@ export function useParentViewDOMRect(): UnwrapNestedRefs<Partial<DOMRect>> {
} }
export function getModule(ctx: UnwrapNestedRefs<EngineContextBase>, mid: string): Module | undefined { export function getModule(ctx: UnwrapNestedRefs<EngineContextBase>, mid: string): Module | undefined {
for (const category of ctx.categories) { for(const category of ctx.categories){
for (const module of category.modules) { for(const module of category.modules){
if (module.mid === mid) { if(module.mid === mid) {
return module return module
} }
} }
@ -116,15 +117,15 @@ export function getModule(ctx: UnwrapNestedRefs<EngineContextBase>, mid: string)
return undefined return undefined
} }
export function useModule() { export function useModule(){
const ctx = useContext() const ctx = useContext()
return computed((): Module | undefined => { return computed((): Module | undefined => {
for (const block of ctx.blocks) { for(const block of ctx.blocks){
if (block.vid !== ctx.activeBlockId) { if(block.vid !== ctx.activeBlockId){
continue continue
} }
const module = getModule(ctx, block.mid) const module = getModule(ctx,block.mid)
if (module != null) { if (module != null) {
return module return module
} }
@ -163,7 +164,9 @@ export interface TreeData {
value: any value: any
} }
const treeDataKey: InjectionKey<UnwrapNestedRefs<TreeData>|WritableComputedRef<TreeData> | null> = Symbol.for('ddd:view:tree:data') type RefTreeData = InjectionKey<UnwrapNestedRefs<TreeData>|WritableComputedRef<TreeData>|null>
const treeDataKey: RefTreeData = Symbol.for('ddd:view:tree:data')
export function provideTreeData(data: UnwrapNestedRefs<TreeData> | WritableComputedRef<TreeData> | null) { export function provideTreeData(data: UnwrapNestedRefs<TreeData> | WritableComputedRef<TreeData> | null) {
provide(treeDataKey, data) provide(treeDataKey, data)
@ -175,7 +178,9 @@ export function useSource<T = unknown>(source: string | undefined, fallback?: T)
const blockId = useBlockId() const blockId = useBlockId()
return computed<T | undefined>({ return computed<T | undefined>({
get(): T | undefined { get(): T | undefined {
console.log('get',source,'source',blockId || ctx.activeBlockId,'id')
if (!source) { if (!source) {
return fallback return fallback
} }
@ -193,6 +198,7 @@ export function useSource<T = unknown>(source: string | undefined, fallback?: T)
return valueOf(ctx.sources[id], source) ?? fallback return valueOf(ctx.sources[id], source) ?? fallback
}, },
set(value: T | undefined) { set(value: T | undefined) {
console.log('set',value,'value',source,'source',blockId || ctx.activeBlockId,'id')
if (!source || source.startsWith("$")) { if (!source || source.startsWith("$")) {
return return
} }

@ -0,0 +1,18 @@
<script lang="ts" setup>
import { useSource } from '../context.ts'
defineOptions({
name: 'DddRadiusStyle'
})
const props = defineProps<{
field: string
help?: string
label?: string
}>()
const tempStr = useSource<string>(props.field)
</script>
<template>
<div style="display: flex;justify-content: space-between">
<label>{{props.label}}</label>
<input type="range" min="1" max="50" v-model="tempStr">
</div>
</template>

@ -274,7 +274,7 @@ export type ClipBehavior =
| 'visible' | 'visible'
| 'auto' | 'auto'
| 'autoX' // overflow-x: auto; overflow-y: hidden | 'autoX' // overflow-x: auto; overflow-y: hidden
| 'autoY' // overflow-x: hidden; overflow-y: auto | 'autoY' // overflow-y: hidden; overflow-y: auto
/** /**
* *
@ -405,6 +405,7 @@ export interface View {
*/ */
// theme?: Theme | Widget // theme?: Theme | Widget
theme?: DynamicTheme theme?: DynamicTheme
title?: string
/** /**
* *
*/ */

@ -9,11 +9,12 @@ const props = defineProps<{
const mod = useModule() const mod = useModule()
const name = useSource(props.source) const name = useSource(props.source)
const view = computed(() => { const view = computed(()=>{
const ts = mod.value?.templates const ts = mod.value?.templates
const key = name.value const key = name.value
return ts && key ? ts[key] : undefined return ts && key ? ts[key] : undefined
}) })
</script> </script>
<template> <template>

@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useSource } from '../context' import { useSource } from '../context'
defineOptions({ defineOptions({
name: 'DddTextView', name: 'DddTextView',
inheritAttrs: false, inheritAttrs: false,

Loading…
Cancel
Save