|
@@ -0,0 +1,362 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref } from 'vue'
|
|
|
+import { Plus, Document } from '@element-plus/icons-vue'
|
|
|
+import Markdown from '/@/components/markdown/Markdown.vue'
|
|
|
+
|
|
|
+const emit = defineEmits<{
|
|
|
+ addCard: [cardData: { title: string; data: string }]
|
|
|
+}>()
|
|
|
+
|
|
|
+// 组件库数据
|
|
|
+const componentLibrary = ref([
|
|
|
+ {
|
|
|
+ id: 'quote-block',
|
|
|
+ title: '引用块',
|
|
|
+ icon: Document,
|
|
|
+ description: '用于显示引用内容',
|
|
|
+ data: `> ### 重要提示
|
|
|
+>
|
|
|
+> 这是一个引用块示例,可以用来显示重要信息、提示或者引用其他内容。
|
|
|
+>
|
|
|
+> 支持多行内容和**格式化文本**。`,
|
|
|
+ preview: `> 这是一个引用块示例\n> 可以包含重要信息`
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'data-table',
|
|
|
+ title: '数据表格',
|
|
|
+ icon: Document,
|
|
|
+ description: '用于显示结构化数据',
|
|
|
+ data: `| 指标名称 | 当前值 | 目标值 | 完成率 |
|
|
|
+|----------|--------|--------|--------|
|
|
|
+| 用户注册 | 1,234 | 1,500 | 82.3% |
|
|
|
+| 活跃用户 | 856 | 1,000 | 85.6% |
|
|
|
+| 转化率 | 12.5% | 15% | 83.3% |
|
|
|
+| 收入 | ¥45,678 | ¥50,000 | 91.4% |`,
|
|
|
+ preview: `| 指标 | 数值 | 状态 |\n|------|------|------|\n| 用户 | 1,234 | 正常 |`
|
|
|
+ }
|
|
|
+])
|
|
|
+
|
|
|
+// 拖拽开始
|
|
|
+const handleDragStart = (event: DragEvent, component: any) => {
|
|
|
+ if (!event.dataTransfer) return
|
|
|
+
|
|
|
+ event.dataTransfer.setData('text/plain', JSON.stringify({
|
|
|
+ title: component.title,
|
|
|
+ data: component.data
|
|
|
+ }))
|
|
|
+
|
|
|
+ event.dataTransfer.effectAllowed = 'copy'
|
|
|
+}
|
|
|
+
|
|
|
+// 添加组件到画布
|
|
|
+const addComponent = (component: any) => {
|
|
|
+ emit('addCard', {
|
|
|
+ title: component.title,
|
|
|
+ data: component.data
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 预览组件
|
|
|
+const previewComponent = ref<any>(null)
|
|
|
+const showPreview = ref(false)
|
|
|
+
|
|
|
+const openPreview = (component: any) => {
|
|
|
+ previewComponent.value = component
|
|
|
+ showPreview.value = true
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="component-library">
|
|
|
+ <div class="library-header">
|
|
|
+ <h3>组件库</h3>
|
|
|
+ <div class="library-info">
|
|
|
+ <span>{{ componentLibrary.length }} 个组件</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="library-content">
|
|
|
+ <div class="component-list">
|
|
|
+ <div
|
|
|
+ v-for="component in componentLibrary"
|
|
|
+ :key="component.id"
|
|
|
+ class="component-item"
|
|
|
+ draggable="true"
|
|
|
+ @dragstart="handleDragStart($event, component)"
|
|
|
+ >
|
|
|
+ <div class="component-header">
|
|
|
+ <div class="component-icon">
|
|
|
+ <el-icon :size="20">
|
|
|
+ <component :is="component.icon" />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="component-info">
|
|
|
+ <h4 class="component-title">{{ component.title }}</h4>
|
|
|
+ <p class="component-description">{{ component.description }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="component-preview">
|
|
|
+ <Markdown
|
|
|
+ :content="component.preview"
|
|
|
+ :plugins="[]"
|
|
|
+ class="preview-content"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="component-actions">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ :icon="Plus"
|
|
|
+ @click="addComponent(component)"
|
|
|
+ class="add-btn"
|
|
|
+ >
|
|
|
+ 添加到画布
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="text"
|
|
|
+ size="small"
|
|
|
+ @click="openPreview(component)"
|
|
|
+ class="preview-btn"
|
|
|
+ >
|
|
|
+ 预览
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 使用说明 -->
|
|
|
+ <div class="library-footer">
|
|
|
+ <div class="usage-tips">
|
|
|
+ <h4>使用说明</h4>
|
|
|
+ <ul>
|
|
|
+ <li>拖拽组件到左侧画布</li>
|
|
|
+ <li>或点击"添加到画布"按钮</li>
|
|
|
+ <li>在画布中拖拽标题栏调整位置</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 预览对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="showPreview"
|
|
|
+ :title="`预览 - ${previewComponent?.title}`"
|
|
|
+ width="600px"
|
|
|
+ v-if="previewComponent"
|
|
|
+ >
|
|
|
+ <div class="preview-dialog-content">
|
|
|
+ <Markdown
|
|
|
+ :content="previewComponent.data"
|
|
|
+ :plugins="[]"
|
|
|
+ class="full-preview-content"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="showPreview = false">关闭</el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="addComponent(previewComponent); showPreview = false"
|
|
|
+ >
|
|
|
+ 添加到画布
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.component-library {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.library-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;
|
|
|
+ }
|
|
|
+
|
|
|
+ .library-info {
|
|
|
+ font-size: 14px;
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.library-content {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.component-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.component-item {
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
+ border-radius: 8px;
|
|
|
+ background: var(--el-bg-color);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ cursor: grab;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: var(--el-color-primary-light-7);
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ cursor: grabbing;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.component-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 16px 16px 12px 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.component-icon {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: var(--el-color-primary-light-9);
|
|
|
+ border-radius: 8px;
|
|
|
+ color: var(--el-color-primary);
|
|
|
+}
|
|
|
+
|
|
|
+.component-info {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.component-title {
|
|
|
+ margin: 0 0 4px 0;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+}
|
|
|
+
|
|
|
+.component-description {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.component-preview {
|
|
|
+ padding: 0 16px 12px 16px;
|
|
|
+ border-top: 1px solid var(--el-border-color-extra-light);
|
|
|
+ margin-top: 8px;
|
|
|
+ padding-top: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-content {
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.4;
|
|
|
+ max-height: 80px;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ :deep(blockquote) {
|
|
|
+ margin: 4px 0;
|
|
|
+ padding: 6px 10px;
|
|
|
+ font-size: 11px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(table) {
|
|
|
+ font-size: 10px;
|
|
|
+ margin: 4px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(th), :deep(td) {
|
|
|
+ padding: 2px 6px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.component-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px 16px;
|
|
|
+ border-top: 1px solid var(--el-border-color-extra-light);
|
|
|
+ background: var(--el-fill-color-extra-light);
|
|
|
+ border-radius: 0 0 8px 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.add-btn {
|
|
|
+ flex: 1;
|
|
|
+ margin-right: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-btn {
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: var(--el-color-primary);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.library-footer {
|
|
|
+ border-top: 1px solid var(--el-border-color-light);
|
|
|
+ padding: 16px 20px;
|
|
|
+ background: var(--el-fill-color-extra-light);
|
|
|
+}
|
|
|
+
|
|
|
+.usage-tips {
|
|
|
+ h4 {
|
|
|
+ margin: 0 0 8px 0;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ ul {
|
|
|
+ margin: 0;
|
|
|
+ padding-left: 16px;
|
|
|
+
|
|
|
+ li {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ line-height: 1.5;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.preview-dialog-content {
|
|
|
+ max-height: 400px;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 16px;
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
+ border-radius: 6px;
|
|
|
+ background: var(--el-fill-color-extra-light);
|
|
|
+}
|
|
|
+
|
|
|
+.full-preview-content {
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+</style>
|