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