|
@@ -1,6 +1,6 @@
|
|
|
<script setup lang="ts">
|
|
|
import { ref, computed, onUnmounted } from 'vue'
|
|
|
-import { Delete, MoreFilled, Edit } from '@element-plus/icons-vue'
|
|
|
+import { Delete, Edit } from '@element-plus/icons-vue'
|
|
|
import Markdown from '/@/components/markdown/Markdown.vue'
|
|
|
import type { MarkdownDashBoard, Position, Size, Content, ResizeType } from './types'
|
|
|
import { MarkdownPlugin } from '/@/components/markdown/type/markdown'
|
|
@@ -36,6 +36,10 @@ const showEditDialog = ref(false)
|
|
|
const editTitle = ref('')
|
|
|
const editData = ref('')
|
|
|
|
|
|
+// 右键菜单相关状态
|
|
|
+const showContextMenu = ref(false)
|
|
|
+const contextMenuPosition = ref({ x: 0, y: 0 })
|
|
|
+
|
|
|
// 计算卡片样式
|
|
|
const cardStyle = computed(() => ({
|
|
|
position: 'absolute' as const,
|
|
@@ -174,6 +178,36 @@ const cancelEdit = () => {
|
|
|
// 删除卡片
|
|
|
const handleRemove = () => {
|
|
|
emit('remove', props.card.id)
|
|
|
+ showContextMenu.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 处理右键菜单
|
|
|
+const handleContextMenu = (event: MouseEvent) => {
|
|
|
+ event.preventDefault()
|
|
|
+ event.stopPropagation()
|
|
|
+
|
|
|
+ contextMenuPosition.value = {
|
|
|
+ x: event.clientX,
|
|
|
+ y: event.clientY
|
|
|
+ }
|
|
|
+ showContextMenu.value = true
|
|
|
+
|
|
|
+ // 点击其他地方关闭菜单
|
|
|
+ const closeMenu = () => {
|
|
|
+ showContextMenu.value = false
|
|
|
+ document.removeEventListener('click', closeMenu)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 延迟添加事件监听器,避免立即触发
|
|
|
+ setTimeout(() => {
|
|
|
+ document.addEventListener('click', closeMenu)
|
|
|
+ }, 0)
|
|
|
+}
|
|
|
+
|
|
|
+// 处理右键菜单编辑
|
|
|
+const handleContextEdit = () => {
|
|
|
+ handleEdit()
|
|
|
+ showContextMenu.value = false
|
|
|
}
|
|
|
|
|
|
// 清理事件监听器
|
|
@@ -190,38 +224,13 @@ onUnmounted(() => {
|
|
|
ref="cardRef"
|
|
|
:style="cardStyle"
|
|
|
:class="['draggable-card', { 'is-dragging': isDragging, 'is-resizing': isResizing }]"
|
|
|
+ @contextmenu="handleContextMenu"
|
|
|
>
|
|
|
- <el-card class="card-content" shadow="hover">
|
|
|
+ <el-card class="card-content" shadow="hover" @mousedown="startDrag">
|
|
|
<!-- 卡片标题栏 - 可拖拽区域 -->
|
|
|
- <template #header>
|
|
|
- <div
|
|
|
- class="card-header"
|
|
|
- @mousedown="startDrag"
|
|
|
- >
|
|
|
+ <template #header v-if="card.title !== undefined">
|
|
|
+ <div class="card-header">
|
|
|
<span class="card-title">{{ card.title }}</span>
|
|
|
- <div class="card-actions">
|
|
|
- <el-dropdown trigger="click" placement="bottom-end">
|
|
|
- <el-button
|
|
|
- type="text"
|
|
|
- :icon="MoreFilled"
|
|
|
- size="small"
|
|
|
- class="action-btn"
|
|
|
- @click.stop
|
|
|
- />
|
|
|
- <template #dropdown>
|
|
|
- <el-dropdown-menu>
|
|
|
- <el-dropdown-item @click="handleEdit">
|
|
|
- <el-icon><Edit /></el-icon>
|
|
|
- <span>编辑</span>
|
|
|
- </el-dropdown-item>
|
|
|
- <el-dropdown-item @click="handleRemove">
|
|
|
- <el-icon><Delete /></el-icon>
|
|
|
- <span>删除</span>
|
|
|
- </el-dropdown-item>
|
|
|
- </el-dropdown-menu>
|
|
|
- </template>
|
|
|
- </el-dropdown>
|
|
|
- </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
@@ -256,62 +265,83 @@ onUnmounted(() => {
|
|
|
></div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 编辑对话框 -->
|
|
|
- <el-dialog
|
|
|
- v-model="showEditDialog"
|
|
|
- title="编辑卡片内容"
|
|
|
- width="600px"
|
|
|
- :before-close="cancelEdit"
|
|
|
- append-to-body
|
|
|
+ <!-- 右键菜单 -->
|
|
|
+ <Teleport to="body">
|
|
|
+ <div
|
|
|
+ v-if="showContextMenu"
|
|
|
+ class="context-menu"
|
|
|
+ :style="{
|
|
|
+ left: contextMenuPosition.x + 'px',
|
|
|
+ top: contextMenuPosition.y + 'px'
|
|
|
+ }"
|
|
|
>
|
|
|
- <div class="edit-form">
|
|
|
- <div class="form-item">
|
|
|
- <label class="form-label">卡片标题</label>
|
|
|
- <el-input
|
|
|
- v-model="editTitle"
|
|
|
- placeholder="请输入卡片标题"
|
|
|
- maxlength="50"
|
|
|
- show-word-limit
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <div class="context-menu-item" @click="handleContextEdit">
|
|
|
+ <el-icon><Edit /></el-icon>
|
|
|
+ <span>编辑</span>
|
|
|
+ </div>
|
|
|
+ <div class="context-menu-item context-menu-item-danger" @click="handleRemove">
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
+ <span>删除</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Teleport>
|
|
|
+
|
|
|
+ <!-- 编辑对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="showEditDialog"
|
|
|
+ title="编辑卡片内容"
|
|
|
+ width="600px"
|
|
|
+ :before-close="cancelEdit"
|
|
|
+ append-to-body
|
|
|
+ >
|
|
|
+ <div class="edit-form">
|
|
|
+ <div class="form-item">
|
|
|
+ <label class="form-label">卡片标题</label>
|
|
|
+ <el-input
|
|
|
+ v-model="editTitle"
|
|
|
+ placeholder="请输入卡片标题"
|
|
|
+ maxlength="50"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="form-item">
|
|
|
- <label class="form-label">卡片内容 (支持Markdown)</label>
|
|
|
- <el-input
|
|
|
- v-model="editData"
|
|
|
- type="textarea"
|
|
|
- placeholder="请输入卡片内容,支持Markdown语法"
|
|
|
- :rows="8"
|
|
|
- resize="vertical"
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <div class="form-item">
|
|
|
+ <label class="form-label">卡片内容 (支持Markdown)</label>
|
|
|
+ <el-input
|
|
|
+ v-model="editData"
|
|
|
+ type="textarea"
|
|
|
+ placeholder="请输入卡片内容,支持Markdown语法"
|
|
|
+ :rows="8"
|
|
|
+ resize="vertical"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="preview-section">
|
|
|
- <label class="form-label">预览效果</label>
|
|
|
- <div class="preview-container">
|
|
|
- <Markdown
|
|
|
- :content="editData || '暂无内容'"
|
|
|
- :plugins="plugin"
|
|
|
- class="preview-content"
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <div class="preview-section">
|
|
|
+ <label class="form-label">预览效果</label>
|
|
|
+ <div class="preview-container">
|
|
|
+ <Markdown
|
|
|
+ :content="editData || '暂无内容'"
|
|
|
+ :plugins="plugin"
|
|
|
+ class="preview-content"
|
|
|
+ />
|
|
|
</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <template #footer>
|
|
|
- <div class="dialog-footer">
|
|
|
- <el-button @click="cancelEdit">取消</el-button>
|
|
|
- <el-button
|
|
|
- type="primary"
|
|
|
- @click="confirmEdit"
|
|
|
- :disabled="!editTitle.trim() || !editData.trim()"
|
|
|
- >
|
|
|
- 确定
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
- </div>
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="cancelEdit">取消</el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="confirmEdit"
|
|
|
+ :disabled="!editTitle.trim() || !editData.trim()"
|
|
|
+ >
|
|
|
+ 确定
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+</div>
|
|
|
</template>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
@@ -376,30 +406,6 @@ onUnmounted(() => {
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
-.card-actions {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 4px;
|
|
|
- opacity: 0;
|
|
|
- transition: opacity 0.2s ease;
|
|
|
-}
|
|
|
-
|
|
|
-.card-header:hover .card-actions {
|
|
|
- opacity: 1;
|
|
|
-}
|
|
|
-
|
|
|
-.action-btn {
|
|
|
- width: 24px !important;
|
|
|
- height: 24px !important;
|
|
|
- padding: 0 !important;
|
|
|
- color: var(--el-text-color-regular);
|
|
|
-
|
|
|
- &:hover {
|
|
|
- color: var(--el-color-primary);
|
|
|
- background: var(--el-color-primary-light-9) !important;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
.card-body {
|
|
|
height: 100%;
|
|
|
overflow: auto;
|
|
@@ -546,33 +552,47 @@ onUnmounted(() => {
|
|
|
text-align: right;
|
|
|
}
|
|
|
|
|
|
-/* 下拉菜单样式 */
|
|
|
-:deep(.el-dropdown-menu) {
|
|
|
- .el-dropdown-menu__item {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 8px;
|
|
|
- padding: 8px 16px;
|
|
|
+/* 右键菜单样式 */
|
|
|
+.context-menu {
|
|
|
+ position: fixed;
|
|
|
+ z-index: 9999;
|
|
|
+ background: var(--el-bg-color-overlay);
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
+ border-radius: 6px;
|
|
|
+ box-shadow: var(--el-box-shadow-light);
|
|
|
+ padding: 4px 0;
|
|
|
+ min-width: 120px;
|
|
|
+ backdrop-filter: blur(12px);
|
|
|
+}
|
|
|
|
|
|
- .el-icon {
|
|
|
- font-size: 14px;
|
|
|
- color: var(--el-text-color-regular);
|
|
|
- }
|
|
|
+.context-menu-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 8px 16px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ font-size: 14px;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
|
- span {
|
|
|
- font-size: 14px;
|
|
|
- color: var(--el-text-color-primary);
|
|
|
- }
|
|
|
+ .el-icon {
|
|
|
+ font-size: 14px;
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
+ }
|
|
|
|
|
|
- &:hover {
|
|
|
- background: var(--el-fill-color-light);
|
|
|
+ &:hover {
|
|
|
+ background: var(--el-fill-color-light);
|
|
|
|
|
|
- .el-icon {
|
|
|
- color: var(--el-color-primary);
|
|
|
- }
|
|
|
+ .el-icon {
|
|
|
+ color: var(--el-color-primary);
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ &.context-menu-item-danger {
|
|
|
+ &:hover {
|
|
|
+ background: var(--el-color-danger-light-9);
|
|
|
+ color: var(--el-color-danger);
|
|
|
|
|
|
- &:hover:has(.el-icon:contains('Delete')) {
|
|
|
.el-icon {
|
|
|
color: var(--el-color-danger);
|
|
|
}
|