feat(字幕样式): 添加字幕样式保存与加载功能

实现字幕样式的本地存储功能,包括保存当前设置、加载已保存设置和应用默认设置。新增样式提示对话框让用户选择是否应用已保存样式,并完善透明背景色的处理逻辑。
This commit is contained in:
2026-05-06 17:39:00 +08:00
parent f8dc097f24
commit 62c5fed2e9

View File

@@ -601,6 +601,125 @@ function hexToRgba(hex, alpha) {
return `rgba(${r}, ${g}, ${b}, ${alpha})`; return `rgba(${r}, ${g}, ${b}, ${alpha})`;
} }
const CAPTION_STYLE_KEY = 'captionStyleSettings';
function saveCaptionStyleToStorage() {
const styleData = {
containerBgColor: elements.containerBgColor.value,
containerBgOpacity: elements.containerBgOpacity.value,
containerBorderColor: elements.containerBorderColor.value,
containerBorderRadius: elements.containerBorderRadius.value,
containerPadding: elements.containerPadding.value,
tipFontSize: elements.tipFontSize.value,
tipFontWeight: elements.tipFontWeight.value,
tipColor: elements.tipColor.value,
tipLineHeight: elements.tipLineHeight.value,
tipBgColor: elements.tipBgColor.value,
tipBgOpacity: elements.tipBgOpacity.value,
tipBorderRadius: elements.tipBorderRadius.value,
tipPadding: elements.tipPadding.value,
messageFontSize: elements.messageFontSize.value,
messageFontWeight: elements.messageFontWeight.value,
messageColor: elements.messageColor.value,
messageLineHeight: elements.messageLineHeight.value,
messageBgColor: elements.messageBgColor.value,
messageBgOpacity: elements.messageBgOpacity.value,
messageBorderRadius: elements.messageBorderRadius.value,
messagePadding: elements.messagePadding.value,
hotTitleFontSize: elements.hotTitleFontSize.value,
hotTitleFontWeight: elements.hotTitleFontWeight.value,
hotTitleColor: elements.hotTitleColor.value,
hotTitleLineHeight: elements.hotTitleLineHeight.value,
hotTitleBgColor: elements.hotTitleBgColor.value,
hotTitleBgOpacity: elements.hotTitleBgOpacity.value,
hotTitleBorderRadius: elements.hotTitleBorderRadius.value,
hotTitlePadding: elements.hotTitlePadding.value,
hotIssuesFontSize: elements.hotIssuesFontSize.value,
hotIssuesFontWeight: elements.hotIssuesFontWeight.value,
hotIssuesColor: elements.hotIssuesColor.value,
hotIssuesLineHeight: elements.hotIssuesLineHeight.value,
hotIssuesBgColor: elements.hotIssuesBgColor.value,
hotIssuesBgOpacity: elements.hotIssuesBgOpacity.value,
hotIssuesBorderRadius: elements.hotIssuesBorderRadius.value,
hotIssuesPadding: elements.hotIssuesPadding.value
};
localStorage.setItem(CAPTION_STYLE_KEY, JSON.stringify(styleData));
logger.info('字幕样式已保存');
}
function loadCaptionStyleFromStorage() {
const savedData = localStorage.getItem(CAPTION_STYLE_KEY);
if (!savedData) return null;
try {
return JSON.parse(savedData);
} catch (e) {
logger.error('加载保存的字幕样式失败', e.message);
return null;
}
}
function hasSavedCaptionStyle() {
return localStorage.getItem(CAPTION_STYLE_KEY) !== null;
}
function applySavedCaptionStyle(savedData) {
if (savedData.containerBgColor) elements.containerBgColor.value = savedData.containerBgColor;
if (savedData.containerBgOpacity) elements.containerBgOpacity.value = savedData.containerBgOpacity;
if (savedData.containerBorderColor) elements.containerBorderColor.value = savedData.containerBorderColor;
if (savedData.containerBorderRadius) elements.containerBorderRadius.value = savedData.containerBorderRadius;
if (savedData.containerPadding) elements.containerPadding.value = savedData.containerPadding;
if (savedData.tipFontSize) elements.tipFontSize.value = savedData.tipFontSize;
if (savedData.tipFontWeight) elements.tipFontWeight.value = savedData.tipFontWeight;
if (savedData.tipColor) elements.tipColor.value = savedData.tipColor;
if (savedData.tipLineHeight) elements.tipLineHeight.value = savedData.tipLineHeight;
if (savedData.tipBgColor) elements.tipBgColor.value = savedData.tipBgColor;
if (savedData.tipBgOpacity) elements.tipBgOpacity.value = savedData.tipBgOpacity;
if (savedData.tipBorderRadius) elements.tipBorderRadius.value = savedData.tipBorderRadius;
if (savedData.tipPadding) elements.tipPadding.value = savedData.tipPadding;
if (savedData.messageFontSize) elements.messageFontSize.value = savedData.messageFontSize;
if (savedData.messageFontWeight) elements.messageFontWeight.value = savedData.messageFontWeight;
if (savedData.messageColor) elements.messageColor.value = savedData.messageColor;
if (savedData.messageLineHeight) elements.messageLineHeight.value = savedData.messageLineHeight;
if (savedData.messageBgColor) elements.messageBgColor.value = savedData.messageBgColor;
if (savedData.messageBgOpacity) elements.messageBgOpacity.value = savedData.messageBgOpacity;
if (savedData.messageBorderRadius) elements.messageBorderRadius.value = savedData.messageBorderRadius;
if (savedData.messagePadding) elements.messagePadding.value = savedData.messagePadding;
if (savedData.hotTitleFontSize) elements.hotTitleFontSize.value = savedData.hotTitleFontSize;
if (savedData.hotTitleFontWeight) elements.hotTitleFontWeight.value = savedData.hotTitleFontWeight;
if (savedData.hotTitleColor) elements.hotTitleColor.value = savedData.hotTitleColor;
if (savedData.hotTitleLineHeight) elements.hotTitleLineHeight.value = savedData.hotTitleLineHeight;
if (savedData.hotTitleBgColor) elements.hotTitleBgColor.value = savedData.hotTitleBgColor;
if (savedData.hotTitleBgOpacity) elements.hotTitleBgOpacity.value = savedData.hotTitleBgOpacity;
if (savedData.hotTitleBorderRadius) elements.hotTitleBorderRadius.value = savedData.hotTitleBorderRadius;
if (savedData.hotTitlePadding) elements.hotTitlePadding.value = savedData.hotTitlePadding;
if (savedData.hotIssuesFontSize) elements.hotIssuesFontSize.value = savedData.hotIssuesFontSize;
if (savedData.hotIssuesFontWeight) elements.hotIssuesFontWeight.value = savedData.hotIssuesFontWeight;
if (savedData.hotIssuesColor) elements.hotIssuesColor.value = savedData.hotIssuesColor;
if (savedData.hotIssuesLineHeight) elements.hotIssuesLineHeight.value = savedData.hotIssuesLineHeight;
if (savedData.hotIssuesBgColor) elements.hotIssuesBgColor.value = savedData.hotIssuesBgColor;
if (savedData.hotIssuesBgOpacity) elements.hotIssuesBgOpacity.value = savedData.hotIssuesBgOpacity;
if (savedData.hotIssuesBorderRadius) elements.hotIssuesBorderRadius.value = savedData.hotIssuesBorderRadius;
if (savedData.hotIssuesPadding) elements.hotIssuesPadding.value = savedData.hotIssuesPadding;
document.querySelectorAll('#captionPanel input[type="range"]').forEach(slider => {
const valueSpan = slider.nextElementSibling;
if (valueSpan && valueSpan.classList.contains('range-value')) {
valueSpan.textContent = `${slider.value}%`;
}
});
}
function showCaptionStylePrompt(callback) {
const savedData = loadCaptionStyleFromStorage();
if (!savedData) {
callback(false);
return;
}
const confirmed = confirm('检测到已保存的字幕样式设置。\n\n点击"确定"应用保存的样式,\n点击"取消"使用全新默认设置。');
callback(confirmed);
}
function loadCaptionStyleFromContainer() { function loadCaptionStyleFromContainer() {
const captionContainer = document.querySelector('.caption-container-hub'); const captionContainer = document.querySelector('.caption-container-hub');
if (!captionContainer) return; if (!captionContainer) return;
@@ -669,13 +788,18 @@ function loadCaptionStyleFromContainer() {
} }
} }
if (styles.backgroundColor && styles.backgroundColor !== 'transparent' && styles.backgroundColor !== 'rgba(0, 0, 0, 0)') { if (styles.backgroundColor) {
const rgbaMatch = styles.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/); const rgbaMatch = styles.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/);
if (rgbaMatch) { if (rgbaMatch) {
section.elements.bgColor.value = rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3])); section.elements.bgColor.value = rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3]));
if (rgbaMatch[4]) { if (rgbaMatch[4]) {
section.elements.bgOpacity.value = Math.round(parseFloat(rgbaMatch[4]) * 100); section.elements.bgOpacity.value = Math.round(parseFloat(rgbaMatch[4]) * 100);
} else {
section.elements.bgOpacity.value = 0;
} }
} else if (styles.backgroundColor === 'transparent' || styles.backgroundColor === 'rgba(0, 0, 0, 0)') {
section.elements.bgColor.value = '#000000';
section.elements.bgOpacity.value = 0;
} }
} }
@@ -831,6 +955,7 @@ function initCaptionDragAndResize(container) {
let captionStyleObserver = null; let captionStyleObserver = null;
let captionStyleApplied = false; let captionStyleApplied = false;
let captionStylePromptShown = false;
function watchCaptionContainer() { function watchCaptionContainer() {
const observer = new MutationObserver((mutations) => { const observer = new MutationObserver((mutations) => {
@@ -947,6 +1072,7 @@ function bindEvents() {
elements.getOnceCodeBtn.addEventListener('click', handleGetOnceCode); elements.getOnceCodeBtn.addEventListener('click', handleGetOnceCode);
elements.applyCaptionStyleBtn.addEventListener('click', () => { elements.applyCaptionStyleBtn.addEventListener('click', () => {
applyCaptionStyle(); applyCaptionStyle();
saveCaptionStyleToStorage();
captionStyleApplied = true; captionStyleApplied = true;
watchCaptionContentChanges(); watchCaptionContentChanges();
}); });
@@ -958,6 +1084,7 @@ function bindEvents() {
valueSpan.textContent = `${e.target.value}%`; valueSpan.textContent = `${e.target.value}%`;
} }
updateSingleStyle(e.target.id); updateSingleStyle(e.target.id);
saveCaptionStyleToStorage();
captionStyleApplied = true; captionStyleApplied = true;
watchCaptionContentChanges(); watchCaptionContentChanges();
}); });
@@ -966,11 +1093,13 @@ function bindEvents() {
document.querySelectorAll('#captionPanel input[type="number"], #captionPanel input[type="color"], #captionPanel select').forEach(input => { document.querySelectorAll('#captionPanel input[type="number"], #captionPanel input[type="color"], #captionPanel select').forEach(input => {
input.addEventListener('input', (e) => { input.addEventListener('input', (e) => {
updateSingleStyle(e.target.id); updateSingleStyle(e.target.id);
saveCaptionStyleToStorage();
captionStyleApplied = true; captionStyleApplied = true;
watchCaptionContentChanges(); watchCaptionContentChanges();
}); });
input.addEventListener('change', (e) => { input.addEventListener('change', (e) => {
updateSingleStyle(e.target.id); updateSingleStyle(e.target.id);
saveCaptionStyleToStorage();
captionStyleApplied = true; captionStyleApplied = true;
watchCaptionContentChanges(); watchCaptionContentChanges();
}); });
@@ -987,7 +1116,28 @@ function bindEvents() {
elements.toggleConfigBtn.addEventListener('click', () => togglePanel('configPanel')); elements.toggleConfigBtn.addEventListener('click', () => togglePanel('configPanel'));
elements.toggleCaptionBtn.addEventListener('click', () => { elements.toggleCaptionBtn.addEventListener('click', () => {
togglePanel('captionPanel'); togglePanel('captionPanel');
loadCaptionStyleFromContainer(); if (!captionStylePromptShown && hasSavedCaptionStyle()) {
captionStylePromptShown = true;
showCaptionStylePrompt((applySaved) => {
if (applySaved) {
const savedData = loadCaptionStyleFromStorage();
if (savedData) {
applySavedCaptionStyle(savedData);
applyCaptionStyle();
captionStyleApplied = true;
watchCaptionContentChanges();
logger.info('已应用保存的字幕样式');
}
} else {
loadCaptionStyleFromContainer();
localStorage.removeItem(CAPTION_STYLE_KEY);
captionStyleApplied = false;
logger.info('已清除保存的字幕样式,使用全新默认设置');
}
});
} else {
loadCaptionStyleFromContainer();
}
}); });
elements.toggleLogBtn.addEventListener('click', () => togglePanel('logPanel')); elements.toggleLogBtn.addEventListener('click', () => togglePanel('logPanel'));
elements.fullscreenBtn.addEventListener('click', toggleFullscreen); elements.fullscreenBtn.addEventListener('click', toggleFullscreen);