Files
FileShare/FileShare-Web-VUE/src/layouts/DefaultLayout.vue
T
admin777 6bdcb48c97 feat: Vue Router 集成 + 压缩密码编码修复
前端:
- 引入 vue-router,HTML5 History 模式,干净 URL
- ClientPage.vue 拆分为 5 个独立路由视图组件
- 提取共享状态到 composables(useLibraryRoots/useMediaPlayer/useViewMode/useModals)
- 新增 DefaultLayout 统一布局
- 导航链接改为 router-link
- FileCard 网格视图修复删除/压缩按钮 hover 不显示问题

后端:
- 压缩密码编码修复:DotNetZip/SharpZipLib 不支持 UTF-8 密码
- 替换为 SharpZipLib + 中文字符替换为 # 的 ASCII 安全密码方案
2026-07-10 15:22:09 +08:00

89 lines
2.5 KiB
Vue

<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import ClientHeader from '../components/client/ClientHeader.vue'
import QrCodeModal from '../components/QrCodeModal.vue'
import ConfirmModal from '../components/ConfirmModal.vue'
import { useLibraryRoots } from '../composables/useLibraryRoots'
import { useMediaPlayer } from '../composables/useMediaPlayer'
import { useModals } from '../composables/useModals'
const route = useRoute()
const router = useRouter()
const { activeRoots, loadRoots } = useLibraryRoots()
const { selectedFile, flushVideoProgress } = useMediaPlayer()
const { confirmModal, qrModal } = useModals()
const searchQuery = ref('')
const errorMessage = ref('')
const isSearching = computed(() => route.name === 'search')
const isBrowsingRoots = computed(() => {
const name = route.name as string
return name === 'home' || name === 'recent-added' || name === 'recent-played'
})
const activeTab = computed<'recent-added' | 'recent-played' | 'libraries'>(() => {
const name = route.name as string
if (name === 'recent-added') return 'recent-added'
if (name === 'recent-played') return 'recent-played'
return 'libraries'
})
const clientTitle = computed(() => {
if (activeTab.value === 'recent-added') return '最近添加'
if (activeTab.value === 'recent-played') return '最近播放'
if (isBrowsingRoots.value) return '文件库'
return '文件'
})
async function doSearch() {
const keyword = searchQuery.value.trim()
if (!keyword) return
flushVideoProgress()
router.push({ name: 'search', query: { q: keyword } })
}
function backToRoots() {
flushVideoProgress()
router.push('/')
}
function exitSearch() {
flushVideoProgress()
searchQuery.value = ''
router.push('/')
}
onMounted(() => {
window.addEventListener('beforeunload', flushVideoProgress)
if (activeRoots.value.length === 0) loadRoots()
})
</script>
<template>
<main class="client-shell">
<ClientHeader
v-model:search-query="searchQuery"
:title="clientTitle"
:active-tab="activeTab"
:recent-count="0"
:active-roots-count="activeRoots.length"
:is-browsing-roots="isBrowsingRoots"
:is-searching="isSearching"
:browse-data="null"
@search="doSearch"
@back="backToRoots"
@exit-search="exitSearch"
@open-qr="qrModal?.open()"
/>
<p v-if="errorMessage" class="error-banner">{{ errorMessage }}</p>
<router-view />
</main>
<QrCodeModal ref="qrModal" />
<ConfirmModal ref="confirmModal" />
</template>