DashboardDesigner.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. import DraggableCard from './DraggableCard.vue'
  4. import { Document } from '@element-plus/icons-vue'
  5. import type { MarkdownDashBoard, Position, Size, Content, AddCardData } from './types'
  6. defineProps<{
  7. cards: MarkdownDashBoard[]
  8. }>()
  9. const emit = defineEmits<{
  10. // eslint-disable-next-line no-unused-vars
  11. (e: 'updatePosition', id: string, position: Position): void
  12. // eslint-disable-next-line no-unused-vars
  13. (e: 'updateSize', id: string, size: Size): void
  14. // eslint-disable-next-line no-unused-vars
  15. (e: 'updateContent', id: string, content: Content): void
  16. // eslint-disable-next-line no-unused-vars
  17. (e: 'removeCard', id: string): void
  18. // eslint-disable-next-line no-unused-vars
  19. (e: 'addCard', cardData: AddCardData): void
  20. }>()
  21. const designerContainer = ref<HTMLElement>()
  22. // 处理卡片位置更新
  23. const handleCardPositionUpdate = (id: string, position: Position) => {
  24. emit('updatePosition', id, position)
  25. }
  26. // 处理卡片大小更新
  27. const handleCardSizeUpdate = (id: string, size: Size) => {
  28. emit('updateSize', id, size)
  29. }
  30. // 处理卡片内容更新
  31. const handleCardContentUpdate = (id: string, content: Content) => {
  32. emit('updateContent', id, content)
  33. }
  34. // 处理卡片删除
  35. const handleCardRemove = (id: string) => {
  36. emit('removeCard', id)
  37. }
  38. // 处理拖拽放置
  39. const handleDrop = (event: DragEvent) => {
  40. event.preventDefault()
  41. if (!designerContainer.value) return
  42. const rect = designerContainer.value.getBoundingClientRect()
  43. const x = ((event.clientX - rect.left) / rect.width) * 100
  44. const y = ((event.clientY - rect.top) / rect.height) * 100
  45. // 从拖拽数据中获取组件信息
  46. const dragData = event.dataTransfer?.getData('text/plain')
  47. if (dragData) {
  48. try {
  49. const componentData = JSON.parse(dragData)
  50. // 限制在画布范围内
  51. const constrainedX = Math.max(0, Math.min(70, x)) // 预留30%宽度
  52. const constrainedY = Math.max(0, Math.min(75, y)) // 预留25%高度
  53. debugger
  54. emit('addCard', {
  55. ...componentData,
  56. x: constrainedX,
  57. y: constrainedY
  58. })
  59. } catch (error) {
  60. console.error('Invalid drag data:', error)
  61. }
  62. }
  63. }
  64. const handleDragOver = (event: DragEvent) => {
  65. event.preventDefault()
  66. }
  67. </script>
  68. <template>
  69. <div class="dashboard-designer">
  70. <div class="designer-header">
  71. <h3>设计画布</h3>
  72. <div class="designer-info">
  73. <span>{{ cards.length }} 个组件</span>
  74. </div>
  75. </div>
  76. <div
  77. ref="designerContainer"
  78. class="designer-canvas"
  79. @drop="handleDrop"
  80. @dragover="handleDragOver"
  81. >
  82. <!-- 网格背景 -->
  83. <div class="grid-background"></div>
  84. <!-- 渲染所有卡片 -->
  85. <DraggableCard
  86. v-for="card in cards"
  87. :key="card.id"
  88. :card="card"
  89. @update-position="handleCardPositionUpdate"
  90. @update-size="handleCardSizeUpdate"
  91. @update-content="handleCardContentUpdate"
  92. @remove="handleCardRemove"
  93. />
  94. <!-- 空状态提示 -->
  95. <div v-if="cards.length === 0" class="empty-canvas">
  96. <div class="empty-icon">
  97. <el-icon :size="60" color="#d1d5db">
  98. <Document />
  99. </el-icon>
  100. </div>
  101. <div class="empty-text">
  102. <h3>开始设计您的仪表板</h3>
  103. <p>从右侧组件库拖拽组件到此处</p>
  104. </div>
  105. </div>
  106. </div>
  107. </div>
  108. </template>
  109. <style scoped lang="scss">
  110. .dashboard-designer {
  111. height: 100%;
  112. display: flex;
  113. flex-direction: column;
  114. }
  115. .designer-header {
  116. display: flex;
  117. justify-content: space-between;
  118. align-items: center;
  119. padding: 16px 20px;
  120. border-bottom: 1px solid var(--el-border-color-light);
  121. background: var(--el-fill-color-extra-light);
  122. h3 {
  123. margin: 0;
  124. color: var(--el-text-color-primary);
  125. font-size: 16px;
  126. font-weight: 600;
  127. }
  128. .designer-info {
  129. font-size: 14px;
  130. color: var(--el-text-color-regular);
  131. }
  132. }
  133. .designer-canvas {
  134. flex: 1;
  135. position: relative;
  136. overflow: hidden;
  137. background: var(--el-bg-color-page);
  138. min-height: 500px;
  139. }
  140. .grid-background {
  141. position: absolute;
  142. top: 0;
  143. left: 0;
  144. right: 0;
  145. bottom: 0;
  146. background-image:
  147. linear-gradient(to right, var(--el-border-color-extra-light) 1px, transparent 1px),
  148. linear-gradient(to bottom, var(--el-border-color-extra-light) 1px, transparent 1px);
  149. background-size: 20px 20px;
  150. pointer-events: none;
  151. opacity: 0.5;
  152. }
  153. .empty-canvas {
  154. position: absolute;
  155. top: 50%;
  156. left: 50%;
  157. transform: translate(-50%, -50%);
  158. text-align: center;
  159. color: var(--el-text-color-secondary);
  160. .empty-icon {
  161. margin-bottom: 16px;
  162. opacity: 0.6;
  163. }
  164. .empty-text {
  165. h3 {
  166. margin: 0 0 8px 0;
  167. font-size: 18px;
  168. font-weight: 500;
  169. color: var(--el-text-color-regular);
  170. }
  171. p {
  172. margin: 0;
  173. font-size: 14px;
  174. color: var(--el-text-color-secondary);
  175. }
  176. }
  177. }
  178. </style>