补充分割低频组件与响应式渲染优化

根据全站性能审计继续降低低频组件请求、重复 DOM 与重复缓存 URL 成本。

- 二维码组件改为详情弹窗实际渲染时异步加载
- 文件管理仅挂载当前断点对应的移动列表或桌面表格
- Vite 静态与动态资源统一使用 /theme-assets/ 前缀
- 已通过架构检查、类型检查、ESLint 和生产构建
This commit is contained in:
2026-07-21 17:02:09 +08:00
parent aa3743b42d
commit 2fe835d8d2
4 changed files with 20 additions and 6 deletions
+3 -2
View File
@@ -108,10 +108,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { inject } from 'vue' import { defineAsyncComponent, inject } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { FileIcon, CalendarIcon, HardDriveIcon, DownloadIcon } from 'lucide-vue-next' import { FileIcon, CalendarIcon, HardDriveIcon, DownloadIcon } from 'lucide-vue-next'
import QRCode from 'qrcode.vue'
import type { ReceivedFileRecord } from '@/types' import type { ReceivedFileRecord } from '@/types'
import { buildDownloadUrl, buildReceivedRecordQrValue } from '@/utils/share-url' import { buildDownloadUrl, buildReceivedRecordQrValue } from '@/utils/share-url'
@@ -129,6 +129,7 @@ defineProps<Props>()
defineEmits<Emits>() defineEmits<Emits>()
const { t } = useI18n() const { t } = useI18n()
const isDarkMode = inject('isDarkMode') const isDarkMode = inject('isDarkMode')
const QRCode = defineAsyncComponent(() => import('qrcode.vue'))
const getDownloadUrl = (record: ReceivedFileRecord) => { const getDownloadUrl = (record: ReceivedFileRecord) => {
return buildDownloadUrl(record.downloadUrl) return buildDownloadUrl(record.downloadUrl)
@@ -168,7 +168,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject } from 'vue' import { computed, defineAsyncComponent, inject } from 'vue'
import { import {
ClipboardCopyIcon, ClipboardCopyIcon,
ClockIcon, ClockIcon,
@@ -177,7 +177,7 @@ import {
TerminalIcon, TerminalIcon,
XIcon XIcon
} from 'lucide-vue-next' } from 'lucide-vue-next'
import QRCode from 'qrcode.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import type { SentFileRecord } from '@/types' import type { SentFileRecord } from '@/types'
@@ -195,6 +195,7 @@ defineEmits<{
const { t } = useI18n() const { t } = useI18n()
const isDarkMode = inject('isDarkMode') const isDarkMode = inject('isDarkMode')
const QRCode = defineAsyncComponent(() => import('qrcode.vue'))
const qrValue = computed(() => (props.record ? props.getQRCodeValue(props.record) : '')) const qrValue = computed(() => (props.record ? props.getQRCodeValue(props.record) : ''))
</script> </script>
+13 -2
View File
@@ -262,6 +262,7 @@
</section> </section>
<section <section
v-if="isMobileViewport"
class="theme-2026-card mb-5 overflow-hidden rounded-[24px] shadow-md md:hidden" class="theme-2026-card mb-5 overflow-hidden rounded-[24px] shadow-md md:hidden"
> >
<div class="flex items-center justify-between gap-3 border-b px-4 py-3" :class="[isDarkMode ? 'border-gray-700' : 'border-[#d9e4e6]']"> <div class="flex items-center justify-between gap-3 border-b px-4 py-3" :class="[isDarkMode ? 'border-gray-700' : 'border-[#d9e4e6]']">
@@ -463,7 +464,7 @@
</aside> </aside>
</Transition> </Transition>
<div class="hidden md:block"> <div v-if="!isMobileViewport" class="hidden md:block">
<DataTable :title="t('fileManage.allFiles')" :headers="fileTableHeaders" table-class="min-w-[1120px]"> <DataTable :title="t('fileManage.allFiles')" :headers="fileTableHeaders" table-class="min-w-[1120px]">
<template #actions> <template #actions>
<BaseButton variant="secondary" :loading="isLoading" @click="refreshFileList"> <BaseButton variant="secondary" :loading="isLoading" @click="refreshFileList">
@@ -1663,7 +1664,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref, watch, type Component } from 'vue' import { computed, onMounted, onUnmounted, ref, watch, type Component } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import type { import type {
@@ -1810,6 +1811,11 @@ const {
const isFilterPanelOpen = ref(false) const isFilterPanelOpen = ref(false)
const showMobilePolicyActions = ref(false) const showMobilePolicyActions = ref(false)
const mobileMediaQuery = window.matchMedia('(max-width: 767px)')
const isMobileViewport = ref(mobileMediaQuery.matches)
const syncMobileViewport = (event: MediaQueryListEvent) => {
isMobileViewport.value = event.matches
}
const isLifecycleExpanded = ref(false) const isLifecycleExpanded = ref(false)
const visibleDetailTimeline = computed(() => { const visibleDetailTimeline = computed(() => {
const rows = selectedFileDetail.value?.detailTimeline || [] const rows = selectedFileDetail.value?.detailTimeline || []
@@ -2321,11 +2327,16 @@ watch(
) )
onMounted(() => { onMounted(() => {
mobileMediaQuery.addEventListener('change', syncMobileViewport)
params.value.health = getRouteHealthFilter() params.value.health = getRouteHealthFilter()
void loadViewPresets() void loadViewPresets()
void loadFiles() void loadFiles()
void loadHistoryFiles() void loadHistoryFiles()
}) })
onUnmounted(() => {
mobileMediaQuery.removeEventListener('change', syncMobileViewport)
})
</script> </script>
<style scoped> <style scoped>
+1
View File
@@ -6,6 +6,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx'
import tailwindcss from 'tailwindcss' import tailwindcss from 'tailwindcss'
import autoprefixer from 'autoprefixer' import autoprefixer from 'autoprefixer'
export default defineConfig({ export default defineConfig({
base: '/theme-assets/',
plugins: [vue(), vueJsx()], plugins: [vue(), vueJsx()],
resolve: { resolve: {
alias: { alias: {