EDMS/components/dms/preview/DocumentViewer.vue
2025-06-05 14:57:08 +08:00

221 lines
6.0 KiB
Vue

<template>
<div class="document-viewer w-full h-full bg-white dark:bg-gray-900 relative">
<!-- Toolbar -->
<div v-if="mode === 'edit'" class="border-b border-gray-200 dark:border-gray-700 p-2 bg-gray-50 dark:bg-gray-800">
<div class="flex items-center space-x-2">
<!-- Formatting Tools -->
<div class="flex items-center space-x-1 border-r border-gray-300 dark:border-gray-600 pr-2">
<button
@click="execCommand('bold')"
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
title="Bold"
>
<Icon name="mdi:format-bold" class="w-4 h-4" />
</button>
<button
@click="execCommand('italic')"
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
title="Italic"
>
<Icon name="mdi:format-italic" class="w-4 h-4" />
</button>
<button
@click="execCommand('underline')"
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
title="Underline"
>
<Icon name="mdi:format-underline" class="w-4 h-4" />
</button>
</div>
<!-- Alignment -->
<div class="flex items-center space-x-1 border-r border-gray-300 dark:border-gray-600 pr-2">
<button
@click="execCommand('justifyLeft')"
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
title="Align Left"
>
<Icon name="mdi:format-align-left" class="w-4 h-4" />
</button>
<button
@click="execCommand('justifyCenter')"
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
title="Align Center"
>
<Icon name="mdi:format-align-center" class="w-4 h-4" />
</button>
<button
@click="execCommand('justifyRight')"
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
title="Align Right"
>
<Icon name="mdi:format-align-right" class="w-4 h-4" />
</button>
</div>
<!-- Lists -->
<div class="flex items-center space-x-1">
<button
@click="execCommand('insertUnorderedList')"
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
title="Bullet List"
>
<Icon name="mdi:format-list-bulleted" class="w-4 h-4" />
</button>
<button
@click="execCommand('insertOrderedList')"
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
title="Numbered List"
>
<Icon name="mdi:format-list-numbered" class="w-4 h-4" />
</button>
</div>
</div>
</div>
<!-- Document Content -->
<div class="flex-1 overflow-auto p-8">
<div class="max-w-4xl mx-auto bg-white shadow-lg min-h-full">
<!-- Document Header -->
<div class="p-8 border-b border-gray-200">
<h1 class="text-2xl font-bold text-gray-900 mb-2">{{ document.name }}</h1>
<div class="text-sm text-gray-500">
Last modified: {{ formatDate(document.lastModified) }}
</div>
</div>
<!-- Editable Content -->
<div
ref="contentEditor"
:contenteditable="mode === 'edit'"
@input="handleContentChange"
@keydown="handleKeydown"
class="p-8 min-h-96 focus:outline-none"
:class="{
'cursor-text': mode === 'edit',
'select-text': mode === 'view'
}"
v-html="content"
></div>
</div>
</div>
<!-- Save Button (Edit Mode) -->
<div v-if="mode === 'edit'" class="absolute bottom-4 right-4">
<button
@click="saveDocument"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center space-x-2"
>
<Icon name="mdi:content-save" class="w-4 h-4" />
<span>Save</span>
</button>
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted, nextTick } from 'vue';
const props = defineProps({
document: {
type: Object,
required: true
},
mode: {
type: String,
default: 'view' // 'view' | 'edit'
},
content: {
type: String,
default: ''
}
});
const emit = defineEmits([
'content-changed',
'save-requested'
]);
const contentEditor = ref(null);
const execCommand = (command, value = null) => {
document.execCommand(command, false, value);
handleContentChange();
};
const handleContentChange = () => {
if (contentEditor.value) {
emit('content-changed', contentEditor.value.innerHTML);
}
};
const handleKeydown = (event) => {
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
saveDocument();
}
};
const saveDocument = () => {
emit('save-requested');
};
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
};
// Watch for content changes
watch(() => props.content, (newContent) => {
if (contentEditor.value && contentEditor.value.innerHTML !== newContent) {
contentEditor.value.innerHTML = newContent;
}
});
onMounted(() => {
if (contentEditor.value && props.content) {
contentEditor.value.innerHTML = props.content;
}
});
</script>
<style scoped>
.document-viewer {
font-family: 'Times New Roman', serif;
}
:deep(.content-editor) {
line-height: 1.6;
}
:deep(.content-editor h1) {
font-size: 2rem;
font-weight: bold;
margin: 1rem 0;
}
:deep(.content-editor h2) {
font-size: 1.5rem;
font-weight: bold;
margin: 0.8rem 0;
}
:deep(.content-editor h3) {
font-size: 1.25rem;
font-weight: bold;
margin: 0.6rem 0;
}
:deep(.content-editor p) {
margin: 0.5rem 0;
}
:deep(.content-editor ul, .content-editor ol) {
margin: 0.5rem 0;
padding-left: 2rem;
}
:deep(.content-editor li) {
margin: 0.25rem 0;
}
</style>