This commit is contained in:
yinsx
2026-01-23 15:23:04 +08:00
commit 5674ce116e
34 changed files with 3901 additions and 0 deletions

View File

@ -0,0 +1,163 @@
<template>
<div v-if="uiStore.contextMenu.isOpen" class="context-menu-overlay" @click="close" @contextmenu.prevent="close">
<div class="context-menu" :style="menuStyle">
<ul v-if="uiStore.contextMenu.itemType === 'icon'">
<li><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>在新标签页打开</li>
<li><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"></path><path d="m15 5 4 4"></path></svg>编辑图标</li>
<li><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><path d="m12 19-7-7 7-7 7 7-7 7Z"></path><path d="M19 5v14"></path><path d="M5 5v14"></path></svg>编辑主页</li>
<li @click="deleteItem"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><path d="M3 6h18"></path><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>删除</li>
</ul>
<ul v-else-if="uiStore.contextMenu.itemType === 'widget'">
<li class="layout-section">
<div class="layout-title">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>
<span>布局</span>
</div>
<div class="widget-size-options">
<span v-for="size in widgetSizes" :key="size" @click="changeWidgetSize(size)" class="size-option">{{ size }}</span>
</div>
</li>
<li><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"></path></svg>编辑组件</li>
<li @click="deleteItem"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><path d="M3 6h18"></path><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>删除</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useUIStore } from '@/store/useUIStore';
import { useLayoutStore } from '@/store/useLayoutStore';
import { useWidgetsStore } from '@/store/useWidgetsStore';
const uiStore = useUIStore();
const layoutStore = useLayoutStore();
const widgetsStore = useWidgetsStore();
const widgetSizes = ['1x1', '1x2', '2x1', '2x2', '2x4'];
const menuStyle = computed(() => ({
left: `${uiStore.contextMenu.x}px`,
top: `${uiStore.contextMenu.y}px`,
}));
const close = () => {
uiStore.closeContextMenu();
};
const deleteItem = () => {
if (uiStore.contextMenu.itemId) {
if (uiStore.contextMenu.itemType === 'icon') {
layoutStore.deleteIcon(uiStore.contextMenu.itemId);
} else if (uiStore.contextMenu.itemType === 'widget') {
// 组件删除逻辑
}
}
close();
};
const changeWidgetSize = (newSize: '1x1' | '1x2' | '2x1' | '2x2' | '2x4') => {
if (uiStore.contextMenu.itemId) {
widgetsStore.updateWidgetSize(uiStore.contextMenu.itemId, newSize);
}
close();
};
</script>
<style lang="scss" scoped>
@import '@/styles/tokens.scss';
.context-menu-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: $z-index-menu - 1;
}
.context-menu {
position: absolute;
width: var(--context-menu-width);
background-color: $color-surface-2;
backdrop-filter: blur($backdrop-filter-blur) saturate($backdrop-filter-saturation);
border-radius: $border-radius-medium;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: $shadow-lg;
z-index: $z-index-menu;
padding: var(--context-menu-padding);
ul {
list-style: none;
margin: 0;
padding: 0;
}
li {
display: flex;
align-items: center;
padding: var(--context-menu-item-padding-y) var(--context-menu-item-padding-x);
border-radius: $border-radius-small;
cursor: pointer;
color: $color-text-primary;
font-size: 14px;
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
&.layout-section {
padding: var(--context-menu-layout-section-padding-y) var(--context-menu-layout-section-padding-x);
flex-direction: column;
align-items: stretch;
cursor: default;
&:hover {
background-color: transparent;
}
}
}
li:last-child {
color: #ff5c5c; // 删除项浅红色
.menu-icon {
stroke: #ff5c5c;
}
}
.menu-icon {
margin-right: var(--context-menu-icon-margin-right);
width: var(--context-menu-icon-size);
height: var(--context-menu-icon-size);
}
}
.layout-title {
display: flex;
align-items: center;
font-size: 14px;
margin-bottom: var(--context-menu-layout-title-margin-bottom);
}
.widget-size-options {
display: flex;
flex-wrap: wrap;
gap: var(--context-menu-layout-options-gap);
.size-option {
display: flex;
justify-content: center;
align-items: center;
width: var(--context-menu-layout-option-width); // 固定宽度便于对齐
padding: var(--context-menu-layout-option-padding-y) 0; // 固定宽度下调整内边距
border-radius: var(--radius-pill); // 胶囊形状
background-color: rgba(255, 255, 255, 0.08);
cursor: pointer;
font-size: 12px;
&:hover {
background-color: rgba(255, 255, 255, 0.15);
}
}
}
</style>