mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-29 02:38:45 +08:00
feat: File list supports right menu operation (#10056)
This commit is contained in:
parent
fd1e0e72a0
commit
0637ca308b
3 changed files with 68 additions and 180 deletions
|
|
@ -46,25 +46,22 @@
|
|||
</slot>
|
||||
</div>
|
||||
|
||||
<div
|
||||
<ul
|
||||
v-if="rightClick.visible"
|
||||
class="custom-context-menu"
|
||||
:style="{
|
||||
left: rightClick.left + 'px',
|
||||
top: rightClick.top + 'px',
|
||||
}"
|
||||
class="context-menu"
|
||||
ref="menuRef"
|
||||
:style="{ top: `${adjustedY}px`, left: `${adjustedX}px` }"
|
||||
@click.stop
|
||||
>
|
||||
<el-button
|
||||
class="no-border-button"
|
||||
v-for="(btn, i) in rightButtons"
|
||||
:disabled="disabled(btn)"
|
||||
@click="rightButtonClick(btn)"
|
||||
:key="i"
|
||||
:command="btn"
|
||||
<li
|
||||
v-for="(btn, index) in rightButtons"
|
||||
:key="index"
|
||||
:class="[{ disabled: disabled(btn) }, { divided: btn.divided }]"
|
||||
@click="!disabled(btn) && rightButtonClick(btn)"
|
||||
>
|
||||
{{ btn.label }}
|
||||
</el-button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
|
@ -105,6 +102,7 @@ const mobile = computed(() => {
|
|||
});
|
||||
const tableRef = ref();
|
||||
const tableHeight = ref(0);
|
||||
const menuRef = ref<HTMLElement | null>(null);
|
||||
|
||||
const rightClick = ref({
|
||||
visible: false,
|
||||
|
|
@ -164,6 +162,34 @@ function clearSort() {
|
|||
tableRef.value.refElTable.clearSort();
|
||||
}
|
||||
|
||||
const adjustedX = ref(rightClick.value.left);
|
||||
const adjustedY = ref(rightClick.value.top);
|
||||
|
||||
watch(
|
||||
() => [rightClick.value.left, rightClick.value.top],
|
||||
async () => {
|
||||
await nextTick();
|
||||
if (!menuRef.value) return;
|
||||
|
||||
const menuRect = menuRef.value.getBoundingClientRect();
|
||||
const windowWidth = window.innerWidth;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
if (rightClick.value.left + menuRect.width > windowWidth) {
|
||||
adjustedX.value = windowWidth - menuRect.width - 4;
|
||||
} else {
|
||||
adjustedX.value = rightClick.value.left;
|
||||
}
|
||||
|
||||
if (rightClick.value.top + menuRect.height > windowHeight) {
|
||||
adjustedY.value = windowHeight - menuRect.height - 4;
|
||||
} else {
|
||||
adjustedY.value = rightClick.value.top;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
clearSelects,
|
||||
sort,
|
||||
|
|
@ -197,7 +223,7 @@ onMounted(() => {
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style scoped lang="scss">
|
||||
@use '@/styles/mixins.scss' as *;
|
||||
|
||||
.complex-table {
|
||||
|
|
@ -223,20 +249,32 @@ onMounted(() => {
|
|||
@include flex-row(flex-end);
|
||||
}
|
||||
}
|
||||
.custom-context-menu {
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
border: 1px solid #dcdfe6;
|
||||
background: var(--panel-main-bg-color-9);
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
width: 80px;
|
||||
color: var(--el-color-primary);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
list-style: none;
|
||||
font-size: 14px;
|
||||
padding: 4px 0;
|
||||
margin: 0;
|
||||
z-index: 9999;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.no-border-button {
|
||||
border: 0;
|
||||
border-radius: 4;
|
||||
margin: 0 !important;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
.context-menu li {
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.context-menu li:hover {
|
||||
background-color: var(--panel-menu-bg-color);
|
||||
}
|
||||
.context-menu li.disabled {
|
||||
color: var(--el-border-color);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.context-menu li.divided {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
<template>
|
||||
<ul class="context-menu" ref="menuRef" :style="{ top: `${adjustedY}px`, left: `${adjustedX}px` }" @click.stop>
|
||||
<li
|
||||
v-for="(btn, index) in buttons"
|
||||
:key="index"
|
||||
:class="[{ disabled: isDisabled(btn) }, { divided: btn.divided }]"
|
||||
@click="!isDisabled(btn) && onClick(btn)"
|
||||
>
|
||||
{{ btn.label }}
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
x: number;
|
||||
y: number;
|
||||
row: any;
|
||||
buttons: {
|
||||
label: string;
|
||||
click: (row: any) => void;
|
||||
disabled?: boolean | ((row: any) => boolean);
|
||||
divided?: boolean;
|
||||
}[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const menuRef = ref<HTMLElement | null>(null);
|
||||
const onClick = (btn: any) => {
|
||||
btn.click?.(props.row);
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const isDisabled = computed(() => {
|
||||
return function (btn: any) {
|
||||
return typeof btn.disabled === 'function' ? btn.disabled(props.row) : btn.disabled;
|
||||
};
|
||||
});
|
||||
|
||||
const adjustedX = ref(props.x);
|
||||
const adjustedY = ref(props.y);
|
||||
|
||||
watch(
|
||||
() => [props.x, props.y],
|
||||
async () => {
|
||||
await nextTick(); // 确保菜单渲染完成
|
||||
if (!menuRef.value) return;
|
||||
|
||||
const menuRect = menuRef.value.getBoundingClientRect();
|
||||
const windowWidth = window.innerWidth;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
// 修正横向
|
||||
if (props.x + menuRect.width > windowWidth) {
|
||||
adjustedX.value = windowWidth - menuRect.width - 4; // 留点边距
|
||||
} else {
|
||||
adjustedX.value = props.x;
|
||||
}
|
||||
|
||||
// 修正纵向
|
||||
if (props.y + menuRect.height > windowHeight) {
|
||||
adjustedY.value = windowHeight - menuRect.height - 4;
|
||||
} else {
|
||||
adjustedY.value = props.y;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const handleClickOutside = () => emit('close');
|
||||
|
||||
onMounted(() => document.addEventListener('click', () => handleClickOutside));
|
||||
onUnmounted(() => document.removeEventListener('click', () => handleClickOutside));
|
||||
</script>
|
||||
<style scoped>
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
background: var(--panel-main-bg-color-9);
|
||||
border: 1px solid var(--el-border-color);
|
||||
color: var(--el-color-primary);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
list-style: none;
|
||||
font-size: 14px;
|
||||
padding: 4px 0;
|
||||
margin: 0;
|
||||
z-index: 9999;
|
||||
min-width: 120px;
|
||||
}
|
||||
.context-menu li {
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.context-menu li:hover {
|
||||
background-color: var(--panel-menu-bg-color);
|
||||
}
|
||||
.context-menu li.disabled {
|
||||
color: var(--el-border-color);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.context-menu li.divided {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,11 +1,5 @@
|
|||
<template>
|
||||
<div
|
||||
@dragover="handleDragover"
|
||||
@drop="handleDrop"
|
||||
@dragleave="handleDragleave"
|
||||
@contextmenu.prevent="onRightClick"
|
||||
@click="onRightClick"
|
||||
>
|
||||
<div @dragover="handleDragover" @drop="handleDrop" @dragleave="handleDragleave">
|
||||
<div class="flex sm:flex-row flex-col justify-start gap-y-2 items-center gap-x-4" ref="toolRef">
|
||||
<div class="flex-shrink-0 flex sm:w-min w-full items-center justify-start">
|
||||
<el-tooltip :content="$t('file.back')" placement="top">
|
||||
|
|
@ -394,7 +388,7 @@
|
|||
@cell-mouse-enter="showFavorite"
|
||||
@cell-mouse-leave="hideFavorite"
|
||||
:heightDiff="300"
|
||||
@row-contextmenu="openRight"
|
||||
:right-buttons="buttons"
|
||||
>
|
||||
<el-table-column type="selection" width="30" />
|
||||
<el-table-column
|
||||
|
|
@ -542,14 +536,6 @@
|
|||
<VscodeOpenDialog ref="dialogVscodeOpenRef" />
|
||||
<Preview ref="previewRef" />
|
||||
<TerminalDialog ref="dialogTerminalRef" />
|
||||
<RightContextMenu
|
||||
v-if="menuVisible"
|
||||
:x="menuPosition.x"
|
||||
:y="menuPosition.y"
|
||||
:row="currentRow"
|
||||
:buttons="buttons"
|
||||
@close="hideRightMenu"
|
||||
/>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -880,7 +866,6 @@ const top = () => {
|
|||
};
|
||||
|
||||
const jump = async (url: string) => {
|
||||
hideRightMenu();
|
||||
history.splice(pointer + 1);
|
||||
history.push(url);
|
||||
pointer = history.length - 1;
|
||||
|
|
@ -1441,36 +1426,6 @@ const handleDragleave = (event: { preventDefault: () => void }) => {
|
|||
event.preventDefault();
|
||||
};
|
||||
|
||||
const menuVisible = ref(false);
|
||||
const noMenuVisible = ref(false);
|
||||
const menuPosition = reactive({ x: 0, y: 0 });
|
||||
const currentRow = ref<File.File>({} as File.File);
|
||||
|
||||
function openRight(row: File.File, _column: any, event: MouseEvent) {
|
||||
noMenuVisible.value = true;
|
||||
if (row.name === currentRow.value?.name && menuVisible.value) {
|
||||
menuVisible.value = false;
|
||||
} else {
|
||||
event.preventDefault();
|
||||
currentRow.value = row;
|
||||
menuPosition.x = event.clientX;
|
||||
menuPosition.y = event.clientY;
|
||||
menuVisible.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function hideRightMenu() {
|
||||
menuVisible.value = false;
|
||||
}
|
||||
|
||||
function onRightClick() {
|
||||
if (noMenuVisible.value) {
|
||||
noMenuVisible.value = false;
|
||||
} else {
|
||||
menuVisible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (localStorage.getItem('show-hidden') === null) {
|
||||
localStorage.setItem('show-hidden', 'true');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue