ComponentContainer.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <template>
  2. <div :class="['component', { active: active }]">
  3. <div
  4. :style="{
  5. ...style
  6. }"
  7. >
  8. <component :is="component.id" :property="component.property" />
  9. </div>
  10. <div class="component-wrap">
  11. <!-- 左侧组件名 -->
  12. <div class="component-name" v-if="component.name">
  13. {{ component.name }}
  14. </div>
  15. <!-- 左侧:组件操作工具栏 -->
  16. <div class="component-toolbar" v-if="showToolbar && component.name && active">
  17. <VerticalButtonGroup type="primary">
  18. <el-tooltip content="上移" placement="right">
  19. <el-button :disabled="!canMoveUp" @click.stop="handleMoveComponent(-1)">
  20. <Icon icon="ep:arrow-up" />
  21. </el-button>
  22. </el-tooltip>
  23. <el-tooltip content="下移" placement="right">
  24. <el-button :disabled="!canMoveDown" @click.stop="handleMoveComponent(1)">
  25. <Icon icon="ep:arrow-down" />
  26. </el-button>
  27. </el-tooltip>
  28. <el-tooltip content="复制" placement="right">
  29. <el-button @click.stop="handleCopyComponent()">
  30. <Icon icon="ep:copy-document" />
  31. </el-button>
  32. </el-tooltip>
  33. <el-tooltip content="删除" placement="right">
  34. <el-button @click.stop="handleDeleteComponent()">
  35. <Icon icon="ep:delete" />
  36. </el-button>
  37. </el-tooltip>
  38. </VerticalButtonGroup>
  39. </div>
  40. </div>
  41. </div>
  42. </template>
  43. <script lang="ts">
  44. // 注册所有的组件
  45. import { components } from '../components/mobile/index'
  46. export default {
  47. components: { ...components }
  48. }
  49. </script>
  50. <script setup lang="ts">
  51. import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
  52. import { propTypes } from '@/utils/propTypes'
  53. import { object } from 'vue-types'
  54. /**
  55. * 组件容器
  56. * 用于包裹组件,为组件提供 背景、外边距、内边距、边框等样式
  57. */
  58. defineOptions({ name: 'ComponentContainer' })
  59. type DiyComponentWithStyle = DiyComponent<any> & { property: { style?: ComponentStyle } }
  60. const props = defineProps({
  61. component: object<DiyComponentWithStyle>().isRequired,
  62. active: propTypes.bool.def(false),
  63. canMoveUp: propTypes.bool.def(false),
  64. canMoveDown: propTypes.bool.def(false),
  65. showToolbar: propTypes.bool.def(true)
  66. })
  67. /**
  68. * 组件样式
  69. */
  70. const style = computed(() => {
  71. let componentStyle = props.component.property.style
  72. if (!componentStyle) {
  73. return {}
  74. }
  75. return {
  76. marginTop: `${componentStyle.marginTop || 0}px`,
  77. marginBottom: `${componentStyle.marginBottom || 0}px`,
  78. marginLeft: `${componentStyle.marginLeft || 0}px`,
  79. marginRight: `${componentStyle.marginRight || 0}px`,
  80. paddingTop: `${componentStyle.paddingTop || 0}px`,
  81. paddingRight: `${componentStyle.paddingRight || 0}px`,
  82. paddingBottom: `${componentStyle.paddingBottom || 0}px`,
  83. paddingLeft: `${componentStyle.paddingLeft || 0}px`,
  84. borderTopLeftRadius: `${componentStyle.borderTopLeftRadius || 0}px`,
  85. borderTopRightRadius: `${componentStyle.borderTopRightRadius || 0}px`,
  86. borderBottomRightRadius: `${componentStyle.borderBottomRightRadius || 0}px`,
  87. borderBottomLeftRadius: `${componentStyle.borderBottomLeftRadius || 0}px`,
  88. overflow: 'hidden',
  89. background:
  90. componentStyle.bgType === 'color' ? componentStyle.bgColor : `url(${componentStyle.bgImg})`
  91. }
  92. })
  93. const emits = defineEmits<{
  94. (e: 'move', direction: number): void
  95. (e: 'copy'): void
  96. (e: 'delete'): void
  97. }>()
  98. /**
  99. * 移动组件
  100. * @param direction 移动方向
  101. */
  102. const handleMoveComponent = (direction: number) => {
  103. emits('move', direction)
  104. }
  105. /**
  106. * 复制组件
  107. */
  108. const handleCopyComponent = () => {
  109. emits('copy')
  110. }
  111. /**
  112. * 删除组件
  113. */
  114. const handleDeleteComponent = () => {
  115. emits('delete')
  116. }
  117. </script>
  118. <style scoped lang="scss">
  119. $active-border-width: 2px;
  120. $hover-border-width: 1px;
  121. $name-position: -85px;
  122. $toolbar-position: -55px;
  123. /* 组件 */
  124. .component {
  125. position: relative;
  126. cursor: move;
  127. .component-wrap {
  128. position: absolute;
  129. top: 0;
  130. left: -$active-border-width;
  131. display: block;
  132. width: 100%;
  133. height: 100%;
  134. /* 鼠标放到组件上时 */
  135. &:hover {
  136. border: $hover-border-width dashed var(--el-color-primary);
  137. box-shadow: 0 0 5px 0 rgb(24 144 255 / 30%);
  138. .component-name {
  139. top: $hover-border-width;
  140. /* 防止加了边框之后,位置移动 */
  141. left: $name-position - $hover-border-width;
  142. }
  143. }
  144. /* 左侧:组件名称 */
  145. .component-name {
  146. position: absolute;
  147. top: $active-border-width;
  148. left: $name-position;
  149. display: block;
  150. width: 80px;
  151. height: 25px;
  152. font-size: 12px;
  153. line-height: 25px;
  154. text-align: center;
  155. background: #fff;
  156. box-shadow:
  157. 0 0 4px #00000014,
  158. 0 2px 6px #0000000f,
  159. 0 4px 8px 2px #0000000a;
  160. /* 右侧小三角 */
  161. &::after {
  162. position: absolute;
  163. top: 7.5px;
  164. right: -10px;
  165. width: 0;
  166. height: 0;
  167. border: 5px solid transparent;
  168. border-left-color: #fff;
  169. content: ' ';
  170. }
  171. }
  172. /* 右侧:组件操作工具栏 */
  173. .component-toolbar {
  174. position: absolute;
  175. top: 0;
  176. right: $toolbar-position;
  177. display: none;
  178. /* 左侧小三角 */
  179. &::before {
  180. position: absolute;
  181. top: 10px;
  182. left: -10px;
  183. width: 0;
  184. height: 0;
  185. border: 5px solid transparent;
  186. border-right-color: #2d8cf0;
  187. content: ' ';
  188. }
  189. }
  190. }
  191. /* 组件选中时 */
  192. &.active {
  193. margin-bottom: 4px;
  194. .component-wrap {
  195. margin-bottom: $active-border-width + $active-border-width;
  196. border: $active-border-width solid var(--el-color-primary) !important;
  197. box-shadow: 0 0 10px 0 rgb(24 144 255 / 30%);
  198. .component-name {
  199. top: 0 !important;
  200. /* 防止加了边框之后,位置移动 */
  201. left: $name-position - $active-border-width !important;
  202. color: #fff;
  203. background: var(--el-color-primary);
  204. &::after {
  205. border-left-color: var(--el-color-primary);
  206. }
  207. }
  208. .component-toolbar {
  209. display: block;
  210. }
  211. }
  212. }
  213. }
  214. </style>