123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- <script setup lang="ts">
- import { ref } from 'vue'
- import DraggableCard from './DraggableCard.vue'
- import { Document } from '@element-plus/icons-vue'
- import type { MarkdownDashBoard, Position, Size, Content, AddCardData } from './types'
- defineProps<{
- cards: MarkdownDashBoard[]
- }>()
- const emit = defineEmits<{
- // eslint-disable-next-line no-unused-vars
- (e: 'updatePosition', id: string, position: Position): void
- // eslint-disable-next-line no-unused-vars
- (e: 'updateSize', id: string, size: Size): void
- // eslint-disable-next-line no-unused-vars
- (e: 'updateContent', id: string, content: Content): void
- // eslint-disable-next-line no-unused-vars
- (e: 'removeCard', id: string): void
- // eslint-disable-next-line no-unused-vars
- (e: 'addCard', cardData: AddCardData): void
- }>()
- const designerContainer = ref<HTMLElement>()
- // 处理卡片位置更新
- const handleCardPositionUpdate = (id: string, position: Position) => {
- emit('updatePosition', id, position)
- }
- // 处理卡片大小更新
- const handleCardSizeUpdate = (id: string, size: Size) => {
- emit('updateSize', id, size)
- }
- // 处理卡片内容更新
- const handleCardContentUpdate = (id: string, content: Content) => {
- emit('updateContent', id, content)
- }
- // 处理卡片删除
- const handleCardRemove = (id: string) => {
- emit('removeCard', id)
- }
- // 处理拖拽放置
- const handleDrop = (event: DragEvent) => {
- event.preventDefault()
- if (!designerContainer.value) return
- const rect = designerContainer.value.getBoundingClientRect()
- const x = ((event.clientX - rect.left) / rect.width) * 100
- const y = ((event.clientY - rect.top) / rect.height) * 100
- // 从拖拽数据中获取组件信息
- const dragData = event.dataTransfer?.getData('text/plain')
- if (dragData) {
- try {
- const componentData = JSON.parse(dragData)
- // 限制在画布范围内
- const constrainedX = Math.max(0, Math.min(70, x)) // 预留30%宽度
- const constrainedY = Math.max(0, Math.min(75, y)) // 预留25%高度
- debugger
- emit('addCard', {
- ...componentData,
- x: constrainedX,
- y: constrainedY
- })
- } catch (error) {
- console.error('Invalid drag data:', error)
- }
- }
- }
- const handleDragOver = (event: DragEvent) => {
- event.preventDefault()
- }
- </script>
- <template>
- <div class="dashboard-designer">
- <div class="designer-header">
- <h3>设计画布</h3>
- <div class="designer-info">
- <span>{{ cards.length }} 个组件</span>
- </div>
- </div>
- <div
- ref="designerContainer"
- class="designer-canvas"
- @drop="handleDrop"
- @dragover="handleDragOver"
- >
- <!-- 网格背景 -->
- <div class="grid-background"></div>
- <!-- 渲染所有卡片 -->
- <DraggableCard
- v-for="card in cards"
- :key="card.id"
- :card="card"
- @update-position="handleCardPositionUpdate"
- @update-size="handleCardSizeUpdate"
- @update-content="handleCardContentUpdate"
- @remove="handleCardRemove"
- />
- <!-- 空状态提示 -->
- <div v-if="cards.length === 0" class="empty-canvas">
- <div class="empty-icon">
- <el-icon :size="60" color="#d1d5db">
- <Document />
- </el-icon>
- </div>
- <div class="empty-text">
- <h3>开始设计您的仪表板</h3>
- <p>从右侧组件库拖拽组件到此处</p>
- </div>
- </div>
- </div>
- </div>
- </template>
- <style scoped lang="scss">
- .dashboard-designer {
- height: 100%;
- display: flex;
- flex-direction: column;
- }
- .designer-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px 20px;
- border-bottom: 1px solid var(--el-border-color-light);
- background: var(--el-fill-color-extra-light);
- h3 {
- margin: 0;
- color: var(--el-text-color-primary);
- font-size: 16px;
- font-weight: 600;
- }
- .designer-info {
- font-size: 14px;
- color: var(--el-text-color-regular);
- }
- }
- .designer-canvas {
- flex: 1;
- position: relative;
- overflow: hidden;
- background: var(--el-bg-color-page);
- min-height: 500px;
- }
- .grid-background {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-image:
- linear-gradient(to right, var(--el-border-color-extra-light) 1px, transparent 1px),
- linear-gradient(to bottom, var(--el-border-color-extra-light) 1px, transparent 1px);
- background-size: 20px 20px;
- pointer-events: none;
- opacity: 0.5;
- }
- .empty-canvas {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- text-align: center;
- color: var(--el-text-color-secondary);
- .empty-icon {
- margin-bottom: 16px;
- opacity: 0.6;
- }
- .empty-text {
- h3 {
- margin: 0 0 8px 0;
- font-size: 18px;
- font-weight: 500;
- color: var(--el-text-color-regular);
- }
- p {
- margin: 0;
- font-size: 14px;
- color: var(--el-text-color-secondary);
- }
- }
- }
- </style>
|