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

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({
icon: 'trash',
text: '基础',
modules: [{
vid: hash(`${++nextId}`),
mid: hash(`${++nextId}`),
title: '轮播图',
maxReferenceCount: -1,
referenceCount: 0,
image: undefined,
configs: [
{
type: 'list',
field: 'items',
label: '轮播图设置',
help: '最多可添加10张图片,建议宽度750px;鼠标拖拽左侧圆点可调整图片顺序',
addable: true,
configs: [{
type: 'image',
field: 'image',
label: '图片',
required: true,
//
inlines: [{
type: 'text',
field: 'title',
label: '标题',
help: '选填,不超过 4 个字',
modules: [
{
vid: hash(`${++nextId}`),
mid: hash(`${++nextId}`),
title: '轮播图',
maxReferenceCount: -1,
referenceCount: 0,
image: undefined,
configs: [
{
type: 'list',
field: 'items',
label: '轮播图设置',
help: '最多可添加10张图片,建议宽度750px;鼠标拖拽左侧圆点可调整图片顺序',
addable: true,
configs: [{
type: 'image',
field: 'image',
label: '图片',
required: true,
//
inlines: [{
type: 'text',
field: 'title',
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',
field: 'link',
label: '链接',
help: '请输入链接', // "${label}"
type: 'mark',
field: 'position',
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',
field: 'color',
label: '指示器颜色',
}],
},
{
type: 'object',
field: 'background',
label: '背景',
configs: [{
type: 'boolean',
field: 'enabled',
label: '是否显示背景色',
}, {
type: 'background',
field: 'value',
// image
//
features: ['color', 'gradient'],
type: 'object',
field: 'background',
label: '背景',
}],
}
],
init: {
items: [
{
image: '',
title: '123',
link: '123',
configs: [{
type: 'boolean',
field: 'enabled',
label: '是否显示背景色',
}, {
type: 'background',
field: 'value',
// image
//
features: ['color', 'gradient'],
label: '背景',
}],
}
],
indicator: {
style: '#circle',
color: 'green',
position: 'start',
},
background: {
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',
},
init: {
items: [
{
image: '',
title: '123',
link: '123',
}
],
indicator: {
style: '#circle',
color: 'green',
position: 'start',
},
background: {
enabled: true,
value: "#ffffff",
},
theme: {
color: 'white',
radius: 24
}
},
'#line': {
theme: {
gap: 20,
templates: {
'#circle': {
theme: {
gap: 20,
},
children: {
type: 'each',
key: 'items',
handle: {
theme: {
width: 20,
height: 20,
radius: 10,
color: '@@indicator.color',
},
}
}
},
children: {
type: 'each',
key: 'items',
handle: {
theme: {
width: 40,
height: 20,
color: '@@indicator.color',
},
'#line': {
theme: {
gap: 20,
},
children: {
type: 'each',
key: 'items',
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: {
color: 'rgba(0,0,0,0.4)',
textColor: '#fff'
theme: {
position: 'relative',
height: 200,
padding: {
horizontal: 12.0,
vertical: 12,
},
children: '1/len(items)'
}
},
theme: {
position: 'relative',
height: 200,
padding: {
horizontal: 12.0,
vertical: 12,
color: 'cyan',
},
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}`),
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: '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: [{
mid: hash(`${++nextId}`),
maxReferenceCount: -1,
referenceCount: 0,
image: undefined,
configs: [
{
type: 'object',
field: 'titleAndDesc',
label: '产品标题及说明',
configs: [
{
type: 'text',
field: 'link',
label: '链接',
help: '请输入链接', // "${label}"
label: '产品标题',
field: 'mainTitle'
},
{
type: 'text',
label: '自定义标题',
field: 'productTitle',
label: '说明',
field: 'content',
},
{
type: 'text',
label: '自定义说明',
field: 'productDesc',
label: '提示',
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',
}
]
},
],
},
theme: {
height: 200,
padding: {
horizontal: 12.0,
init: {
titleAndDesc: {
mainTitle: '标题123',
content: '说明123',
more: '查看更多',
},
groups: [
{
productTitle: '111',
}
],
theme: {
color: 'blue',
},
children:{
theme: {
radius: 12
},
}
},
color: 'pink',
},
children: {
vid: hash(`${++nextId}`),
theme: {
width: '100%',
height: '100%',
color: 'white',
radius: 12.0,
textAlign: 'center',
height: 200,
padding: 12,
color: '@@theme.color',
},
children: [
{
theme: {
flexible: true,
gap: 12,
crossAlign: 'center',
},
children: [
{
theme: {
padding: {
horizontal: 5
},
},
children: [
{
theme: {
flexible: true,
gap: 5,
crossAlign: 'center',
children: {
vid: hash(`${++nextId}`),
theme: {
width: '100%',
height: '100%',
color: 'white',
clip: 'hidden',
radius: '@@children.theme.radius',
textAlign: 'center',
},
children: [
{
theme: {
flexible: true,
gap: 12,
crossAlign: 'center',
},
children: [
{
theme: {
padding: {
horizontal: 5
},
children: [
{
theme: {
fontSize: 16,
},
children: '左侧标题',
},
children: [
{
theme: {
flexible: true,
gap: 5,
crossAlign: 'center',
},
{
theme: {
fontSize: 16,
children: [
{
theme: {
fontSize: 16,
},
children: {
type: 'text',
key: 'titleAndDesc.mainTitle'
},
},
children: '左侧说明',
}
]
},
]
},
{
theme: {
grow: 1,
}
},
{
vid: hash(`${++nextId}`),
theme: {
width: 'auto',
margin: {
horizontal: 5,
{
theme: {
fontSize: 16,
},
children: {
type: 'text',
key: 'titleAndDesc.content'
},
}
]
},
]
},
{
theme: {
grow: 1,
}
},
{
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,
padding: {
vertical: 1,
horizontal: 5,
children: {
type: 'text',
key: 'titleAndDesc.more'
},
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: {
width: '35%',
height: '100%',
mainAlign: "center",
color: '#fff',
crossAlign: "center"
width: "94%",
height: "70%",
color: 'pink',
radius: 12.0,
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 { useContext } from '../context';
import ColorConfig from '../configs/ColorConfig.vue';
import NumberStyle from '../styles/NumberStyle.vue'
defineOptions({
name: 'ConfigCurrentView',
@ -17,17 +18,30 @@ const styles = computed(() => {
return undefined
}
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]>
})
const label = computed(()=>{
if(!view.value?.title) {
return undefined
}
return view.value.title
})
</script>
<template>
{{ ctx.activeViewId }}
<hr/>
<template v-for="([key, value]) in styles" :key="key">
<ColorConfig v-if="key === 'color'" :field="value.slice(2)" label="背景颜色" />
<div v-else>{{ key }}:{{ value }}</div>
</template>
<pre><code>{{ view }}</code></pre>
<fieldset>
<legend>{{ label }}设置</legend>
<template v-for="([key, value]) in styles" :key="key">
<ColorConfig v-if="key === 'color'" :field="value.slice(2)" label="背景颜色" />
<NumberStyle v-if="key === 'radius'" :field="value.slice(2)" label="圆角设置" />
<!-- <div v-else>{{ key }}:{{ value }}</div>-->
</template>
</fieldset>
<!--<pre><code>{{ view }}</code></pre>-->
</template>

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

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

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

Loading…
Cancel
Save