fix(web-admin): 恢复 Excel 查看中 Ctrl/Cmd+C 复制选区
filterOnly 默认关闭 WorksheetCopy;打开复制权限并增加选区 TSV 快捷键兜底。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
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">
|
||||
|
||||
Reference in new issue
Block a user