fix(web-admin): 恢复 Excel 查看中 Ctrl/Cmd+C 复制选区

filterOnly 默认关闭 WorksheetCopy;打开复制权限并增加选区 TSV 快捷键兜底。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-11-14 23:34:37 +08:00
1 parent a327ccf69a
commit 168e909914
1 file changed
+94 -3
@@ -22,10 +22,10 @@
</template>
<script setup lang="ts">
import { computed, nextTick, ref, shallowRef } from 'vue'
import { computed, nextTick, onBeforeUnmount, ref, shallowRef } from 'vue'
import { ElMessage } from 'element-plus'
import type { ListExcelSnapshot } from '@/utils/list-excel-copy'
import { snapshotToUniverCellData } from '@/utils/list-excel-copy'
import { excelCell, snapshotToUniverCellData } from '@/utils/list-excel-copy'
defineOptions({ name: 'ListExcelViewerDialog' })
@@ -46,28 +46,109 @@
const containerRef = ref<HTMLElement | null>(null)
const booting = ref(false)
const univerRef = shallowRef<{ dispose: () => void } | null>(null)
const univerApiRef = shallowRef<UniverAPI | null>(null)
type UniverAPI = {
createWorkbook: (data: Record<string, unknown>) => unknown
getActiveWorkbook: () => any
Enum: {
WorkbookPermissionPoint: Record<string, string>
WorksheetPermissionPoint: Record<string, string>
}
}
function disposeUniver() {
unbindCopyHotkey()
try {
univerRef.value?.dispose()
} catch {
// ignore dispose race
}
univerRef.value = null
univerApiRef.value = null
}
function selectionToTsv(univerAPI: UniverAPI): string {
const workbook = univerAPI.getActiveWorkbook?.()
const range = workbook?.getActiveRange?.() || workbook?.getActiveSheet?.()?.getActiveRange?.()
if (!range) return ''
const values: unknown[][] =
range.getDisplayValues?.() || range.getValues?.() || range.getRawValues?.() || []
if (!Array.isArray(values) || !values.length) {
const single = range.getDisplayValue?.() ?? range.getValue?.()
if (single == null || single === '') return ''
return excelCell(String(single))
}
return values
.map((row) =>
(Array.isArray(row) ? row : [row])
.map((cell) => excelCell(cell == null ? '' : String(cell)))
.join('\t')
)
.join('\r\n')
}
async function copySelectionFallback(univerAPI: UniverAPI) {
const text = selectionToTsv(univerAPI)
if (!text) return false
try {
await navigator.clipboard.writeText(text)
ElMessage.success('已复制,到 Excel 中直接粘贴')
return true
} catch {
const area = document.createElement('textarea')
area.value = text
area.setAttribute('readonly', 'true')
area.style.position = 'fixed'
area.style.left = '-9999px'
document.body.appendChild(area)
area.select()
const ok = document.execCommand('copy')
area.remove()
if (ok) {
ElMessage.success('已复制,到 Excel 中直接粘贴')
return true
}
ElMessage.warning('复制失败,请允许剪贴板权限后重试')
return false
}
}
function isTextEntryTarget(target: EventTarget | null): boolean {
if (target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) return true
if (target instanceof HTMLInputElement) {
const type = (target.getAttribute('type') || 'text').toLowerCase()
return type !== 'checkbox' && type !== 'radio'
}
return target instanceof HTMLElement && target.isContentEditable
}
function onCopyHotkey(event: KeyboardEvent) {
if (!innerVisible.value || !univerApiRef.value) return
if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== 'c') return
if (isTextEntryTarget(event.target)) return
const host = containerRef.value
const target = event.target as Node | null
// 弹层内任意位置(含 Univer canvas)都接住复制
const dialog = host?.closest('.el-dialog')
if (dialog && target && !dialog.contains(target) && target !== document.body) return
event.preventDefault()
event.stopPropagation()
void copySelectionFallback(univerApiRef.value)
}
function bindCopyHotkey() {
document.addEventListener('keydown', onCopyHotkey, true)
}
function unbindCopyHotkey() {
document.removeEventListener('keydown', onCopyHotkey, true)
}
/**
* 工作表 setPoint 必须先 protect(),否则会抛
* "Cannot set … because worksheet protection does not exist"。
* 工作簿 setEditable / setMode 不依赖 protect。
* filterOnly 默认关掉 WorksheetCopy,必须再显式打开,否则 Ctrl/Cmd+C 无效。
*/
async function applyReadonlyPermissions(univerAPI: UniverAPI) {
const workbook = univerAPI.getActiveWorkbook()
@@ -102,6 +183,10 @@
if (sheetPermission.setMode) {
await sheetPermission.setMode('filterOnly')
}
const sheetCopy = univerAPI.Enum?.WorksheetPermissionPoint?.Copy
if (sheetPermission.setPoint && sheetCopy) {
await sheetPermission.setPoint(sheetCopy, true)
}
} catch (error) {
console.warn('[ListExcelViewer] worksheet filterOnly skipped', error)
}
@@ -195,6 +280,7 @@
univerRef.value = univer
const api = univerAPI as unknown as UniverAPI
univerApiRef.value = api
api.createWorkbook({
id: 'list-excel-viewer',
@@ -222,6 +308,7 @@
} catch (error) {
console.warn('[ListExcelViewer] filter setup skipped', error)
}
bindCopyHotkey()
} catch (error) {
console.error(error)
ElMessage.error('Excel 查看加载失败,请稍后重试')
@@ -239,6 +326,10 @@
function onClosed() {
disposeUniver()
}
onBeforeUnmount(() => {
disposeUniver()
})
</script>
<style scoped lang="scss">