修复无法复制命令bug

This commit is contained in:
nnwang
2025-12-01 18:25:41 +08:00
parent 9539d0010a
commit 9e6c86e4f3

View File

@@ -1,10 +1,23 @@
<template>
<div class="full-screen-wrapper" style="width: 100vw; height: 100vh;">
<MonacoTreeEditor ref="monacoEditorRef" :font-size="14" :files="files" :sider-min-width="250" filelist-title="文件列表"
language="zh-CN" @reload="handleReload" @new-file="handleNewFile" @new-folder="handleNewFolder"
@save-file="handleSaveFile" @delete-file="handleDeleteFile" @delete-folder="handleDeleteFolder"
@rename-file="handleRename" @rename-folder="handleRename" :file-menu="fileMenu"
@contextmenu-select="handleContextMenuSelect"></MonacoTreeEditor>
<div class="full-screen-wrapper" style="width: 100vw; height: 100vh">
<MonacoTreeEditor
ref="monacoEditorRef"
:font-size="14"
:files="files"
:sider-min-width="250"
filelist-title="文件列表"
language="zh-CN"
@reload="handleReload"
@new-file="handleNewFile"
@new-folder="handleNewFolder"
@save-file="handleSaveFile"
@delete-file="handleDeleteFile"
@delete-folder="handleDeleteFolder"
@rename-file="handleRename"
@rename-folder="handleRename"
:file-menu="fileMenu"
@contextmenu-select="handleContextMenuSelect"
></MonacoTreeEditor>
<!-- 隐藏的文件上传输入框 -->
<input ref="fileInputRef" type="file" multiple style="display: none" @change="handleFileUpload" />
@@ -14,12 +27,18 @@
<style scoped>
.full-screen-wrapper :deep(.url-info-text) {
font-size: 13px;
background-color: var(--url-info-bg, rgba(0, 0, 0, 0.05));
border-color: var(--url-info-border, rgba(0, 0, 0, 0.1));
color: var(--url-info-color, inherit);
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
transition: all 0.3s;
}
.full-screen-wrapper :deep(.url-info-text:hover) {
background-color: var(--url-info-bg-hover, rgba(0, 0, 0, 0.1));
}
.full-screen-wrapper :deep(.message-container) {
user-select: none;
@@ -31,7 +50,6 @@
/* 上传按钮样式 - 插入到文件列表标题栏 */
.full-screen-wrapper :deep(.monaco-tree-editor-list-title) {
position: relative;
user-select: none;
padding-right: 80px;
}
@@ -49,7 +67,6 @@
white-space: nowrap;
}
.full-screen-wrapper :deep(.upload-error-message) {
position: fixed;
top: 20px;
@@ -64,6 +81,32 @@
max-width: 300px;
font-size: 14px;
}
/* 命令选择菜单样式 */
.full-screen-wrapper :deep(.command-selection-menu) {
position: fixed;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
min-width: 200px;
}
.full-screen-wrapper :deep(.command-selection-menu div) {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s;
}
.full-screen-wrapper :deep(.command-selection-menu div:last-child) {
border-bottom: none;
}
.full-screen-wrapper :deep(.command-selection-menu div:hover) {
background-color: #f5f5f5;
}
</style>
<script setup lang="ts">
import { Editor as MonacoTreeEditor, useMonaco, type Files } from 'monaco-tree-editor'
@@ -85,6 +128,8 @@ declare global {
getWorker: (moduleId: any, label: string) => Worker
globalAPI?: boolean
}
copyManualText?: () => void
closeManualCopy?: () => void
}
}
@@ -194,11 +239,12 @@ const handleRename = (path: string, newPath: string, resolve: () => void, reject
}
// ================ 自定义菜单 =================
const fileMenu = ref([
{ label: '下载文件', value: 'download' },
])
const fileMenu = ref([{ label: '下载文件', value: 'download' }])
const handleContextMenuSelect = (path: string, item: { label: string | import('vue').ComputedRef<string>; value: any }) => {
const handleContextMenuSelect = (
path: string,
item: { label: string | import('vue').ComputedRef<string>; value: any }
) => {
console.log('选中菜单项:', item.value)
if (item.value === 'download') {
@@ -280,7 +326,7 @@ const handleFileUpload = (event: Event) => {
const oversizedFiles: string[] = []
const validFiles: File[] = []
Array.from(files).forEach(file => {
Array.from(files).forEach((file) => {
if (file.size > MAX_FILE_SIZE) {
oversizedFiles.push(file.name)
} else {
@@ -302,22 +348,25 @@ const handleFileUpload = (event: Event) => {
}
// 处理有效的文件
validFiles.forEach(file => {
validFiles.forEach((file) => {
const reader = new FileReader()
reader.onload = (e) => {
const content = e.target?.result as string
const filePath = `/${file.name}`
// 模拟保存文件到服务器
server.createOrSaveFile(filePath, content)
server
.createOrSaveFile(filePath, content)
.then(() => {
console.log('文件上传成功:', file.name)
// 重新加载文件列表
handleReload(() => { }, (msg) => console.error(msg))
handleReload(
() => {},
(msg) => console.error(msg)
)
})
.catch(error => {
.catch((error) => {
console.error('文件上传失败:', error)
showErrorMessage(`文件上传失败: ${file.name}`)
})
@@ -383,7 +432,7 @@ const commandManager = {
// 获取所有命令
getAllCommands() {
return Array.from(this.commands.entries())
}
},
}
// ================ URL参数提取和命令生成逻辑 ================
@@ -411,59 +460,126 @@ const extractUrlParams = () => {
}
}
// ================ 安全的剪贴板复制函数 ================
const copyToClipboard = (text: string) => {
// 如果文本是错误信息,直接复制错误信息
if (text.includes('URL解析错误') || text.includes('未找到')) {
navigator.clipboard.writeText(text).then(() => {
console.log('文本已复制到剪贴板:', text)
}).catch(err => {
console.error('复制失败:', err)
})
// 检查 clipboard API 是否可用
if (!navigator.clipboard) {
// 使用降级方案
fallbackCopyToClipboard(text)
return
}
// 否则复制命令
navigator.clipboard.writeText(text).then(() => {
console.log('命令已复制到剪贴板:', text)
}).catch(err => {
console.error('复制失败:', err)
const textArea = document.createElement('textarea')
textArea.value = text
document.body.appendChild(textArea)
navigator.clipboard
.writeText(text)
.then(() => {
console.log('命令已复制到剪贴板:', text)
})
.catch((err) => {
console.error('复制失败,使用降级方案:', err)
fallbackCopyToClipboard(text)
})
}
// 降级复制方案
const fallbackCopyToClipboard = (text: string) => {
const textArea = document.createElement('textarea')
textArea.value = text
textArea.style.position = 'fixed'
textArea.style.left = '-999999px'
textArea.style.top = '-999999px'
textArea.style.opacity = '0'
document.body.appendChild(textArea)
try {
textArea.select()
try {
document.execCommand('copy')
textArea.setSelectionRange(0, 99999) // 移动设备支持
const successful = document.execCommand('copy')
if (successful) {
console.log('命令已复制到剪贴板(降级方案):', text)
} catch (fallbackError) {
console.error('复制失败(降级方案):', fallbackError)
} else {
console.error('复制失败(降级方案)')
// 最终方案:提示用户手动复制
promptManualCopy(text)
}
} catch (fallbackError) {
console.error('复制失败(降级方案):', fallbackError)
promptManualCopy(text)
} finally {
document.body.removeChild(textArea)
})
}
}
// 最终方案:提示用户手动复制
const promptManualCopy = (text: string) => {
// 创建提示框让用户手动复制
const promptElement = document.createElement('div')
promptElement.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border: 2px solid #ccc;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 10000;
max-width: 80%;
max-height: 80%;
overflow: auto;
`
promptElement.innerHTML = `
<h3 style="margin: 0 0 15px 0; font-size: 16px;">请手动复制以下内容:</h3>
<textarea id="manual-copy-text" style="width: 100%; height: 100px; margin: 10px 0; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; resize: vertical;">${text}</textarea>
<div style="display: flex; justify-content: flex-end; gap: 10px;">
<button onclick="window.copyManualText && window.copyManualText()" style="padding: 8px 16px; background: #1890ff; color: white; border: none; border-radius: 4px; cursor: pointer;">复制</button>
<button onclick="window.closeManualCopy && window.closeManualCopy()" style="padding: 8px 16px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">关闭</button>
</div>
`
// 添加全局函数供按钮使用
window.copyManualText = () => {
const textarea = document.getElementById('manual-copy-text') as HTMLTextAreaElement
if (textarea) {
textarea.select()
try {
const successful = document.execCommand('copy')
if (successful) {
alert('复制成功!')
} else {
alert('复制失败,请手动选择文本后按 Ctrl+C 复制')
}
} catch (e) {
alert('复制失败,请手动选择文本后按 Ctrl+C 复制')
}
}
}
window.closeManualCopy = () => {
promptElement.remove()
delete window.copyManualText
delete window.closeManualCopy
}
document.body.appendChild(promptElement)
}
// ================ DOM操作逻辑 ================
let observer: MutationObserver | null = null
onMounted(() => {
// 检查剪贴板API支持情况
if (!navigator.clipboard) {
console.warn('剪贴板API不可用将使用降级方案')
}
extractUrlParams()
nextTick(() => {
setTimeout(insertUrlInfoText, 100)
setTimeout(insertUploadButton, 100)
// 添加命令选择菜单的CSS样式
const style = document.createElement('style')
style.textContent = `
.command-selection-menu div:last-child {
border-bottom: none;
}
.command-selection-menu div:hover {
background-color: #f5f5f5;
}
`
document.head.appendChild(style)
})
observer = new MutationObserver((mutations) => {
@@ -486,7 +602,7 @@ onMounted(() => {
if (container) {
observer.observe(container, {
childList: true,
subtree: true
subtree: true,
})
}
})
@@ -495,6 +611,10 @@ onUnmounted(() => {
if (observer) {
observer.disconnect()
}
// 清理全局函数
delete window.copyManualText
delete window.closeManualCopy
})
const insertUrlInfoText = () => {
@@ -563,6 +683,7 @@ const showCommandSelection = (event: MouseEvent, commands: [string, string][]) =
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s;
`
menuItem.textContent = label
menuItem.title = command
@@ -600,16 +721,23 @@ const showCommandSelection = (event: MouseEvent, commands: [string, string][]) =
// 复制命令并显示反馈
const copyCommand = (command: string, element: HTMLElement) => {
const originalText = element.textContent
const originalColor = element.style.color
copyToClipboard(command)
const originalText = element.textContent
// 显示反馈
element.textContent = '已复制,运行后请刷新文件列表'
element.style.color = '#52c41a'
element.style.fontWeight = 'bold'
setTimeout(() => {
element.textContent = originalText
element.style.color = ''
}, 1000)
if (element) {
element.textContent = originalText
element.style.color = originalColor
element.style.fontWeight = ''
}
}, 1500)
}
const insertUploadButton = () => {
@@ -634,6 +762,4 @@ const insertUploadButton = () => {
titleArea.appendChild(uploadBtn)
}
}
</script>