2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 04:22:33 +08:00

Add editor close confirmation, path copy on double-click, and disable overlay close (#1314)

* Initial plan

* Implement file management optimization features

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* Improve error handling in file save dialog

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* Disable overlay click to close editor window

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* Fix comment clarity for closeOnOverlay prop

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
This commit is contained in:
Copilot
2026-02-01 17:03:33 +08:00
committed by GitHub
parent c5667d8ce4
commit ba12def9f2
4 changed files with 99 additions and 8 deletions

View File

@@ -12,13 +12,16 @@ const props = withDefaults(
minHeight?: number
defaultWidth?: number
defaultHeight?: number
beforeClose?: () => Promise<boolean> | boolean // 关闭前的确认回调,返回 true 继续关闭false 取消关闭
closeOnOverlay?: boolean // 点击遮罩层是否最小化窗口,默认 true
}>(),
{
title: '',
minWidth: 400,
minHeight: 300,
defaultWidth: 800,
defaultHeight: 600
defaultHeight: 600,
closeOnOverlay: true
}
)
@@ -200,8 +203,20 @@ function restore() {
minimized.value = false
}
// 处理遮罩层点击
function handleOverlayClick() {
if (props.closeOnOverlay) {
minimize()
}
}
// 关闭
function close() {
async function close() {
// 如果提供了 beforeClose 回调,先执行它
if (props.beforeClose) {
const result = await props.beforeClose()
if (!result) return // 如果返回 false取消关闭
}
show.value = false
}
@@ -240,7 +255,7 @@ onBeforeUnmount(() => {
<Teleport to="body">
<!-- 遮罩层 -->
<Transition name="fade">
<div v-if="show && !minimized" class="draggable-window-overlay" @click="minimize" />
<div v-if="show && !minimized" class="draggable-window-overlay" @click="handleOverlayClick" />
</Transition>
<!-- 主窗口 -->

View File

@@ -25,6 +25,63 @@ const initialPath = computed(() => {
return parts.join('/') || '/'
})
// 关闭前确认
async function handleBeforeClose(): Promise<boolean> {
// 检查是否有未保存的文件
if (!editorStore.hasUnsavedFiles) {
return true // 没有未保存的文件,直接关闭
}
// 显示确认对话框
return new Promise((resolve) => {
window.$dialog.warning({
title: $gettext('Unsaved Changes'),
content: $gettext('You have unsaved changes. Do you want to save them before closing?'),
positiveText: $gettext('Save'),
negativeText: $gettext('Cancel'),
onPositiveClick: async () => {
// 保存所有未保存的文件
const unsavedTabs = editorStore.unsavedTabs
let allSaved = true
const failedFiles: string[] = []
for (const tab of unsavedTabs) {
try {
await new Promise<void>((resolveInner, rejectInner) => {
useRequest(file.save(tab.path, tab.content))
.onSuccess(() => {
editorStore.markSaved(tab.path)
resolveInner()
})
.onError(() => {
allSaved = false
failedFiles.push(tab.path)
rejectInner()
})
})
} catch {
// 保存失败,已记录到 failedFiles 数组中
// 继续尝试保存其他文件
}
}
if (allSaved) {
window.$message.success($gettext('All files saved successfully'))
resolve(true) // 保存成功,关闭窗口
} else {
// 显示失败的文件列表
const fileList = failedFiles.map(f => f.split('/').pop()).join(', ')
window.$message.error($gettext('Failed to save files: %{ files }', { files: fileList }))
resolve(false) // 保存失败,不关闭窗口
}
},
onNegativeClick: () => {
resolve(false) // 用户取消,不关闭窗口
}
})
})
}
// 加载文件
function loadFile(path: string) {
if (!path) return
@@ -97,6 +154,8 @@ watch(minimized, (isMinimized) => {
:default-height="defaultHeight"
:min-width="600"
:min-height="400"
:before-close="handleBeforeClose"
:close-on-overlay="false"
>
<FileEditorView ref="editorRef" :initial-path="initialPath" />
</DraggableWindow>

View File

@@ -4,6 +4,7 @@ import { useGettext } from 'vue3-gettext'
import { useFileStore } from '@/store'
import { checkPath } from '@/utils/file'
import copy2clipboard from '@vavt/copy2clipboard'
const { $gettext } = useGettext()
const fileStore = useFileStore()
@@ -24,6 +25,16 @@ const handleInput = () => {
})
}
// 双击地址栏复制路径
const handlePathDoubleClick = async () => {
try {
await copy2clipboard(path.value)
window.$message.success($gettext('Path copied to clipboard'))
} catch (error) {
window.$message.error($gettext('Failed to copy path'))
}
}
const handleBlur = () => {
input.value = input.value.replace(/(^\/)|(\/$)/g, '')
if (!checkPath(input.value)) {
@@ -140,7 +151,13 @@ onUnmounted(() => {
</n-tooltip>
</n-button-group>
<n-input-group flex-1>
<n-tag size="large" v-if="!isInput" flex-1 @click="handleInput">
<n-tag
size="large"
v-if="!isInput"
flex-1
@click="handleInput"
@dblclick="handlePathDoubleClick"
>
<n-breadcrumb separator=">">
<n-breadcrumb-item @click.stop="setPath(-1)">
{{ $gettext('Root Directory') }}

View File

@@ -9,6 +9,7 @@ import CreateModal from '@/views/ssh/CreateModal.vue'
import UpdateModal from '@/views/ssh/UpdateModal.vue'
import '@fontsource-variable/jetbrains-mono/wght-italic.css'
import '@fontsource-variable/jetbrains-mono/wght.css'
import copy2clipboard from '@vavt/copy2clipboard'
import { ClipboardAddon } from '@xterm/addon-clipboard'
import { FitAddon } from '@xterm/addon-fit'
import { Unicode11Addon } from '@xterm/addon-unicode11'
@@ -220,8 +221,7 @@ const initTerminal = async (tabId: string) => {
try {
// 根据ID选择连接方式
const socket = tab.hostId === LOCAL_SERVER_ID ? await ws.pty('bash') : await ws.ssh(tab.hostId)
tab.ws = socket
tab.ws = tab.hostId === LOCAL_SERVER_ID ? await ws.pty('bash') : await ws.ssh(tab.hostId)
tab.ws.binaryType = 'arraybuffer'
tab.terminal = new Terminal({
@@ -252,7 +252,7 @@ const initTerminal = async (tabId: string) => {
tab.terminal.onSelectionChange(() => {
const selection = tab.terminal?.getSelection()
if (selection) {
navigator.clipboard.writeText(selection)
copy2clipboard(selection)
}
})
@@ -400,7 +400,7 @@ const onKeyDown = (event: KeyboardEvent) => {
event.preventDefault()
const selection = tab.terminal.getSelection()
if (selection) {
navigator.clipboard.writeText(selection)
copy2clipboard(selection)
}
}