123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- <template>
- <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, DiyComponent } from '@/components/DiyEditor/util'
- import { propTypes } from '@/utils/propTypes'
- import { object } from 'vue-types'
- /**
- * 组件容器
- * 用于包裹组件,为组件提供 背景、外边距、内边距、边框等样式
- */
- defineOptions({ name: 'ComponentContainer' })
- 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(() => {
- let componentStyle = props.component.property.style
- if (!componentStyle) {
- return {}
- }
- return {
- 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:
- 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">
- $active-border-width: 2px;
- $hover-border-width: 1px;
- $name-position: -85px;
- $toolbar-position: -55px;
- /* 组件 */
- .component {
- position: relative;
- cursor: move;
- .component-wrap {
- position: absolute;
- top: 0;
- left: -$active-border-width;
- display: block;
- width: 100%;
- height: 100%;
- /* 鼠标放到组件上时 */
- &:hover {
- border: $hover-border-width dashed var(--el-color-primary);
- box-shadow: 0 0 5px 0 rgb(24 144 255 / 30%);
- .component-name {
- top: $hover-border-width;
- /* 防止加了边框之后,位置移动 */
- left: $name-position - $hover-border-width;
- }
- }
- /* 左侧:组件名称 */
- .component-name {
- position: absolute;
- top: $active-border-width;
- left: $name-position;
- display: block;
- width: 80px;
- height: 25px;
- font-size: 12px;
- line-height: 25px;
- text-align: center;
- background: #fff;
- box-shadow:
- 0 0 4px #00000014,
- 0 2px 6px #0000000f,
- 0 4px 8px 2px #0000000a;
- /* 右侧小三角 */
- &::after {
- position: absolute;
- top: 7.5px;
- right: -10px;
- width: 0;
- height: 0;
- border: 5px solid transparent;
- border-left-color: #fff;
- content: ' ';
- }
- }
- /* 右侧:组件操作工具栏 */
- .component-toolbar {
- position: absolute;
- top: 0;
- right: $toolbar-position;
- display: none;
- /* 左侧小三角 */
- &::before {
- position: absolute;
- top: 10px;
- left: -10px;
- width: 0;
- height: 0;
- border: 5px solid transparent;
- border-right-color: #2d8cf0;
- content: ' ';
- }
- }
- }
- /* 组件选中时 */
- &.active {
- margin-bottom: 4px;
- .component-wrap {
- margin-bottom: $active-border-width + $active-border-width;
- border: $active-border-width solid var(--el-color-primary) !important;
- box-shadow: 0 0 10px 0 rgb(24 144 255 / 30%);
- .component-name {
- top: 0 !important;
- /* 防止加了边框之后,位置移动 */
- left: $name-position - $active-border-width !important;
- color: #fff;
- background: var(--el-color-primary);
- &::after {
- border-left-color: var(--el-color-primary);
- }
- }
- .component-toolbar {
- display: block;
- }
- }
- }
- }
- </style>
|