mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-06-06 00:02:03 +08:00
⚡ Perf(custom): reduce idle memory usage by 60 percent
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<div id="layout" :key="pageReloadCount" class="h-full select-none">
|
||||
<router-view />
|
||||
<div
|
||||
class="pointer-events-none absolute inset-0 -z-1 bg-custom bg-cover bg-fixed bg-center bg-no-repeat opacity-custom blur-custom"
|
||||
/>
|
||||
<UIServiceProvider />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -21,3 +21,11 @@
|
||||
@apply outline-2 outline-offset-2 outline-accent outline-solid;
|
||||
}
|
||||
}
|
||||
|
||||
@utility drag-region {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
@utility no-drag-region {
|
||||
-webkit-app-region: none;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { Monitor, Moon, Sun } from 'lucide-vue-next'
|
||||
import { computed, onBeforeMount, onBeforeUnmount, watch } from 'vue'
|
||||
import { computed, onBeforeMount, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { configPaths } from '@/utils/configPaths'
|
||||
import { getConfig, saveConfig } from '@/utils/dataSender'
|
||||
|
||||
interface Props {
|
||||
collapsed?: boolean
|
||||
}
|
||||
@@ -11,22 +13,12 @@ interface Props {
|
||||
defineProps<Props>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const currentTheme = useStorage<'light' | 'dark' | 'system'>('systemTheme', 'system')
|
||||
const currentTheme = ref<'light' | 'dark' | 'system'>('light')
|
||||
|
||||
watch(
|
||||
currentTheme,
|
||||
async newTheme => {
|
||||
document.documentElement.setAttribute('data-theme', newTheme)
|
||||
document.documentElement.classList.remove('light', 'dark', 'system')
|
||||
if (newTheme === 'system') {
|
||||
const systemTheme = (await window.electron.triggerRPC<'light' | 'dark'>('GET_SYSTEM_THEME')) || 'light'
|
||||
document.documentElement.classList.add(systemTheme)
|
||||
} else {
|
||||
document.documentElement.classList.add(newTheme)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
async function initializeTheme() {
|
||||
const savedTheme = (await getConfig<'light' | 'dark' | 'system'>(configPaths.settings.systemTheme)) || 'system'
|
||||
currentTheme.value = savedTheme
|
||||
}
|
||||
|
||||
const themeOptions = computed(() => [
|
||||
{
|
||||
@@ -53,83 +45,38 @@ const currentThemeOption = computed(
|
||||
() => themeOptions.value.find(option => option.value === currentTheme.value) || themeOptions.value[0],
|
||||
)
|
||||
|
||||
function toggleTheme() {
|
||||
async function toggleTheme() {
|
||||
const themes = ['light', 'dark', 'system'] as const
|
||||
const currentIndex = themes.indexOf(currentTheme.value)
|
||||
const nextTheme = themes[(currentIndex + 1) % themes.length]
|
||||
currentTheme.value = nextTheme
|
||||
}
|
||||
|
||||
let listenThemeChange: () => void = () => {}
|
||||
const themUpdateHandler = (value: 'light' | 'dark') => {
|
||||
const savedTheme = localStorage.getItem('systemTheme') || 'system'
|
||||
if (savedTheme === 'system') {
|
||||
currentTheme.value = value
|
||||
document.documentElement.classList.remove('light', 'dark', 'system')
|
||||
if (nextTheme === 'system') {
|
||||
const systemTheme = (await window.electron.triggerRPC<'light' | 'dark'>('GET_SYSTEM_THEME')) || 'light'
|
||||
document.documentElement.classList.add(systemTheme)
|
||||
document.documentElement.setAttribute('data-theme', systemTheme)
|
||||
} else {
|
||||
document.documentElement.classList.add(nextTheme)
|
||||
document.documentElement.setAttribute('data-theme', nextTheme)
|
||||
}
|
||||
saveConfig({ [configPaths.settings.systemTheme]: nextTheme })
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
listenThemeChange = window.electron.ipcRendererOn('theme-update', themUpdateHandler)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
listenThemeChange()
|
||||
onBeforeMount(() => {
|
||||
initializeTheme()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="theme-switcher">
|
||||
<button class="theme-toggle-btn" :class="{ collapsed }" :title="t('settings.theme.toggle')" @click="toggleTheme">
|
||||
<div class="relative flex items-center">
|
||||
<button
|
||||
class="flex cursor-pointer items-center gap-2 rounded-md border border-border-secondary bg-bg-secondary px-3 py-2 text-sm text-secondary transition-all duration-fast ease-standard hover:text-main max-md:justify-center max-md:gap-0 max-md:p-2 [.collapsed]:justify-center [.collapsed]:gap-0 [.collapsed]:p-2"
|
||||
:class="{ collapsed }"
|
||||
:title="t('settings.theme.toggle')"
|
||||
@click="toggleTheme"
|
||||
>
|
||||
<component :is="currentThemeOption.icon" :size="18" />
|
||||
<span v-if="!collapsed" class="theme-label">{{ currentThemeOption.label }}</span>
|
||||
<span v-if="!collapsed" class="font-medium max-md:hidden">{{ currentThemeOption.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.theme-switcher {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.theme-toggle-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-background-secondary);
|
||||
transition: all 0.2s ease;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.theme-toggle-btn.collapsed {
|
||||
justify-content: center;
|
||||
padding: 0.5rem;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.theme-toggle-btn:hover {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.theme-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Mobile responsive */
|
||||
@media (width <= 768px) {
|
||||
.theme-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.theme-toggle-btn {
|
||||
justify-content: center;
|
||||
padding: 0.5rem;
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,35 +1,44 @@
|
||||
<template>
|
||||
<div class="title-bar" data-drag-region>
|
||||
<div class="title-bar-content">
|
||||
<div v-if="osGlobal !== 'darwin'" class="title-left">
|
||||
<div class="app-icon">
|
||||
<img :src="defaultLogo" width="18" height="18" />
|
||||
<div
|
||||
class="fixed top-0 right-0 left-0 z-1000 h-[32px] border-b border-b-border bg-bg-secondary drag-region"
|
||||
data-drag-region
|
||||
>
|
||||
<div class="flex h-full items-center justify-between px-4 py-0">
|
||||
<div v-if="osGlobal !== 'darwin'" class="flex items-center gap-2 no-drag-region">
|
||||
<div class="flex items-center text-accent">
|
||||
<img :src="defaultLogo" width="18" height="18" class="pointer-events-none select-none no-drag-region" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="title-center">
|
||||
<!-- Progress bar in title bar -->
|
||||
<div v-if="isShowprogress" class="progress-container">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: `${progress}%` }" />
|
||||
<div class="flex flex-1 items-center justify-center no-drag-region">
|
||||
<div v-if="isShowprogress" class="flex w-full max-w-[600px] min-w-[100px] items-center gap-2">
|
||||
<div class="h-[14px] w-full max-w-[600px] min-w-[100px] flex-1 overflow-hidden rounded-[2px] bg-border">
|
||||
<div
|
||||
class="h-full rounded-[2px] bg-success transition-all duration-300 ease-in-out"
|
||||
:style="{ width: `${progress}%` }"
|
||||
/>
|
||||
</div>
|
||||
<span class="progress-text">{{ progress }}%</span>
|
||||
<span class="min-w-[35px] text-[11px] text-secondary">{{ progress }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="title-right">
|
||||
<div class="window-controls">
|
||||
<button class="control-button pin-button" :title="$t('titleBar.alwaysOnTop')" @click="setAlwaysOnTop">
|
||||
<PinIcon :size="14" class="pin-icon" :class="{ active: isAlwaysOnTop }" />
|
||||
<div class="flex items-center no-drag-region">
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<button class="control-button" :title="$t('titleBar.alwaysOnTop')" @click="setAlwaysOnTop">
|
||||
<PinIcon
|
||||
:size="14"
|
||||
class="text-[#6b7280] [.active]:rotate-90 [.active]:text-[#ce6769]"
|
||||
:class="{ active: isAlwaysOnTop }"
|
||||
/>
|
||||
</button>
|
||||
<template v-if="osGlobal !== 'darwin'">
|
||||
<button class="control-button minimize-button" :title="$t('titleBar.minimize')" @click="minimizeWindow">
|
||||
<button class="control-button minimize" :title="$t('titleBar.minimize')" @click="minimizeWindow">
|
||||
<MinusIcon :size="14" />
|
||||
</button>
|
||||
<button class="control-button mini-button" :title="$t('titleBar.miniWindow')" @click="openMiniWindow">
|
||||
<button class="control-button mini" :title="$t('titleBar.miniWindow')" @click="openMiniWindow">
|
||||
<ShrinkIcon :size="14" />
|
||||
</button>
|
||||
<button class="control-button close-button" :title="$t('titleBar.close')" @click="closeWindow">
|
||||
<button class="control-button close" :title="$t('titleBar.close')" @click="closeWindow">
|
||||
<XIcon :size="14" />
|
||||
</button>
|
||||
</template>
|
||||
@@ -75,138 +84,11 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.title-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
height: 32px;
|
||||
background: var(--color-background-secondary);
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.title-bar-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.title-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.app-icon img {
|
||||
-webkit-user-drag: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.title-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-width: 100px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
width: 100%;
|
||||
min-width: 100px;
|
||||
max-width: 600px;
|
||||
height: 14px;
|
||||
background: var(--color-border);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
border-radius: 2px;
|
||||
height: 100%;
|
||||
background: var(--color-success);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
min-width: 35px;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.title-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
@import 'tailwindcss' reference;
|
||||
@import '../../assets/css/theme.css' reference;
|
||||
@import '../../assets/css/utilities.css' reference;
|
||||
|
||||
.control-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
width: 28px;
|
||||
height: 20px;
|
||||
color: var(--color-text-secondary);
|
||||
background: transparent;
|
||||
transition: var(--transition);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.control-button:hover {
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-surface-elevated);
|
||||
}
|
||||
|
||||
.pin-icon {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.pin-icon.active {
|
||||
rotate: 90deg;
|
||||
color: #ce6769;
|
||||
}
|
||||
|
||||
.minimize-button:hover {
|
||||
color: white;
|
||||
background: color-mix(in srgb, var(--color-warning), transparent 15%);
|
||||
}
|
||||
|
||||
.mini-button:hover {
|
||||
color: white;
|
||||
background: color-mix(in srgb, var(--color-success), transparent 15%);
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
color: white;
|
||||
background: var(--color-danger);
|
||||
@apply flex h-[20px] w-[28px] cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent text-secondary transition-all duration-fast ease-standard hover:bg-surface-elevated hover:text-main [.close:hover]:bg-danger [.close:hover]:text-white [.mini:hover]:bg-success/85 [.mini:hover]:text-white [.minimize:hover]:bg-accent/85 [.minimize:hover]:text-white;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -53,9 +53,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
:root,
|
||||
.light,
|
||||
[data-theme='light'] {
|
||||
:root {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
@@ -63,7 +61,11 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizelegibility;
|
||||
}
|
||||
|
||||
:root,
|
||||
.light,
|
||||
[data-theme='light'] {
|
||||
--background-image: none;
|
||||
--background-image-opacity: 1;
|
||||
--color-text-primary: #1d1d1f;
|
||||
@@ -90,7 +92,9 @@
|
||||
--shadow-xl: 0 20px 25px rgb(0 0 0 / 10%), 0 10px 10px rgb(0 0 0 / 4%);
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
:root.dark,
|
||||
.dark,
|
||||
[data-theme='dark'] {
|
||||
--color-text-primary: #f5f5f7;
|
||||
--color-text-secondary: #a1a1a6;
|
||||
--color-text-tertiary: #86868b;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div id="main" class="relative flex h-screen overflow-hidden bg-bg pt-[32px]">
|
||||
<InputBoxDialog />
|
||||
<div
|
||||
class="pointer-events-none absolute inset-0 -z-1 bg-custom bg-cover bg-fixed bg-center bg-no-repeat opacity-custom blur-custom"
|
||||
/>
|
||||
<TitleBar />
|
||||
<Navigation />
|
||||
<main class="relative z-1 no-scrollbar h-screen flex-1 overflow-scroll bg-bg-secondary">
|
||||
|
||||
@@ -22,9 +22,6 @@ import db from '@/utils/db'
|
||||
type MessageSchema = typeof zhCN
|
||||
|
||||
window.electron.setVisualZoomLevelLimits(1, 1)
|
||||
const savedTheme = localStorage.getItem('systemTheme') || 'system'
|
||||
document.documentElement.setAttribute('data-theme', savedTheme)
|
||||
document.documentElement.classList.add(savedTheme)
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@
|
||||
<span class="text-sm font-medium text-secondary">{{ t('pages.upload.taskUpload') }}</span>
|
||||
<span
|
||||
v-if="taskQueueStatus.tasks.length > 0"
|
||||
class="absolute top-[50%] right-3 flex min-w-6 -translate-y-[50%] animate-[badge-pulse_2s_ease-in-out_infinite] items-center justify-center rounded-full px-1.5 py-0 text-sm font-bold text-accent"
|
||||
class="absolute top-[50%] right-3 flex min-w-6 animate-[badge-pulse_2s_ease-in-out_infinite] items-center justify-center rounded-full px-1.5 py-0 text-sm font-bold text-accent"
|
||||
>
|
||||
{{ taskQueueStatus.tasks.length }}
|
||||
</span>
|
||||
|
||||
@@ -182,6 +182,7 @@ export const configPaths = {
|
||||
enableSecondUploader: 'settings.enableSecondUploader',
|
||||
secondPicBedMode: 'settings.secondPicBedMode',
|
||||
theme: 'settings.theme',
|
||||
systemTheme: 'settings.systemTheme',
|
||||
enableAdvancedAnimation: 'settings.enableAdvancedAnimation',
|
||||
isDisableGPU: 'settings.isDisableGPU',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user