Feature(custom): support custom background image

This commit is contained in:
Kuingsmile
2026-01-27 14:12:00 +08:00
parent a5c07f2beb
commit d64cf2d4f3
12 changed files with 101 additions and 7 deletions

View File

@@ -37,6 +37,7 @@
- 接入主题仓库 [PicList ThemeHub](https://github.com/Kuingsmile/PicList-ThemeHub),支持自定义下载 - 接入主题仓库 [PicList ThemeHub](https://github.com/Kuingsmile/PicList-ThemeHub),支持自定义下载
- 提供 12 个内置主题(如 bilibili、二次元、极夜紫等风格 - 提供 12 个内置主题(如 bilibili、二次元、极夜紫等风格
- 支持自定义主题编辑和保存 - 支持自定义主题编辑和保存
- 支持自定义背景图片
- 重新设计了管理功能的全部页面 - 重新设计了管理功能的全部页面
- 优化相册页面卡片样式,边界更清晰,提升选择框视觉效果 - 优化相册页面卡片样式,边界更清晰,提升选择框视觉效果
- 优化多个页面在窄屏下的显示,避免内容溢出 - 优化多个页面在窄屏下的显示,避免内容溢出

View File

@@ -37,6 +37,7 @@ Use custom `javascript` scripts to extend PicList's functionality without the ne
- Integrated theme repository [PicList ThemeHub](https://github.com/Kuingsmile/PicList-ThemeHub), supporting custom downloads. - Integrated theme repository [PicList ThemeHub](https://github.com/Kuingsmile/PicList-ThemeHub), supporting custom downloads.
- Provided 12 built-in themes (such as bilibili, Anime, purple styles). - Provided 12 built-in themes (such as bilibili, Anime, purple styles).
- Supports custom theme editing and saving. - Supports custom theme editing and saving.
- Supports custom background images.
- Redesigned all pages of the management function - Redesigned all pages of the management function
- Optimized album page card styles, clearer boundaries, improved selection box visual effects. - Optimized album page card styles, clearer boundaries, improved selection box visual effects.
- Optimized the display of multiple pages under narrow screens to avoid content overflow. - Optimized the display of multiple pages under narrow screens to avoid content overflow.

View File

@@ -68,6 +68,17 @@ export default [
}, },
type: IRPCType.INVOKE, type: IRPCType.INVOKE,
}, },
{
action: IRPCActionType.COPY_CUSTOM_IMG_TO_THEMES_DIR,
handler: async (_: IIPCEvent, args: [filePath: string]) => {
const fileName = `custom-bgimg-${Date.now()}${path.extname(args[0])}`
const abFilePath = path.join(themesDir(), 'image', fileName)
fs.ensureDirSync(path.dirname(abFilePath))
fs.copyFileSync(args[0], abFilePath)
return fileName
},
type: IRPCType.INVOKE,
},
{ {
action: IRPCActionType.THEME_WRITE_THEME, action: IRPCActionType.THEME_WRITE_THEME,
handler: async (_: IIPCEvent, args: [fileName: string, content: string]) => { handler: async (_: IIPCEvent, args: [fileName: string, content: string]) => {

View File

@@ -82,6 +82,7 @@ export const IRPCActionType = {
THEME_IMPORT_THEMES: 'THEME_IMPORT_THEMES', THEME_IMPORT_THEMES: 'THEME_IMPORT_THEMES',
THEME_APPLY_THEME: 'THEME_APPLY_THEME', THEME_APPLY_THEME: 'THEME_APPLY_THEME',
THEME_GET_BOOTSTRAP: 'THEME_GET_BOOTSTRAP', THEME_GET_BOOTSTRAP: 'THEME_GET_BOOTSTRAP',
COPY_CUSTOM_IMG_TO_THEMES_DIR: 'COPY_CUSTOM_IMG_TO_THEMES_DIR',
RELOAD_APP: 'RELOAD_APP', RELOAD_APP: 'RELOAD_APP',
OPEN_URL: 'OPEN_URL', OPEN_URL: 'OPEN_URL',

View File

@@ -14,7 +14,7 @@ function setTheme(mode: string) {
document.documentElement.classList.toggle('light', m === 'light') document.documentElement.classList.toggle('light', m === 'light')
} }
function injectCSS(css: string) { function injectCSS(css: string, config: { imageUrl?: string; opacity?: string; blur?: string }) {
const id = '__piclist_theme__' const id = '__piclist_theme__'
let el = document.getElementById(id) as HTMLStyleElement | null let el = document.getElementById(id) as HTMLStyleElement | null
if (!el) { if (!el) {
@@ -22,14 +22,33 @@ function injectCSS(css: string) {
el.id = id el.id = id
;(document.head || document.documentElement).appendChild(el) ;(document.head || document.documentElement).appendChild(el)
} }
el.textContent = css const overrides = `
:root, .dark, .light, [data-theme='dark'], [data-theme='light'] {
${config.imageUrl ? `--background-image: url("${config.imageUrl}") !important;` : ''}
${config.opacity ? `--background-image-opacity: ${config.opacity} !important;` : ''}
${config.blur ? `--background-blur: ${config.blur} !important;` : ''}
--color-background-primary: transparent !important;
--color-background-secondary: transparent !important;
}
`
el.textContent = css + '\n' + overrides
} }
;(async () => { ;(async () => {
try { try {
const { mode, css } = await ipcRenderer.invoke('RPC_ACTIONS_INVOKE', 'THEME_GET_BOOTSTRAP') const { mode, css } = await ipcRenderer.invoke('RPC_ACTIONS_INVOKE', 'THEME_GET_BOOTSTRAP')
const allConfig = await ipcRenderer.invoke('RPC_ACTIONS_INVOKE', 'PICLIST_GET_CONFIG', [])
const enableCustomBgImg = allConfig?.settings?.enableCustomBgImg || false
const customBgImgPath = allConfig?.settings?.customBgImgPath || ''
const config = enableCustomBgImg
? {
imageUrl: customBgImgPath,
opacity: '0.7',
blur: '5px',
}
: {}
if (document.documentElement) setTheme(mode) if (document.documentElement) setTheme(mode)
if (css) injectCSS(css) if (css) injectCSS(css, config)
} catch (e) { } catch (e) {
console.error('[theme] bootstrap failed', e) console.error('[theme] bootstrap failed', e)
} }
@@ -117,8 +136,18 @@ try {
return webUtils.getPathForFile(file) return webUtils.getPathForFile(file)
}, },
onThemeUpdate: (callback: (css: string) => void) => { onThemeUpdate: (callback: (css: string) => void) => {
const subscription = (_: any, css: string) => { const subscription = async (_: any, css: string) => {
injectCSS(css) const allConfig = await ipcRenderer.invoke('RPC_ACTIONS_INVOKE', 'PICLIST_GET_CONFIG', [])
const enableCustomBgImg = allConfig?.settings?.enableCustomBgImg || false
const customBgImgPath = allConfig?.settings?.customBgImgPath || ''
const config = enableCustomBgImg
? {
imageUrl: customBgImgPath,
opacity: '0.7',
blur: '5px',
}
: {}
injectCSS(css, config)
callback(css) callback(css)
} }
ipcRenderer.on('THEME_UPDATE', subscription) ipcRenderer.on('THEME_UPDATE', subscription)

View File

@@ -901,6 +901,7 @@
"autoLaunchDesc": "Automatically start PicList when system boots", "autoLaunchDesc": "Automatically start PicList when system boots",
"chooseLanguage": "Choose Language", "chooseLanguage": "Choose Language",
"chooseTheme": "Choose Theme", "chooseTheme": "Choose Theme",
"customBgImgPath": "Select custom background img",
"customMiniIconPath": "Custom Mini Icon Path", "customMiniIconPath": "Custom Mini Icon Path",
"downloadingThemes": "Downloading...", "downloadingThemes": "Downloading...",
"downloadThemes": "Download Themes", "downloadThemes": "Download Themes",
@@ -909,6 +910,7 @@
"editTheme": "Edit Theme", "editTheme": "Edit Theme",
"enableAdvancedAnimation": "Enable Advanced Animation", "enableAdvancedAnimation": "Enable Advanced Animation",
"enableAdvancedAnimationDesc": "Do not enable this option on low-performance devices or when GPU acceleration is disabled", "enableAdvancedAnimationDesc": "Do not enable this option on low-performance devices or when GPU acceleration is disabled",
"enableCustomBgImg": "Enable Custom Background img",
"getThemeContentFailed": "Failed to get theme content", "getThemeContentFailed": "Failed to get theme content",
"hideDockHint": "Cannot hide both dock and tray at the same time", "hideDockHint": "Cannot hide both dock and tray at the same time",
"importThemes": "Import Themes", "importThemes": "Import Themes",

View File

@@ -901,6 +901,7 @@
"autoLaunchDesc": "登录时自动启动 PicList", "autoLaunchDesc": "登录时自动启动 PicList",
"chooseLanguage": "选择语言", "chooseLanguage": "选择语言",
"chooseTheme": "选择主题", "chooseTheme": "选择主题",
"customBgImgPath": "自定义背景图片选择",
"customMiniIconPath": "自定义迷你窗口图标路径", "customMiniIconPath": "自定义迷你窗口图标路径",
"downloadingThemes": "下载中...", "downloadingThemes": "下载中...",
"downloadThemes": "下载主题", "downloadThemes": "下载主题",
@@ -909,6 +910,7 @@
"editTheme": "编辑主题", "editTheme": "编辑主题",
"enableAdvancedAnimation": "启用高级动画效果", "enableAdvancedAnimation": "启用高级动画效果",
"enableAdvancedAnimationDesc": "不要在低性能设备上或关闭GPU加速时启用此选项", "enableAdvancedAnimationDesc": "不要在低性能设备上或关闭GPU加速时启用此选项",
"enableCustomBgImg": "启用自定义背景图片",
"getThemeContentFailed": "获取主题内容失败", "getThemeContentFailed": "获取主题内容失败",
"hideDockHint": "不支持同时隐藏 dock 和托盘", "hideDockHint": "不支持同时隐藏 dock 和托盘",
"importThemes": "导入主题", "importThemes": "导入主题",

View File

@@ -901,6 +901,7 @@
"autoLaunchDesc": "系統啟動時自動啟動 PicList", "autoLaunchDesc": "系統啟動時自動啟動 PicList",
"chooseLanguage": "選擇語言", "chooseLanguage": "選擇語言",
"chooseTheme": "選擇主題", "chooseTheme": "選擇主題",
"customBgImgPath": "選擇自定義背景圖片",
"customMiniIconPath": "自定義迷你窗口圖標路徑", "customMiniIconPath": "自定義迷你窗口圖標路徑",
"downloadingThemes": "下載中...", "downloadingThemes": "下載中...",
"downloadThemes": "下載主題", "downloadThemes": "下載主題",
@@ -909,6 +910,7 @@
"editTheme": "編輯主題", "editTheme": "編輯主題",
"enableAdvancedAnimation": "啟用高級動畫效果", "enableAdvancedAnimation": "啟用高級動畫效果",
"enableAdvancedAnimationDesc": "不要在低性能設備上或關閉GPU加速時啟用此選項", "enableAdvancedAnimationDesc": "不要在低性能設備上或關閉GPU加速時啟用此選項",
"enableCustomBgImg": "啟用自定義背景圖片",
"getThemeContentFailed": "獲取主題內容失敗", "getThemeContentFailed": "獲取主題內容失敗",
"hideDockHint": "不支持同時隱藏 dock 和托盤", "hideDockHint": "不支持同時隱藏 dock 和托盤",
"importThemes": "導入主題", "importThemes": "導入主題",

View File

@@ -128,6 +128,26 @@
</div> </div>
</template> </template>
</SettingCard> </SettingCard>
<SettingCard p1 class="flex flex-col justify-center">
<CustomSwitch
v-model="formOfSetting.enableCustomBgImg"
no-border
small
:title="t('pages.settings.system.enableCustomBgImg')"
/>
</SettingCard>
<CustomNavCard
v-if="formOfSetting.enableCustomBgImg"
:icon="ImageIcon"
noarrow
:title="t('pages.settings.system.customBgImgPath')"
>
<template #extra>
<CustomButton type="primary" :text="t('pages.settings.clickToSet')" @click="handleCustomBgImg" />
</template>
</CustomNavCard>
</SettingSection> </SettingSection>
<!-- Window Behavior Section --> <!-- Window Behavior Section -->
@@ -195,7 +215,7 @@
:title="t('pages.settings.system.customMiniIconPath')" :title="t('pages.settings.system.customMiniIconPath')"
> >
<template #extra> <template #extra>
<CustomButton type="secondary" :text="t('pages.settings.clickToSet')" @click="handleMiniIconPath" /> <CustomButton type="primary" :text="t('pages.settings.clickToSet')" @click="handleMiniIconPath" />
</template> </template>
</CustomNavCard> </CustomNavCard>
</SettingSection> </SettingSection>
@@ -1402,6 +1422,8 @@ const formOfSetting = ref<ISettingForm>({
enableSecondUploader: false, enableSecondUploader: false,
enableAdvancedAnimation: false, enableAdvancedAnimation: false,
theme: 'default.css', theme: 'default.css',
enableCustomBgImg: false,
customBgImgPath: '',
}) })
/* computed properties */ /* computed properties */
@@ -1690,6 +1712,14 @@ const addWatch = () => {
}) })
}, },
) )
watch(
() => formOfSetting.value.enableCustomBgImg,
newVal => {
saveConfig({ [configPaths.settings.enableCustomBgImg]: newVal })
window.electron.sendRPC(IRPCActionType.RELOAD_WINDOW)
},
)
} }
/* methods */ /* methods */
@@ -2129,12 +2159,22 @@ function handleMiniWindowOntop(val: ICheckBoxValueType) {
window.electron.sendRPC(IRPCActionType.MINI_WINDOW_ON_TOP, val) window.electron.sendRPC(IRPCActionType.MINI_WINDOW_ON_TOP, val)
} }
async function handleCustomBgImg() {
const result = await window.electron.triggerRPC<string[]>(IRPCActionType.MANAGE_OPEN_FILE_SELECT_DIALOG)
if (result && result[0]) {
const fileName = await window.electron.triggerRPC<string>(IRPCActionType.COPY_CUSTOM_IMG_TO_THEMES_DIR, result[0])
formOfSetting.value.customBgImgPath = `theme://./image/${fileName}`
saveConfig(configPaths.settings.customBgImgPath, formOfSetting.value.customBgImgPath)
await window.electron.triggerRPC(IRPCActionType.THEME_APPLY_THEME, currentTheme.value)
}
}
async function handleMiniIconPath() { async function handleMiniIconPath() {
const result = await window.electron.triggerRPC<string[]>(IRPCActionType.MANAGE_OPEN_FILE_SELECT_DIALOG) const result = await window.electron.triggerRPC<string[]>(IRPCActionType.MANAGE_OPEN_FILE_SELECT_DIALOG)
if (result && result[0]) { if (result && result[0]) {
formOfSetting.value.customMiniIcon = result[0] formOfSetting.value.customMiniIcon = result[0]
saveConfig(configPaths.settings.customMiniIcon, formOfSetting.value.customMiniIcon) saveConfig(configPaths.settings.customMiniIcon, formOfSetting.value.customMiniIcon)
window.electron.sendRPC(IRPCActionType.UPDATE_MINI_WINDOW_ICON, formOfSetting.value.customMiniIcon) window.electron.sendRPC(IRPCActionType.RELOAD_WINDOW)
} }
} }

View File

@@ -188,6 +188,8 @@ export const configPaths = {
systemTheme: 'settings.systemTheme', systemTheme: 'settings.systemTheme',
enableAdvancedAnimation: 'settings.enableAdvancedAnimation', enableAdvancedAnimation: 'settings.enableAdvancedAnimation',
isDisableGPU: 'settings.isDisableGPU', isDisableGPU: 'settings.isDisableGPU',
enableCustomBgImg: 'settings.enableCustomBgImg',
customBgImgPath: 'settings.customBgImgPath',
}, },
needReload: 'needReload', needReload: 'needReload',
picgoPlugins: 'picgoPlugins', picgoPlugins: 'picgoPlugins',

View File

@@ -29,6 +29,7 @@ export const IRPCActionType = {
THEME_FETCH_THEMES: 'THEME_FETCH_THEMES', THEME_FETCH_THEMES: 'THEME_FETCH_THEMES',
THEME_IMPORT_THEMES: 'THEME_IMPORT_THEMES', THEME_IMPORT_THEMES: 'THEME_IMPORT_THEMES',
THEME_APPLY_THEME: 'THEME_APPLY_THEME', THEME_APPLY_THEME: 'THEME_APPLY_THEME',
COPY_CUSTOM_IMG_TO_THEMES_DIR: 'COPY_CUSTOM_IMG_TO_THEMES_DIR',
OPEN_WINDOW: 'OPEN_WINDOW', OPEN_WINDOW: 'OPEN_WINDOW',
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW', OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
CLOSE_WINDOW: 'CLOSE_WINDOW', CLOSE_WINDOW: 'CLOSE_WINDOW',

View File

@@ -42,6 +42,8 @@ interface ISettingForm {
enableSecondUploader: boolean enableSecondUploader: boolean
enableAdvancedAnimation: boolean enableAdvancedAnimation: boolean
theme: string theme: string
enableCustomBgImg: boolean
customBgImgPath: string
} }
interface IToolboxItem { interface IToolboxItem {