|
@@ -1,15 +1,57 @@
|
|
|
<template>
|
|
|
- <div
|
|
|
- :style="{
|
|
|
- ...style
|
|
|
- }"
|
|
|
- >
|
|
|
- <slot></slot>
|
|
|
+ <div :class="['component', { active: active }]">
|
|
|
+ <div
|
|
|
+ :style="{
|
|
|
+ ...style
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <component :is="component.id" :property="component.property" />
|
|
|
+ </div>
|
|
|
+ <div class="component-wrap">
|
|
|
+ <!-- 左侧组件名 -->
|
|
|
+ <div class="component-name" v-if="component.name">
|
|
|
+ {{ component.name }}
|
|
|
+ </div>
|
|
|
+ <!-- 左侧:组件操作工具栏 -->
|
|
|
+ <div class="component-toolbar" v-if="showToolbar && component.name && active">
|
|
|
+ <VerticalButtonGroup type="primary">
|
|
|
+ <el-tooltip content="上移" placement="right">
|
|
|
+ <el-button :disabled="!canMoveUp" @click.stop="handleMoveComponent(-1)">
|
|
|
+ <Icon icon="ep:arrow-up" />
|
|
|
+ </el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip content="下移" placement="right">
|
|
|
+ <el-button :disabled="!canMoveDown" @click.stop="handleMoveComponent(1)">
|
|
|
+ <Icon icon="ep:arrow-down" />
|
|
|
+ </el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip content="复制" placement="right">
|
|
|
+ <el-button @click.stop="handleCopyComponent()">
|
|
|
+ <Icon icon="ep:copy-document" />
|
|
|
+ </el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip content="删除" placement="right">
|
|
|
+ <el-button @click.stop="handleDeleteComponent()">
|
|
|
+ <Icon icon="ep:delete" />
|
|
|
+ </el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ </VerticalButtonGroup>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
+<script lang="ts">
|
|
|
+// 注册所有的组件
|
|
|
+import { components } from '../components/mobile/index'
|
|
|
+export default {
|
|
|
+ components: { ...components }
|
|
|
+}
|
|
|
+</script>
|
|
|
<script setup lang="ts">
|
|
|
-import { ComponentStyle } from '@/components/DiyEditor/util'
|
|
|
+import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
|
|
+import { propTypes } from '@/utils/propTypes'
|
|
|
+import { object } from 'vue-types'
|
|
|
|
|
|
/**
|
|
|
* 组件容器
|
|
@@ -17,30 +59,164 @@ import { ComponentStyle } from '@/components/DiyEditor/util'
|
|
|
*/
|
|
|
defineOptions({ name: 'ComponentContainer' })
|
|
|
|
|
|
-const props = defineProps<{ property: ComponentStyle | undefined }>()
|
|
|
+type DiyComponentWithStyle = DiyComponent<any> & { property: { style?: ComponentStyle } }
|
|
|
+const props = defineProps({
|
|
|
+ component: object<DiyComponentWithStyle>().isRequired,
|
|
|
+ active: propTypes.bool.def(false),
|
|
|
+ canMoveUp: propTypes.bool.def(false),
|
|
|
+ canMoveDown: propTypes.bool.def(false),
|
|
|
+ showToolbar: propTypes.bool.def(true)
|
|
|
+})
|
|
|
|
|
|
+/**
|
|
|
+ * 组件样式
|
|
|
+ */
|
|
|
const style = computed(() => {
|
|
|
- if (!props.property) {
|
|
|
+ let componentStyle = props.component.property.style
|
|
|
+ if (!componentStyle) {
|
|
|
return {}
|
|
|
}
|
|
|
return {
|
|
|
- marginTop: `${props.property.marginTop || 0}px`,
|
|
|
- marginBottom: `${props.property.marginBottom || 0}px`,
|
|
|
- marginLeft: `${props.property.marginLeft || 0}px`,
|
|
|
- marginRight: `${props.property.marginRight || 0}px`,
|
|
|
- paddingTop: `${props.property.paddingTop || 0}px`,
|
|
|
- paddingRight: `${props.property.paddingRight || 0}px`,
|
|
|
- paddingBottom: `${props.property.paddingBottom || 0}px`,
|
|
|
- paddingLeft: `${props.property.paddingLeft || 0}px`,
|
|
|
- borderTopLeftRadius: `${props.property.borderTopLeftRadius || 0}px`,
|
|
|
- borderTopRightRadius: `${props.property.borderTopRightRadius || 0}px`,
|
|
|
- borderBottomRightRadius: `${props.property.borderBottomRightRadius || 0}px`,
|
|
|
- borderBottomLeftRadius: `${props.property.borderBottomLeftRadius || 0}px`,
|
|
|
+ marginTop: `${componentStyle.marginTop || 0}px`,
|
|
|
+ marginBottom: `${componentStyle.marginBottom || 0}px`,
|
|
|
+ marginLeft: `${componentStyle.marginLeft || 0}px`,
|
|
|
+ marginRight: `${componentStyle.marginRight || 0}px`,
|
|
|
+ paddingTop: `${componentStyle.paddingTop || 0}px`,
|
|
|
+ paddingRight: `${componentStyle.paddingRight || 0}px`,
|
|
|
+ paddingBottom: `${componentStyle.paddingBottom || 0}px`,
|
|
|
+ paddingLeft: `${componentStyle.paddingLeft || 0}px`,
|
|
|
+ borderTopLeftRadius: `${componentStyle.borderTopLeftRadius || 0}px`,
|
|
|
+ borderTopRightRadius: `${componentStyle.borderTopRightRadius || 0}px`,
|
|
|
+ borderBottomRightRadius: `${componentStyle.borderBottomRightRadius || 0}px`,
|
|
|
+ borderBottomLeftRadius: `${componentStyle.borderBottomLeftRadius || 0}px`,
|
|
|
overflow: 'hidden',
|
|
|
background:
|
|
|
- props.property.bgType === 'color' ? props.property.bgColor : `url(${props.property.bgImg})`
|
|
|
+ componentStyle.bgType === 'color' ? componentStyle.bgColor : `url(${componentStyle.bgImg})`
|
|
|
}
|
|
|
})
|
|
|
+
|
|
|
+const emits = defineEmits<{
|
|
|
+ (e: 'move', direction: number): void
|
|
|
+ (e: 'copy'): void
|
|
|
+ (e: 'delete'): void
|
|
|
+}>()
|
|
|
+/**
|
|
|
+ * 移动组件
|
|
|
+ * @param direction 移动方向
|
|
|
+ */
|
|
|
+const handleMoveComponent = (direction: number) => {
|
|
|
+ emits('move', direction)
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 复制组件
|
|
|
+ */
|
|
|
+const handleCopyComponent = () => {
|
|
|
+ emits('copy')
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 删除组件
|
|
|
+ */
|
|
|
+const handleDeleteComponent = () => {
|
|
|
+ emits('delete')
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
-<style scoped lang="scss"></style>
|
|
|
+<style scoped lang="scss">
|
|
|
+$active-border-width: 2px;
|
|
|
+$hover-border-width: 1px;
|
|
|
+$name-position: -85px;
|
|
|
+$toolbar-position: -55px;
|
|
|
+/* 组件 */
|
|
|
+.component {
|
|
|
+ position: relative;
|
|
|
+ cursor: move;
|
|
|
+ .component-wrap {
|
|
|
+ display: block;
|
|
|
+ position: absolute;
|
|
|
+ left: -$active-border-width;
|
|
|
+ top: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ /* 鼠标放到组件上时 */
|
|
|
+ &:hover {
|
|
|
+ border: $hover-border-width dashed var(--el-color-primary);
|
|
|
+ box-shadow: 0 0 5px 0 rgba(24, 144, 255, 0.3);
|
|
|
+ .component-name {
|
|
|
+ /* 防止加了边框之后,位置移动 */
|
|
|
+ left: $name-position - $hover-border-width;
|
|
|
+ top: $hover-border-width;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* 左侧:组件名称 */
|
|
|
+ .component-name {
|
|
|
+ display: block;
|
|
|
+ position: absolute;
|
|
|
+ width: 80px;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 25px;
|
|
|
+ height: 25px;
|
|
|
+ background: #fff;
|
|
|
+ font-size: 12px;
|
|
|
+ left: $name-position;
|
|
|
+ top: $active-border-width;
|
|
|
+ box-shadow:
|
|
|
+ 0 0 4px #00000014,
|
|
|
+ 0 2px 6px #0000000f,
|
|
|
+ 0 4px 8px 2px #0000000a;
|
|
|
+ /* 右侧小三角 */
|
|
|
+ &:after {
|
|
|
+ position: absolute;
|
|
|
+ top: 7.5px;
|
|
|
+ right: -10px;
|
|
|
+ content: ' ';
|
|
|
+ height: 0;
|
|
|
+ width: 0;
|
|
|
+ border: 5px solid transparent;
|
|
|
+ border-left-color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* 右侧:组件操作工具栏 */
|
|
|
+ .component-toolbar {
|
|
|
+ display: none;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ right: $toolbar-position;
|
|
|
+ /* 左侧小三角 */
|
|
|
+ &:before {
|
|
|
+ position: absolute;
|
|
|
+ top: 10px;
|
|
|
+ left: -10px;
|
|
|
+ content: ' ';
|
|
|
+ height: 0;
|
|
|
+ width: 0;
|
|
|
+ border: 5px solid transparent;
|
|
|
+ border-right-color: #2d8cf0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* 组件选中时 */
|
|
|
+ &.active {
|
|
|
+ margin-bottom: 4px;
|
|
|
+
|
|
|
+ .component-wrap {
|
|
|
+ border: $active-border-width solid var(--el-color-primary) !important;
|
|
|
+ box-shadow: 0 0 10px 0 rgba(24, 144, 255, 0.3);
|
|
|
+ margin-bottom: $active-border-width + $active-border-width;
|
|
|
+
|
|
|
+ .component-name {
|
|
|
+ background: var(--el-color-primary);
|
|
|
+ color: #fff;
|
|
|
+ /* 防止加了边框之后,位置移动 */
|
|
|
+ left: $name-position - $active-border-width !important;
|
|
|
+ top: 0 !important;
|
|
|
+ &:after {
|
|
|
+ border-left-color: var(--el-color-primary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .component-toolbar {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|