Skip to content

Commit

Permalink
feat(data builder): support component slot
Browse files Browse the repository at this point in the history
  • Loading branch information
qianmoQ committed Nov 19, 2024
1 parent d9a2ebb commit c8789d2
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 6 deletions.
79 changes: 79 additions & 0 deletions docs/components/data-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,38 @@ const items = ref([

:::

## Slot

::: raw

<CodeRunner title="Usage">
<ShadcnDataBuilderEditor :items="panels2" :config-width="300" :height="300" :width="1080" @update-config="console.log($event)">
<template #text="{ configure, isSelected }">
<ShadcnText type="h1" :class="isSelected ? 'text-blue-600' : 'text-gray-900'">
{{ getConfigValue(configure, 'Text Group', 'Text Component') }}
</ShadcnText>
</template>
</ShadcnDataBuilderEditor>
</CodeRunner>

:::

::: details Show code

```vue
<template>
<ShadcnDataBuilderEditor :items="items" :config-width="300" @update-config="console.log($event)">
<template #text="{ configure, isSelected }">
<ShadcnText type="h1" :class="isSelected ? 'text-blue-600' : 'text-gray-900'">
{{ getConfigValue(configure, 'Text Group', 'Text Component') }}
</ShadcnText>
</template>
</ShadcnDataBuilderEditor>
</template>
```

:::

## DataBuilder Props

<ApiTable title="DataBuilder Editor Props"
Expand Down Expand Up @@ -269,6 +301,15 @@ const items = ref([
]">
</ApiTable>

## DataBuilder Slots

<ApiTable title="DataBuilder Editor Slots"
:headers="['Slot', 'Description']"
:columns="[
['slots', 'Render the corresponding slot according to the component type, for example, if item.type=text, render the text slot, { component, configure, isSelected }'],
]">
</ApiTable>

## DataBuilder Events

<ApiTable title="DataBuilder Editor Events"
Expand Down Expand Up @@ -333,4 +374,42 @@ const panels = ref([
]
}
])

const panels2 = ref([
{
group: 'Basic Components',
children: [
{
type: 'text', label: 'Text', configure: [
{
group: 'Text Group',
items: [
{ type: 'text', label: 'Text Component', description: 'Description', value: 'Hello, View Shadcn UI' },
]
},
{
group: 'Text Group 2',
items: [
{ type: 'text', label: 'Text', value: 'Hello, View Shadcn UI' },
{ type: 'title', label: 'Title' },
{ type: 'paragraph', label: 'Paragraph' }
]
}
]
}
]
}
])

const getConfigValue = (configure, groupName, label) => {
if (!configure) {
return null
}
const group = configure.find(g => g.group === groupName)
if (!group) {
return null
}
const item = group.items?.find(item => item.label === label)
return item?.value
}
</script>
21 changes: 19 additions & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
<template>
<div class="flex h-screen bg-gray-100 w-screen">
<ShadcnDataBuilderEditor :items="panels"
:resize="false"
:config-width="300"
:canvas-style="{backgroundColor: '#ffffff'}"
@update-config="console.log($event)"/>
@update-config="console.log($event)">
<template #text="{ configure, isSelected }">
<ShadcnText type="h1" :class="isSelected ? 'text-blue-600' : 'text-gray-900'">
{{ getConfigValue(configure, 'Text Group', 'Text Component') }}
</ShadcnText>
</template>
</ShadcnDataBuilderEditor>
</div>
</template>

Expand Down Expand Up @@ -66,4 +71,16 @@ const panels = ref([
]
}
])
const getConfigValue = (configure, groupName, label) => {
if (!configure) {
return null
}
const group = configure.find(g => g.group === groupName)
if (!group) {
return null
}
const item = group.items?.find(item => item.label === label)
return item?.value
}
</script>
13 changes: 12 additions & 1 deletion src/ui/data-builder/ShadcnDataBuilderCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,17 @@
]"
:style="getComponentStyle(item)"
@mousedown="onComponentMouseDown($event, item)">
{{ item.label }}

<!-- 使用命名插槽进行自定义渲染 -->
<!-- Use named slot for custom rendering -->
<slot :name="item.type"
:component="item"
:configure="item.configure"
:is-selected="selectedIdRef === item.id">
<!-- 默认渲染 -->
<!-- Default rendering -->
<ShadcnDataBuilderRenderer :type="item.type" :configure="item.configure"/>
</slot>

<!-- Delete button - only show for selected component -->
<div v-if="selectedIdRef === item.id"
Expand Down Expand Up @@ -215,6 +225,7 @@
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { calcSize } from '@/utils/common'
import { ShadcnDataBuilderCanvasEmits, ShadcnDataBuilderCanvasProps, ShadcnDataBuilderPanelChildProps } from './types'
import ShadcnDataBuilderRenderer from './ShadcnDataBuilderRenderer.vue'
const emit = defineEmits<ShadcnDataBuilderCanvasEmits>()
const props = withDefaults(defineProps<ShadcnDataBuilderCanvasProps>(), {
Expand Down
3 changes: 1 addition & 2 deletions src/ui/data-builder/ShadcnDataBuilderConfigure.vue
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,7 @@ watch(() => props.selectedComponent, (newVal) => {
x: newVal.x,
y: newVal.y,
width: newVal.width,
height: newVal.height,
configure: newVal.configure
height: newVal.height
}
}
}, { deep: true })
Expand Down
7 changes: 6 additions & 1 deletion src/ui/data-builder/ShadcnDataBuilderEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
:canvas-style="canvasStyle"
:show-guidelines="showGuidelines"
@select="onSelect"
@update:selected-id="selectedId = $event"/>
@update:selected-id="selectedId = $event">
<!-- Pass the custom renderer slot to Canvas -->
<template v-for="(_, name) in $slots" :key="name" #[name]="slotData">
<slot :name="name" v-bind="slotData"/>
</template>
</ShadcnDataBuilderCanvas>

<!-- Right Configure -->
<ShadcnDataBuilderConfigure :selected-component="onSelectedComponent"
Expand Down
102 changes: 102 additions & 0 deletions src/ui/data-builder/ShadcnDataBuilderRenderer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<template>
<!-- 每个组件的渲染容器 -->
<div class="w-full h-full flex items-center justify-center">
<!-- 根据组件类型渲染不同内容 -->
<template v-if="type === 'text'">
<div class="text-base">{{ getConfigValue('Text Group', 'Text Component') }}</div>
</template>

<template v-else-if="type === 'image'">
<div class="space-y-2 w-full">
<!-- Text Component -->
<div class="text-sm">{{ getConfigValue('Text Group', 'Text Component') }}</div>

<!-- Number Component -->
<div class="text-sm">Number: {{ getConfigValue('Text Group', 'Number Component') }}</div>

<!-- Textarea Component -->
<div class="text-sm whitespace-pre-wrap">{{ getConfigValue('Text Group', 'Textarea Component') }}</div>

<!-- Password Component (显示为掩码) -->
<div class="text-sm">Password: {{ maskPassword(getConfigValue('Text Group', 'Password Component')) }}</div>

<!-- Switch Component -->
<div class="text-sm">
Switch: {{ getConfigValue('Text Group', 'Switch Component') ? 'On' : 'Off' }}
</div>

<!-- Radio Component -->
<div class="text-sm">
Selected: {{ getConfigValue('Text Group', 'Radio Component') }}
</div>

<!-- Checkbox Component -->
<div class="text-sm">
Checked: {{ getConfigValue('Text Group', 'Checkbox Component')?.join(', ') }}
</div>

<!-- Select Component -->
<div class="text-sm">
Selected: {{ getConfigValue('Text Group', 'Select Component') }}
</div>

<!-- Slider Component -->
<div class="text-sm">
Value: {{ getConfigValue('Text Group', 'Slider Component') }}
</div>

<!-- Rate Component -->
<div class="flex items-center space-x-1">
<template v-for="i in 5" :key="i">
<ShadcnIcon
icon="Star"
:class="i <= getConfigValue('Text Group', 'Rate Component') ? 'text-yellow-400' : 'text-gray-300'"
size="16"
/>
</template>
</div>
</div>
</template>

<template v-else-if="type === 'chart'">
<div class="text-gray-500">Chart Component</div>
</template>

<template v-else>
<div class="text-gray-500">Unknown Component Type</div>
</template>
</div>
</template>

<script setup lang="ts">
import type { ItemConfigureGroupProps } from './types'
const props = defineProps<{
type: string
configure?: ItemConfigureGroupProps[]
}>()
// 获取指定分组和标签的配置值
const getConfigValue = (groupName: string, label: string) => {
if (!props.configure) {
return null
}
const group = props.configure.find(g => g.group === groupName)
if (!group) {
return null
}
const item = group.items?.find(item => item.label === label)
return item?.value
}
// 密码掩码处理
const maskPassword = (password: string) => {
if (!password) {
return ''
}
return ''.repeat(password.length)
}
</script>

0 comments on commit c8789d2

Please sign in to comment.