parent
32ccceb338
commit
7764fcafc9
@ -0,0 +1,116 @@ |
||||
/* |
||||
* @Author: 卜启缘 |
||||
* @Date: 2021-05-04 05:36:58 |
||||
* @LastEditTime: 2021-07-11 22:38:54 |
||||
* @LastEditors: 卜启缘 |
||||
* @Description: 导航栏 |
||||
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\tabbar\index.tsx |
||||
*/ |
||||
import { Tabbar, TabbarItem } from 'vant' |
||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils' |
||||
import { |
||||
createEditorCrossSortableProp, |
||||
createEditorInputProp, |
||||
createEditorSwitchProp, |
||||
createEditorColorProp |
||||
} from '@/visual-editor/visual-editor.props' |
||||
import { useGlobalProperties } from '@/hooks/useGlobalProperties' |
||||
import tabbarItem from './tabbar-item' |
||||
import { createNewBlock } from '@/visual-editor/visual-editor.utils' |
||||
import { BASE_URL } from '@/visual-editor/utils' |
||||
|
||||
export default { |
||||
key: 'tabbar', |
||||
moduleName: 'baseWidgets', |
||||
label: '底部标签栏', |
||||
preview: () => ( |
||||
<Tabbar> |
||||
<TabbarItem icon="home-o">首页</TabbarItem> |
||||
<TabbarItem icon="apps-o">导航</TabbarItem> |
||||
<TabbarItem icon="user-o">我的</TabbarItem> |
||||
</Tabbar> |
||||
), |
||||
render: ({ props, block }) => { |
||||
const { registerRef } = useGlobalProperties() |
||||
|
||||
setTimeout(() => { |
||||
const compEl = window.$$refs[block._vid]?.$el |
||||
const draggableEl = compEl?.closest('div[data-draggable]') |
||||
const tabbarEl = draggableEl?.querySelector('.van-tabbar') as HTMLDivElement |
||||
if (draggableEl && tabbarEl) { |
||||
tabbarEl.style.position = 'unset' |
||||
draggableEl.style.position = 'fixed' |
||||
draggableEl.style.bottom = '0' |
||||
draggableEl.style.left = '0' |
||||
draggableEl.style.width = '100%' |
||||
draggableEl.style.zIndex = '1000' |
||||
} else { |
||||
document.body.style.paddingBottom = '50px' |
||||
const slotEl = compEl?.closest('__slot-item') |
||||
if (slotEl) { |
||||
slotEl.style.position = 'fixed' |
||||
slotEl.style.bottom = '0' |
||||
} |
||||
} |
||||
}) |
||||
|
||||
return ( |
||||
<Tabbar ref={(el) => registerRef(el, block._vid)} v-model={props.modelValue} {...props}> |
||||
{props.tabs?.map((item) => { |
||||
const itemProps = item.block?.props |
||||
const url = `${BASE_URL}${props.baseUrl}${itemProps.url}`.replace(/\/{2,}/g, '/') |
||||
return ( |
||||
<TabbarItem name={item.value} key={item.value} {...itemProps} url={url}> |
||||
{item.label} |
||||
</TabbarItem> |
||||
) |
||||
})} |
||||
</Tabbar> |
||||
) |
||||
}, |
||||
props: { |
||||
modelValue: createEditorInputProp({ |
||||
label: '当前选中标签的名称或索引值', |
||||
defaultValue: '' |
||||
}), |
||||
tabs: createEditorCrossSortableProp({ |
||||
label: '默认选项', |
||||
labelPosition: 'top', |
||||
multiple: false, |
||||
showItemPropsConfig: true, |
||||
defaultValue: [ |
||||
{ label: '首页', value: 'index', component: tabbarItem, block: createNewBlock(tabbarItem) }, |
||||
{ |
||||
label: '导航', |
||||
value: 'navigation', |
||||
component: tabbarItem, |
||||
block: createNewBlock(tabbarItem) |
||||
}, |
||||
{ label: '我的', value: 'user', component: tabbarItem, block: createNewBlock(tabbarItem) } |
||||
] |
||||
}), |
||||
fixed: createEditorSwitchProp({ label: '是否固定在底部', defaultValue: true }), |
||||
border: createEditorSwitchProp({ label: '是否显示外边框', defaultValue: true }), |
||||
zIndex: createEditorInputProp({ label: '元素 z-index', defaultValue: '1' }), |
||||
baseUrl: createEditorInputProp({ label: '路由路径前缀', defaultValue: '/preview/#/' }), |
||||
activeColor: createEditorColorProp({ label: '选中标签的颜色', defaultValue: '#1989fa' }), |
||||
inactiveColor: createEditorColorProp({ label: '未选中标签的颜色', defaultValue: '#7d7e80' }), |
||||
route: createEditorSwitchProp({ label: '是否开启路由模式', defaultValue: false }), |
||||
// placeholder: createEditorSwitchProp({
|
||||
// label: '固定在底部时,是否在标签位置生成一个等高的占位元素',
|
||||
// defaultValue: true
|
||||
// }),
|
||||
safeAreaInsetBottom: createEditorSwitchProp({ |
||||
label: '是否开启底部安全区适配,设置 fixed 时默认开启', |
||||
defaultValue: false |
||||
}) |
||||
}, |
||||
events: [ |
||||
{ label: '点击左侧按钮时触发', value: 'click-left' }, |
||||
{ label: '点击右侧按钮时触发', value: 'click-right' } |
||||
], |
||||
draggable: false, |
||||
resize: { |
||||
width: true |
||||
} |
||||
} as VisualEditorComponent |
@ -0,0 +1,47 @@ |
||||
/* |
||||
* @Author: 卜启缘 |
||||
* @Date: 2021-05-04 05:36:58 |
||||
* @LastEditTime: 2021-07-11 19:58:14 |
||||
* @LastEditors: 卜启缘 |
||||
* @Description: 导航栏项 |
||||
* @FilePath: \vite-vue3-lowcode\src\packages\container-component\tabbar\tabbar-item.tsx |
||||
*/ |
||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils' |
||||
import { createEditorInputProp, createEditorSwitchProp } from '@/visual-editor/visual-editor.props' |
||||
|
||||
export default { |
||||
key: 'tabbar-item', |
||||
moduleName: 'baseWidgets', |
||||
label: '底部标签栏', |
||||
preview: () => <></>, |
||||
render: () => <></>, |
||||
props: { |
||||
// name: createEditorInputProp({
|
||||
// label: '标签名称,作为匹配的标识符',
|
||||
// defaultValue: '当前标签的索引值'
|
||||
// }),
|
||||
icon: createEditorInputProp({ label: '图标名称或图片链接', defaultValue: 'home-o' }), |
||||
iconPrefix: createEditorInputProp({ |
||||
label: '图标类名前缀', |
||||
tips: '图标类名前缀,同 Icon 组件的 class-prefix 属性', |
||||
defaultValue: 'van-icon' |
||||
}), |
||||
dot: createEditorSwitchProp({ label: '是否显示图标右上角小红点', defaultValue: false }), |
||||
badge: createEditorInputProp({ label: '图标右上角徽标的内容', defaultValue: '' }), |
||||
url: createEditorInputProp({ label: '点击后跳转的链接地址', defaultValue: '' }), |
||||
// to: createEditorInputProp({
|
||||
// label: '点击后跳转的目标路由对象',
|
||||
// tips: '点击后跳转的目标路由对象,同 vue-router 的 to 属性',
|
||||
// defaultValue: ''
|
||||
// }),
|
||||
replace: createEditorSwitchProp({ label: '是否在跳转时替换当前页面历史', defaultValue: false }) |
||||
}, |
||||
events: [ |
||||
{ label: '点击左侧按钮时触发', value: 'click-left' }, |
||||
{ label: '点击右侧按钮时触发', value: 'click-right' } |
||||
], |
||||
draggable: false, |
||||
resize: { |
||||
width: true |
||||
} |
||||
} as VisualEditorComponent |
@ -1,32 +0,0 @@ |
||||
type RequestIdleCallbackHandle = any |
||||
type RequestIdleCallbackOptions = { |
||||
timeout: number |
||||
} |
||||
type RequestIdleCallbackDeadline = { |
||||
readonly didTimeout: boolean |
||||
timeRemaining: () => number |
||||
} |
||||
|
||||
declare interface Window { |
||||
$$refs: any |
||||
requestIdleCallback: ( |
||||
callback: (deadline: RequestIdleCallbackDeadline) => void, |
||||
opts?: RequestIdleCallbackOptions |
||||
) => RequestIdleCallbackHandle |
||||
cancelIdleCallback: (handle: RequestIdleCallbackHandle) => void |
||||
} |
||||
|
||||
// declare module '*.vue' {
|
||||
// import { DefineComponent } from 'vue'
|
||||
//
|
||||
// const component: DefineComponent<{}, {}, any>
|
||||
// export default component
|
||||
// }
|
||||
|
||||
// declare module '*.module.scss'
|
||||
|
||||
declare module '*.vue' { |
||||
import { ComponentOptions } from 'vue' |
||||
const component: ComponentOptions |
||||
export default component |
||||
} |
@ -1,43 +1,45 @@ |
||||
.list-group { |
||||
|
||||
} |
||||
.list-group-item { |
||||
position: relative; |
||||
display: flex; |
||||
width: calc(100% - 20px); |
||||
min-height: 120px; |
||||
padding: 0 5px; |
||||
margin-top: 20px; |
||||
margin-left: 10px; |
||||
border: solid 3px #ebeef5; |
||||
margin-top: 20px; |
||||
min-height: 120px; |
||||
display: flex; |
||||
transform: translate(0); |
||||
box-sizing: border-box; |
||||
align-items: center; |
||||
justify-content: center; |
||||
padding: 0px 5px; |
||||
box-sizing: border-box; |
||||
|
||||
&:hover { |
||||
border-color: #409EFF; |
||||
cursor: move; |
||||
border-color: #409eff; |
||||
} |
||||
|
||||
&:last-of-type { |
||||
margin-bottom: 20px; |
||||
} |
||||
|
||||
&::before { |
||||
content: attr(data-label); |
||||
position: absolute; |
||||
top: -3px; |
||||
left: -3px; |
||||
background-color: #409EFF; |
||||
color: white; |
||||
z-index: 1; |
||||
padding: 4px 8px; |
||||
font-size: 12px; |
||||
z-index: 1; |
||||
color: white; |
||||
background-color: #409eff; |
||||
content: attr(data-label); |
||||
} |
||||
|
||||
&::after { |
||||
content: ""; |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
left: 0; |
||||
z-index: 2; |
||||
content: ''; |
||||
} |
||||
} |
||||
|
@ -1,238 +0,0 @@ |
||||
/* |
||||
* @Author: 卜启缘 |
||||
* @Date: 2021-06-10 16:23:06 |
||||
* @LastEditTime: 2021-07-07 19:36:45 |
||||
* @LastEditors: 卜启缘 |
||||
* @Description: 组件属性编辑器 |
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\AttrEditor.tsx |
||||
*/ |
||||
import { defineComponent, computed, watch } from 'vue' |
||||
import { |
||||
ElColorPicker, |
||||
ElForm, |
||||
ElFormItem, |
||||
ElInput, |
||||
ElOption, |
||||
ElSelect, |
||||
ElSwitch, |
||||
ElPopover, |
||||
ElCascader, |
||||
ElInputNumber, |
||||
ElRadioGroup, |
||||
ElRadioButton |
||||
} from 'element-plus' |
||||
import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props' |
||||
import { TablePropEditor, CrossSortableOptionsEditor } from './components' |
||||
import { useDotProp } from '@/visual-editor/hooks/useDotProp' |
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData' |
||||
import { cloneDeep } from 'lodash' |
||||
import { FormatInputNumber } from '@/visual-editor/components/common/format-input-number' |
||||
|
||||
export const AttrEditor = defineComponent({ |
||||
setup() { |
||||
const { visualConfig, currentBlock, jsonData } = useVisualData() |
||||
/** |
||||
* @description 模型集合 |
||||
*/ |
||||
const models = computed(() => cloneDeep(jsonData.models)) |
||||
|
||||
const compPaddingAttrs = ['paddingTop', 'paddingLeft', 'paddingRight', 'paddingBottom'] |
||||
|
||||
/** |
||||
* @description 监听组件padding值的变化 |
||||
*/ |
||||
watch( |
||||
compPaddingAttrs.map((item) => () => currentBlock.value.styles?.[item]), |
||||
(val: string[]) => { |
||||
const isSame = val.every((item) => currentBlock.value.styles?.tempPadding == item) |
||||
if (isSame || new Set(val).size === 1) { |
||||
if (Reflect.has(currentBlock.value, 'styles')) { |
||||
currentBlock.value.styles.tempPadding = val[0] |
||||
} |
||||
} else { |
||||
currentBlock.value.styles.tempPadding = '' |
||||
} |
||||
} |
||||
) |
||||
|
||||
/** |
||||
* @description 总的组件padding变化时进行的操作 |
||||
*/ |
||||
const compPadding = computed({ |
||||
get: () => currentBlock.value.styles?.tempPadding, |
||||
set(val) { |
||||
compPaddingAttrs.forEach((item) => (currentBlock.value.styles[item] = val)) |
||||
currentBlock.value.styles.tempPadding = val |
||||
} |
||||
}) |
||||
|
||||
const renderEditor = (propName: string, propConfig: VisualEditorProps) => { |
||||
const { propObj, prop } = useDotProp(currentBlock.value.props, propName) |
||||
|
||||
propObj[prop] ??= propConfig.defaultValue |
||||
|
||||
return { |
||||
[VisualEditorPropsType.input]: () => { |
||||
if (!Object.is(propObj[prop], undefined) && !Object.is(propObj[prop], null)) { |
||||
propObj[prop] = `${propObj[prop]}` |
||||
} |
||||
return ( |
||||
<ElInput v-model={propObj[prop]} placeholder={propConfig.tips || propConfig.label} /> |
||||
) |
||||
}, |
||||
[VisualEditorPropsType.inputNumber]: () => <ElInputNumber v-model={propObj[prop]} />, |
||||
[VisualEditorPropsType.switch]: () => <ElSwitch v-model={propObj[prop]} />, |
||||
[VisualEditorPropsType.color]: () => <ElColorPicker v-model={propObj[prop]} />, |
||||
[VisualEditorPropsType.crossSortable]: () => ( |
||||
<CrossSortableOptionsEditor v-model={propObj[prop]} multiple={propConfig.multiple} /> |
||||
), |
||||
[VisualEditorPropsType.select]: () => ( |
||||
<ElSelect v-model={propObj[prop]} valueKey={'value'} multiple={propConfig.multiple}> |
||||
{propConfig.options?.map((opt) => ( |
||||
<ElOption label={opt.label} style={{ fontFamily: opt.value }} value={opt.value} /> |
||||
))} |
||||
</ElSelect> |
||||
), |
||||
[VisualEditorPropsType.table]: () => ( |
||||
<TablePropEditor v-model={propObj[prop]} propConfig={propConfig} /> |
||||
), |
||||
[VisualEditorPropsType.modelBind]: () => ( |
||||
<ElCascader |
||||
clearable={true} |
||||
props={{ |
||||
checkStrictly: true, |
||||
children: 'entitys', |
||||
label: 'name', |
||||
value: 'key', |
||||
expandTrigger: 'hover' |
||||
}} |
||||
placeholder="请选择绑定的请求数据" |
||||
v-model={propObj[prop]} |
||||
options={models.value} |
||||
></ElCascader> |
||||
) |
||||
}[propConfig.type]() |
||||
} |
||||
|
||||
// 表单项
|
||||
const FormEditor = () => { |
||||
const content: JSX.Element[] = [] |
||||
if (currentBlock.value) { |
||||
const { componentKey } = currentBlock.value |
||||
const component = visualConfig.componentMap[componentKey] |
||||
console.log('props.block:', currentBlock.value) |
||||
content.push( |
||||
<> |
||||
<ElFormItem label="组件ID" labelWidth={'76px'}> |
||||
{currentBlock.value._vid} |
||||
<ElPopover |
||||
width={200} |
||||
trigger="hover" |
||||
content={`你可以利用该组件ID。对该组件进行获取和设置其属性,组件可用属性可在控制台输入:$$refs.${currentBlock.value._vid} 进行查看`} |
||||
> |
||||
{{ |
||||
reference: () => <i class={'el-icon-warning-outline ml-6px'}></i> |
||||
}} |
||||
</ElPopover> |
||||
</ElFormItem> |
||||
</> |
||||
) |
||||
if (!!component) { |
||||
if (!!component.props) { |
||||
content.push( |
||||
...Object.entries(component.props || {}).map(([propName, propConfig]) => ( |
||||
<> |
||||
<ElFormItem |
||||
key={currentBlock.value._vid + propName} |
||||
style={ |
||||
propConfig.labelPosition == 'top' |
||||
? { |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
alignItems: 'flex-start' |
||||
} |
||||
: {} |
||||
} |
||||
> |
||||
{{ |
||||
label: () => ( |
||||
<> |
||||
{propConfig.tips && ( |
||||
<ElPopover width={200} trigger={'hover'} content={propConfig.tips}> |
||||
{{ |
||||
reference: () => <i class={'el-icon-warning-outline'}></i> |
||||
}} |
||||
</ElPopover> |
||||
)} |
||||
{propConfig.label} |
||||
</> |
||||
), |
||||
default: () => renderEditor(propName, propConfig) |
||||
}} |
||||
</ElFormItem> |
||||
</> |
||||
)) |
||||
) |
||||
content.push( |
||||
<ElFormItem label={'组件对齐方式'} labelWidth={'90px'}> |
||||
<ElRadioGroup v-model={currentBlock.value.styles.justifyContent} size="mini"> |
||||
<ElRadioButton label="flex-start"></ElRadioButton> |
||||
<ElRadioButton label="center"></ElRadioButton> |
||||
<ElRadioButton label="flex-end"></ElRadioButton> |
||||
</ElRadioGroup> |
||||
</ElFormItem> |
||||
) |
||||
content.push( |
||||
<> |
||||
<ElFormItem class={'flex flex-col justify-start'}> |
||||
{{ |
||||
label: () => ( |
||||
<div class={'flex justify-between mb-2'}> |
||||
<div>组件内边距</div> |
||||
<FormatInputNumber v-model={compPadding.value} class={'!w-100px'} /> |
||||
</div> |
||||
), |
||||
default: () => ( |
||||
<div class={'grid grid-cols-3 gap-2 w-full bg-gray-100 p-20px items-center'}> |
||||
<FormatInputNumber |
||||
v-model={currentBlock.value.styles.paddingTop} |
||||
class={'!w-100px col-span-full col-start-2'} |
||||
/> |
||||
<FormatInputNumber |
||||
v-model={currentBlock.value.styles.paddingLeft} |
||||
class={'!w-100px col-span-1'} |
||||
/> |
||||
<div class={'bg-white col-span-1 h-40px'}></div> |
||||
<FormatInputNumber |
||||
v-model={currentBlock.value.styles.paddingRight} |
||||
class={'!w-100px col-span-1'} |
||||
/> |
||||
<FormatInputNumber |
||||
v-model={currentBlock.value.styles.paddingBottom} |
||||
class={'!w-100px col-span-full col-start-2'} |
||||
/> |
||||
</div> |
||||
) |
||||
}} |
||||
</ElFormItem> |
||||
</> |
||||
) |
||||
} |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<ElForm size="mini" labelPosition={'left'}> |
||||
{content} |
||||
</ElForm> |
||||
</> |
||||
) |
||||
} |
||||
|
||||
return () => ( |
||||
<> |
||||
<FormEditor /> |
||||
</> |
||||
) |
||||
} |
||||
}) |
@ -0,0 +1,134 @@ |
||||
/* |
||||
* @Author: 卜启缘 |
||||
* @Date: 2021-07-11 17:53:54 |
||||
* @LastEditTime: 2021-07-11 18:36:17 |
||||
* @LastEditors: 卜启缘 |
||||
* @Description: 组件属性配置 |
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\components\prop-config\index.tsx |
||||
*/ |
||||
|
||||
import { computed, defineComponent, PropType } from 'vue' |
||||
import { |
||||
ElColorPicker, |
||||
ElInput, |
||||
ElOption, |
||||
ElSelect, |
||||
ElSwitch, |
||||
ElCascader, |
||||
ElInputNumber, |
||||
ElFormItem, |
||||
ElPopover |
||||
} from 'element-plus' |
||||
import { useDotProp } from '@/visual-editor/hooks/useDotProp' |
||||
import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props' |
||||
import { TablePropEditor, CrossSortableOptionsEditor } from '../../components' |
||||
import { cloneDeep } from 'lodash' |
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData' |
||||
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils' |
||||
|
||||
export const PropConfig = defineComponent({ |
||||
props: { |
||||
component: { |
||||
type: Object as PropType<VisualEditorComponent>, |
||||
default: () => ({}) |
||||
}, |
||||
block: { |
||||
type: Object as PropType<VisualEditorBlockData>, |
||||
default: () => ({}) |
||||
} |
||||
}, |
||||
setup(props) { |
||||
const { jsonData } = useVisualData() |
||||
/** |
||||
* @description 模型集合 |
||||
*/ |
||||
const models = computed(() => cloneDeep(jsonData.models)) |
||||
|
||||
const renderPropItem = (propName: string, propConfig: VisualEditorProps) => { |
||||
const { propObj, prop } = useDotProp(props.block.props, propName) |
||||
|
||||
propObj[prop] ??= propConfig.defaultValue |
||||
|
||||
return { |
||||
[VisualEditorPropsType.input]: () => { |
||||
if (!Object.is(propObj[prop], undefined) && !Object.is(propObj[prop], null)) { |
||||
propObj[prop] = `${propObj[prop]}` |
||||
} |
||||
return ( |
||||
<ElInput v-model={propObj[prop]} placeholder={propConfig.tips || propConfig.label} /> |
||||
) |
||||
}, |
||||
[VisualEditorPropsType.inputNumber]: () => <ElInputNumber v-model={propObj[prop]} />, |
||||
[VisualEditorPropsType.switch]: () => <ElSwitch v-model={propObj[prop]} />, |
||||
[VisualEditorPropsType.color]: () => <ElColorPicker v-model={propObj[prop]} />, |
||||
[VisualEditorPropsType.crossSortable]: () => ( |
||||
<CrossSortableOptionsEditor |
||||
v-model={propObj[prop]} |
||||
multiple={propConfig.multiple} |
||||
showItemPropsConfig={propConfig.showItemPropsConfig} |
||||
/> |
||||
), |
||||
[VisualEditorPropsType.select]: () => ( |
||||
<ElSelect v-model={propObj[prop]} valueKey={'value'} multiple={propConfig.multiple}> |
||||
{propConfig.options?.map((opt) => ( |
||||
<ElOption label={opt.label} style={{ fontFamily: opt.value }} value={opt.value} /> |
||||
))} |
||||
</ElSelect> |
||||
), |
||||
[VisualEditorPropsType.table]: () => ( |
||||
<TablePropEditor v-model={propObj[prop]} propConfig={propConfig} /> |
||||
), |
||||
[VisualEditorPropsType.modelBind]: () => ( |
||||
<ElCascader |
||||
clearable={true} |
||||
props={{ |
||||
checkStrictly: true, |
||||
children: 'entitys', |
||||
label: 'name', |
||||
value: 'key', |
||||
expandTrigger: 'hover' |
||||
}} |
||||
placeholder="请选择绑定的请求数据" |
||||
v-model={propObj[prop]} |
||||
options={models.value} |
||||
></ElCascader> |
||||
) |
||||
}[propConfig.type]() |
||||
} |
||||
|
||||
return () => { |
||||
return Object.entries(props.component.props ?? {}).map(([propName, propConfig]) => ( |
||||
<> |
||||
<ElFormItem |
||||
key={props.block._vid + propName} |
||||
style={ |
||||
propConfig.labelPosition == 'top' |
||||
? { |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
alignItems: 'flex-start' |
||||
} |
||||
: {} |
||||
} |
||||
> |
||||
{{ |
||||
label: () => ( |
||||
<> |
||||
{propConfig.tips && ( |
||||
<ElPopover width={200} trigger={'hover'} content={propConfig.tips}> |
||||
{{ |
||||
reference: () => <i class={'el-icon-warning-outline'}></i> |
||||
}} |
||||
</ElPopover> |
||||
)} |
||||
{propConfig.label} |
||||
</> |
||||
), |
||||
default: () => renderPropItem(propName, propConfig) |
||||
}} |
||||
</ElFormItem> |
||||
</> |
||||
)) |
||||
} |
||||
} |
||||
}) |
@ -0,0 +1,138 @@ |
||||
/* |
||||
* @Author: 卜启缘 |
||||
* @Date: 2021-06-10 16:23:06 |
||||
* @LastEditTime: 2021-07-11 18:36:24 |
||||
* @LastEditors: 卜启缘 |
||||
* @Description: 组件属性编辑器 |
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\index.tsx |
||||
*/ |
||||
import { defineComponent, computed, watch } from 'vue' |
||||
import { ElForm, ElFormItem, ElPopover, ElRadioGroup, ElRadioButton } from 'element-plus' |
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData' |
||||
import { FormatInputNumber } from '@/visual-editor/components/common/format-input-number' |
||||
import { PropConfig } from './components/prop-config' |
||||
|
||||
export const AttrEditor = defineComponent({ |
||||
setup() { |
||||
const { visualConfig, currentBlock } = useVisualData() |
||||
|
||||
const compPaddingAttrs = ['paddingTop', 'paddingLeft', 'paddingRight', 'paddingBottom'] |
||||
|
||||
/** |
||||
* @description 监听组件padding值的变化 |
||||
*/ |
||||
watch( |
||||
compPaddingAttrs.map((item) => () => currentBlock.value.styles?.[item]), |
||||
(val: string[]) => { |
||||
const isSame = val.every((item) => currentBlock.value.styles?.tempPadding == item) |
||||
if (isSame || new Set(val).size === 1) { |
||||
if (Reflect.has(currentBlock.value, 'styles')) { |
||||
currentBlock.value.styles.tempPadding = val[0] |
||||
} |
||||
} else { |
||||
currentBlock.value.styles.tempPadding = '' |
||||
} |
||||
} |
||||
) |
||||
|
||||
/** |
||||
* @description 总的组件padding变化时进行的操作 |
||||
*/ |
||||
const compPadding = computed({ |
||||
get: () => currentBlock.value.styles?.tempPadding, |
||||
set(val) { |
||||
compPaddingAttrs.forEach((item) => (currentBlock.value.styles[item] = val)) |
||||
currentBlock.value.styles.tempPadding = val |
||||
} |
||||
}) |
||||
|
||||
// 表单项
|
||||
const FormEditor = () => { |
||||
const content: JSX.Element[] = [] |
||||
if (currentBlock.value) { |
||||
const { componentKey } = currentBlock.value |
||||
const component = visualConfig.componentMap[componentKey] |
||||
console.log('props.block:', currentBlock.value) |
||||
content.push( |
||||
<> |
||||
<ElFormItem label="组件ID" labelWidth={'76px'}> |
||||
{currentBlock.value._vid} |
||||
<ElPopover |
||||
width={200} |
||||
trigger="hover" |
||||
content={`你可以利用该组件ID。对该组件进行获取和设置其属性,组件可用属性可在控制台输入:$$refs.${currentBlock.value._vid} 进行查看`} |
||||
> |
||||
{{ |
||||
reference: () => <i class={'el-icon-warning-outline ml-6px'}></i> |
||||
}} |
||||
</ElPopover> |
||||
</ElFormItem> |
||||
</> |
||||
) |
||||
if (!!component) { |
||||
if (!!component.props) { |
||||
content.push(<PropConfig component={component} block={currentBlock.value} />) |
||||
{ |
||||
currentBlock.value.showStyleConfig && |
||||
content.push( |
||||
<ElFormItem label={'组件对齐方式'} labelWidth={'90px'}> |
||||
<ElRadioGroup v-model={currentBlock.value.styles.justifyContent} size="mini"> |
||||
<ElRadioButton label="flex-start">{'左对齐'}</ElRadioButton> |
||||
<ElRadioButton label="center">{'居中'}</ElRadioButton> |
||||
<ElRadioButton label="flex-end">{'右对齐'}</ElRadioButton> |
||||
</ElRadioGroup> |
||||
</ElFormItem>, |
||||
<ElFormItem class={'flex flex-col justify-start'}> |
||||
{{ |
||||
label: () => ( |
||||
<div class={'flex justify-between mb-2'}> |
||||
<div>组件内边距</div> |
||||
<FormatInputNumber v-model={compPadding.value} class={'!w-100px'} /> |
||||
</div> |
||||
), |
||||
default: () => ( |
||||
<div |
||||
class={'grid grid-cols-3 gap-2 w-full bg-gray-100 p-20px items-center'} |
||||
> |
||||
<FormatInputNumber |
||||
v-model={currentBlock.value.styles.paddingTop} |
||||
class={'!w-100px col-span-full col-start-2'} |
||||
/> |
||||
<FormatInputNumber |
||||
v-model={currentBlock.value.styles.paddingLeft} |
||||
class={'!w-100px col-span-1'} |
||||
/> |
||||
<div class={'bg-white col-span-1 h-40px'}></div> |
||||
<FormatInputNumber |
||||
v-model={currentBlock.value.styles.paddingRight} |
||||
class={'!w-100px col-span-1'} |
||||
/> |
||||
<FormatInputNumber |
||||
v-model={currentBlock.value.styles.paddingBottom} |
||||
class={'!w-100px col-span-full col-start-2'} |
||||
/> |
||||
</div> |
||||
) |
||||
}} |
||||
</ElFormItem> |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<ElForm size="mini" labelPosition={'left'}> |
||||
{content} |
||||
</ElForm> |
||||
</> |
||||
) |
||||
} |
||||
|
||||
return () => ( |
||||
<> |
||||
<FormEditor /> |
||||
</> |
||||
) |
||||
} |
||||
}) |
@ -1,15 +1,48 @@ |
||||
/* |
||||
* @Author: 卜启缘 |
||||
* @Date: 2021-07-05 10:51:09 |
||||
* @LastEditTime: 2021-07-05 10:52:26 |
||||
* @LastEditTime: 2021-07-08 23:20:17 |
||||
* @LastEditors: 卜启缘 |
||||
* @Description: 表单规则 |
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\form-rule\index.tsx |
||||
*/ |
||||
import { defineComponent } from 'vue' |
||||
import { ElCard, ElTooltip } from 'element-plus' |
||||
|
||||
export const FormRule = defineComponent({ |
||||
setup() { |
||||
return () => <>表单规则</> |
||||
return () => ( |
||||
<> |
||||
<ElCard shadow={'always'} class={'mb-20px'}> |
||||
{{ |
||||
header: () => ( |
||||
<div class="flex justify-between"> |
||||
<span>设置关联规则</span> |
||||
<ElTooltip content="当前面题目选中某些选项时才出现此题" placement="bottom-end"> |
||||
<i class={'el-icon-question'}></i> |
||||
</ElTooltip> |
||||
</div> |
||||
), |
||||
default: () => <div>暂无规则</div> |
||||
}} |
||||
</ElCard> |
||||
<ElCard shadow={'always'} bodyStyle={{ padding: 1 ? '0' : '20px' }} class={'mb-20px'}> |
||||
{{ |
||||
header: () => ( |
||||
<div class="flex justify-between"> |
||||
<span>设置选项关联规则</span> |
||||
<ElTooltip |
||||
content="当前面题目选择某些选项时才出现此题的某些选项 " |
||||
placement="bottom-end" |
||||
> |
||||
<i class={'el-icon-question'}></i> |
||||
</ElTooltip> |
||||
</div> |
||||
), |
||||
default: () => null |
||||
}} |
||||
</ElCard> |
||||
</> |
||||
) |
||||
} |
||||
}) |
||||
|
Loading…
Reference in new issue