EDMS/pages/dms/index.vue
2025-05-31 17:59:23 +08:00

452 lines
19 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onMounted, nextTick, computed } from 'vue';
import { useRouter } from 'vue-router';
// Router
const router = useRouter();
// Define page metadata
definePageMeta({
title: "Document Management System",
middleware: ["auth"],
requiresAuth: true,
breadcrumb: [
{
name: "DMS",
path: "/dms",
},
],
});
// Import DMS components dynamically to handle potential import errors
let DMSExplorer = null;
let useDmsStore = null;
// Basic state
const activeTab = ref('all');
const isLoading = ref(true);
const hasError = ref(false);
const errorMessage = ref('');
const componentsLoaded = ref(false);
// Tab definitions with icons
const tabs = [
{
id: 'all',
label: 'All Documents',
icon: 'folder',
color: 'blue'
},
{
id: 'public',
label: 'Public',
icon: 'unlock',
color: 'green'
},
{
id: 'private',
label: 'Private',
icon: 'lock',
color: 'red'
},
{
id: 'personal',
label: 'Personal',
icon: 'user',
color: 'purple'
}
];
// Change active tab
const changeTab = (tabId) => {
activeTab.value = tabId;
};
// Get SVG icon function
const getSvgIcon = (iconName, size = 20) => {
const icons = {
folder: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>`,
unlock: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path></svg>`,
lock: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>`,
user: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>`
};
return icons[iconName] || icons.folder;
};
// Load components dynamically
const loadComponents = async () => {
try {
isLoading.value = true;
hasError.value = false;
// Import components
const dmsStoreModule = await import('~/stores/dms');
const dmsExplorerModule = await import('~/components/dms/explorer/DMSExplorer.vue');
useDmsStore = dmsStoreModule.useDmsStore;
DMSExplorer = dmsExplorerModule.default;
componentsLoaded.value = true;
await nextTick();
} catch (error) {
console.error('Failed to load DMS components:', error);
hasError.value = true;
errorMessage.value = `Failed to load DMS components: ${error.message}`;
} finally {
isLoading.value = false;
}
};
// Check if user is superadmin
const isSuperAdmin = computed(() => {
if (!useDmsStore) return false;
const store = useDmsStore();
return store.currentUser?.role === 'superadmin';
});
// Check if user is admin or superadmin
const isAdmin = computed(() => {
if (!useDmsStore) return false;
const store = useDmsStore();
return ['admin', 'superadmin'].includes(store.currentUser?.role);
});
// Add Admin Dashboard link for admins
const showAdminDashboard = computed(() => {
if (!useDmsStore) return false;
const store = useDmsStore();
return ['admin', 'superadmin'].includes(store.currentUser?.role);
});
// Event handlers for DMSExplorer component interactions
const handleItemSelected = (item) => {
if (DMSExplorer && item) {
// The explorer component already handles file opening and navigation
console.log('Item selected:', item);
}
};
const handleViewModeChanged = (mode) => {
if (DMSExplorer) {
console.log('View mode changed to:', mode);
}
};
const handlePathChanged = (path) => {
if (DMSExplorer) {
console.log('Path changed to:', path);
}
};
// Handle uploaded files
const handleUploadCompleted = (files) => {
console.log('Files uploaded:', files);
// The explorer component already handles refreshing the view
};
// Handle create new item
const handleItemCreated = (item) => {
console.log('New item created:', item);
// The explorer component already handles refreshing the view
};
// Handle access request submitted
const handleAccessRequestSubmitted = (request) => {
console.log('Access request submitted:', request);
};
// Lifecycle hooks
onMounted(() => {
loadComponents();
});
</script>
<template>
<div class="dms-page">
<LayoutsBreadcrumb />
<rs-card class="h-full">
<template #body>
<div class="dms-layout h-full flex flex-col">
<!-- Loading State -->
<div v-if="isLoading" class="flex items-center justify-center h-full">
<div class="text-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
<p class="text-gray-600 dark:text-gray-400">Loading Document Management System...</p>
</div>
</div>
<!-- Error State -->
<div v-else-if="hasError" class="flex items-center justify-center h-full">
<div class="text-center p-6">
<div class="text-red-500 text-5xl mb-4"></div>
<h2 class="text-xl font-semibold text-red-600 mb-2">Error Loading DMS</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4">{{ errorMessage }}</p>
<rs-button @click="loadComponents" variant="primary">
Retry
</rs-button>
</div>
</div>
<!-- Main Content -->
<div v-else class="dms-content h-full flex flex-col">
<!-- Enhanced Access Level Tabs -->
<div class="access-tabs bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<div class="flex flex-col sm:flex-row sm:items-center justify-between px-4 sm:px-6 py-4 gap-3">
<div class="flex flex-wrap gap-1">
<button
v-for="tab in tabs"
:key="tab.id"
@click="changeTab(tab.id)"
class="access-tab flex items-center space-x-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200"
:class="[
activeTab === tab.id
? `bg-${tab.color}-100 text-${tab.color}-700 border border-${tab.color}-200 shadow-sm dark:bg-${tab.color}-900/20 dark:text-${tab.color}-300 dark:border-${tab.color}-800`
: 'text-gray-600 hover:text-gray-900 hover:bg-white dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-700 border border-transparent hover:border-gray-200 dark:hover:border-gray-600'
]"
>
<span
v-html="getSvgIcon(tab.icon)"
:class="[
activeTab === tab.id
? `text-${tab.color}-600 dark:text-${tab.color}-400`
: 'text-gray-500 dark:text-gray-500'
]"
></span>
<span>{{ tab.label }}</span>
<span
v-if="activeTab === tab.id"
:class="`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-${tab.color}-100 text-${tab.color}-800 dark:bg-${tab.color}-900/30 dark:text-${tab.color}-300`"
>
Active
</span>
</button>
</div>
<!-- Access Management Button - Moved to this row -->
<div class="flex flex-wrap gap-2">
<NuxtLink
to="/dms/access-management"
class="inline-flex items-center px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
Access Management
</NuxtLink>
<!-- Admin Dashboard Button (for admins only) -->
<NuxtLink
v-if="showAdminDashboard"
to="/dms/admin-dashboard"
class="inline-flex items-center px-3 py-1.5 border border-purple-300 dark:border-purple-600 rounded-md text-sm font-medium text-purple-700 dark:text-purple-300 bg-white dark:bg-gray-800 hover:bg-purple-50 dark:hover:bg-purple-900/10 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
Admin Dashboard
</NuxtLink>
</div>
</div>
</div>
<!-- Superadmin KPI Dashboard -->
<div v-if="isSuperAdmin" class="kpi-dashboard bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 sm:px-6 py-3 overflow-hidden">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-white dark:bg-gray-700 rounded-lg p-3 border border-gray-200 dark:border-gray-600 shadow-sm">
<div class="flex items-center">
<div class="w-8 h-8 rounded-lg bg-blue-100 dark:bg-blue-900/20 flex items-center justify-center mr-3 flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div class="overflow-hidden">
<div class="text-xl font-bold text-gray-900 dark:text-gray-100 truncate">450</div>
<div class="text-xs text-gray-600 dark:text-gray-400 truncate">Total Documents</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-700 rounded-lg p-3 border border-gray-200 dark:border-gray-600 shadow-sm">
<div class="flex items-center">
<div class="w-8 h-8 rounded-lg bg-green-100 dark:bg-green-900/20 flex items-center justify-center mr-3 flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-600 dark:text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
</div>
<div class="overflow-hidden">
<div class="text-xl font-bold text-gray-900 dark:text-gray-100 truncate">12</div>
<div class="text-xs text-gray-600 dark:text-gray-400 truncate">New Documents Today</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-700 rounded-lg p-3 border border-gray-200 dark:border-gray-600 shadow-sm">
<div class="flex items-center">
<div class="w-8 h-8 rounded-lg bg-yellow-100 dark:bg-yellow-900/20 flex items-center justify-center mr-3 flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-yellow-600 dark:text-yellow-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
</div>
<div class="overflow-hidden">
<div class="text-xl font-bold text-gray-900 dark:text-gray-100 truncate">5</div>
<div class="text-xs text-gray-600 dark:text-gray-400 truncate">Pending Access Requests</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-700 rounded-lg p-3 border border-gray-200 dark:border-gray-600 shadow-sm">
<div class="flex items-center">
<div class="w-8 h-8 rounded-lg bg-purple-100 dark:bg-purple-900/20 flex items-center justify-center mr-3 flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-purple-600 dark:text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
<div class="overflow-hidden">
<div class="text-xl font-bold text-gray-900 dark:text-gray-100 truncate">32</div>
<div class="text-xs text-gray-600 dark:text-gray-400 truncate">Active Users</div>
</div>
</div>
</div>
</div>
</div>
<!-- Content Area -->
<div class="content-area flex-1">
<!-- Action Buttons -->
<div class="px-6 py-3 bg-white dark:bg-gray-900/10 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<div class="flex space-x-2">
<h2 class="text-lg font-medium text-gray-800 dark:text-gray-200">
{{ activeTab === 'all' ? 'All Documents' :
activeTab === 'public' ? 'Public Documents' :
activeTab === 'private' ? 'Private Documents' :
'Personal Documents' }}
</h2>
</div>
<div class="flex space-x-2">
<!-- View toggles and other action buttons will be handled by the DMSExplorer component -->
</div>
</div>
<!-- DMS Explorer Component -->
<component
v-if="componentsLoaded && DMSExplorer"
:is="DMSExplorer"
:initial-path="'/'"
:view-mode="'list'"
:active-document-tab="activeTab"
@item-selected="handleItemSelected"
@view-mode-changed="handleViewModeChanged"
@path-changed="handlePathChanged"
@upload-completed="handleUploadCompleted"
@item-created="handleItemCreated"
@access-request-submitted="handleAccessRequestSubmitted"
/>
<!-- Fallback Content -->
<div v-else class="text-center py-12">
<h2 class="text-2xl font-semibold mb-4">{{ tabs.find(t => t.id === activeTab)?.label }}</h2>
<p class="text-gray-600 dark:text-gray-400">
Document explorer is loading...
</p>
</div>
</div>
</div>
</div>
</template>
</rs-card>
</div>
</template>
<style scoped>
.dms-page {
height: calc(100vh - 64px);
}
.dms-layout {
height: 100%;
}
.content-area {
min-height: 0;
overflow: hidden;
}
.dms-content {
height: 100%;
}
.access-tabs {
flex-shrink: 0;
}
.access-tab {
position: relative;
overflow: hidden;
}
.access-tab:hover {
transform: translateY(-1px);
}
.access-tab.active {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* Dynamic color classes */
.bg-blue-100 { background-color: rgb(219 234 254); }
.text-blue-700 { color: rgb(29 78 216); }
.border-blue-200 { border-color: rgb(191 219 254); }
.text-blue-600 { color: rgb(37 99 235); }
.bg-blue-100 { background-color: rgb(219 234 254); }
.text-blue-800 { color: rgb(30 64 175); }
.bg-green-100 { background-color: rgb(220 252 231); }
.text-green-700 { color: rgb(21 128 61); }
.border-green-200 { border-color: rgb(187 247 208); }
.text-green-600 { color: rgb(22 163 74); }
.text-green-800 { color: rgb(22 101 52); }
.bg-red-100 { background-color: rgb(254 226 226); }
.text-red-700 { color: rgb(185 28 28); }
.border-red-200 { border-color: rgb(254 202 202); }
.text-red-600 { color: rgb(220 38 38); }
.text-red-800 { color: rgb(153 27 27); }
.bg-purple-100 { background-color: rgb(243 232 255); }
.text-purple-700 { color: rgb(126 34 206); }
.border-purple-200 { border-color: rgb(233 213 255); }
.text-purple-600 { color: rgb(147 51 234); }
.text-purple-800 { color: rgb(107 33 168); }
/* Dark mode colors */
.dark .bg-blue-900\/20 { background-color: rgba(30, 58, 138, 0.2); }
.dark .text-blue-300 { color: rgb(147 197 253); }
.dark .border-blue-800 { border-color: rgb(30 64 175); }
.dark .text-blue-400 { color: rgb(96 165 250); }
.dark .bg-blue-900\/30 { background-color: rgba(30, 58, 138, 0.3); }
.dark .bg-green-900\/20 { background-color: rgba(20, 83, 45, 0.2); }
.dark .text-green-300 { color: rgb(134 239 172); }
.dark .border-green-800 { border-color: rgb(22 101 52); }
.dark .text-green-400 { color: rgb(74 222 128); }
.dark .bg-green-900\/30 { background-color: rgba(20, 83, 45, 0.3); }
.dark .bg-red-900\/20 { background-color: rgba(127, 29, 29, 0.2); }
.dark .text-red-300 { color: rgb(252 165 165); }
.dark .border-red-800 { border-color: rgb(153 27 27); }
.dark .text-red-400 { color: rgb(248 113 113); }
.dark .bg-red-900\/30 { background-color: rgba(127, 29, 29, 0.3); }
.dark .bg-purple-900\/20 { background-color: rgba(88, 28, 135, 0.2); }
.dark .text-purple-300 { color: rgb(196 181 253); }
.dark .border-purple-800 { border-color: rgb(107 33 168); }
.dark .text-purple-400 { color: rgb(168 85 247); }
.dark .bg-purple-900\/30 { background-color: rgba(88, 28, 135, 0.3); }
</style>