feat(export): split default media options

This commit is contained in:
aits2026
2026-03-06 14:41:02 +08:00
parent d735ed19cb
commit 39e59a4077
4 changed files with 185 additions and 30 deletions

View File

@@ -220,6 +220,29 @@
background: var(--bg-primary); background: var(--bg-primary);
} }
.media-default-grid {
width: 100%;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px 12px;
margin-bottom: 10px;
label {
display: inline-flex;
align-items: center;
gap: 6px;
margin-bottom: 0;
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
cursor: pointer;
}
input {
margin: 0;
}
}
.log-status { .log-status {
font-size: 13px; font-size: 13px;
color: var(--text-secondary); color: var(--text-secondary);
@@ -354,6 +377,11 @@
margin-bottom: 0; margin-bottom: 0;
} }
.media-default-grid {
max-width: 360px;
margin-bottom: 0;
}
.concurrency-inline-options { .concurrency-inline-options {
max-width: 360px; max-width: 360px;
margin-bottom: 0; margin-bottom: 0;
@@ -391,6 +419,7 @@
.select-field, .select-field,
.settings-time-range-field, .settings-time-range-field,
.log-toggle-line, .log-toggle-line,
.media-default-grid,
.concurrency-inline-options, .concurrency-inline-options,
.format-grid { .format-grid {
max-width: none; max-width: none;

View File

@@ -15,7 +15,7 @@ export interface ExportDefaultsSettingsPatch {
format?: string format?: string
avatars?: boolean avatars?: boolean
dateRange?: ExportDateRangeSelection dateRange?: ExportDateRangeSelection
media?: boolean media?: configService.ExportDefaultMediaConfig
voiceAsText?: boolean voiceAsText?: boolean
excelCompactColumns?: boolean excelCompactColumns?: boolean
concurrency?: number concurrency?: number
@@ -62,7 +62,12 @@ export function ExportDefaultsSettingsForm({
const [exportDefaultFormat, setExportDefaultFormat] = useState('excel') const [exportDefaultFormat, setExportDefaultFormat] = useState('excel')
const [exportDefaultAvatars, setExportDefaultAvatars] = useState(true) const [exportDefaultAvatars, setExportDefaultAvatars] = useState(true)
const [exportDefaultDateRange, setExportDefaultDateRange] = useState<ExportDateRangeSelection>(() => createDefaultExportDateRangeSelection()) const [exportDefaultDateRange, setExportDefaultDateRange] = useState<ExportDateRangeSelection>(() => createDefaultExportDateRangeSelection())
const [exportDefaultMedia, setExportDefaultMedia] = useState(false) const [exportDefaultMedia, setExportDefaultMedia] = useState<configService.ExportDefaultMediaConfig>({
images: true,
videos: true,
voices: true,
emojis: true
})
const [exportDefaultVoiceAsText, setExportDefaultVoiceAsText] = useState(false) const [exportDefaultVoiceAsText, setExportDefaultVoiceAsText] = useState(false)
const [exportDefaultExcelCompactColumns, setExportDefaultExcelCompactColumns] = useState(true) const [exportDefaultExcelCompactColumns, setExportDefaultExcelCompactColumns] = useState(true)
const [exportDefaultConcurrency, setExportDefaultConcurrency] = useState(2) const [exportDefaultConcurrency, setExportDefaultConcurrency] = useState(2)
@@ -85,7 +90,12 @@ export function ExportDefaultsSettingsForm({
setExportDefaultFormat(savedFormat || 'excel') setExportDefaultFormat(savedFormat || 'excel')
setExportDefaultAvatars(savedAvatars ?? true) setExportDefaultAvatars(savedAvatars ?? true)
setExportDefaultDateRange(resolveExportDateRangeConfig(savedDateRange)) setExportDefaultDateRange(resolveExportDateRangeConfig(savedDateRange))
setExportDefaultMedia(savedMedia ?? false) setExportDefaultMedia(savedMedia ?? {
images: true,
videos: true,
voices: true,
emojis: true
})
setExportDefaultVoiceAsText(savedVoiceAsText ?? false) setExportDefaultVoiceAsText(savedVoiceAsText ?? false)
setExportDefaultExcelCompactColumns(savedExcelCompactColumns ?? true) setExportDefaultExcelCompactColumns(savedExcelCompactColumns ?? true)
setExportDefaultConcurrency(savedConcurrency ?? 2) setExportDefaultConcurrency(savedConcurrency ?? 2)
@@ -237,27 +247,66 @@ export function ExportDefaultsSettingsForm({
<div className="form-group"> <div className="form-group">
<div className="form-copy"> <div className="form-copy">
<label></label> <label></label>
<span className="form-hint">//</span> <span className="form-hint"></span>
</div> </div>
<div className="form-control"> <div className="form-control">
<div className="log-toggle-line"> <div className="media-default-grid">
<span className="log-status">{exportDefaultMedia ? '已开启' : '已关闭'}</span> <label>
<label className="switch" htmlFor="shared-export-default-media">
<input <input
id="shared-export-default-media"
className="switch-input"
type="checkbox" type="checkbox"
checked={exportDefaultMedia} checked={exportDefaultMedia.images}
onChange={async (e) => { onChange={async (e) => {
const enabled = e.target.checked const next = { ...exportDefaultMedia, images: e.target.checked }
setExportDefaultMedia(enabled) setExportDefaultMedia(next)
await configService.setExportDefaultMedia(enabled) await configService.setExportDefaultMedia(next)
onDefaultsChanged?.({ media: enabled }) onDefaultsChanged?.({ media: next })
notify(enabled ? '开启默认媒体导出' : '关闭默认媒体导出', true) notify(`${e.target.checked ? '开启' : '关闭'}默认导出图片`, true)
}} }}
/> />
<span className="switch-slider" />
</label>
<label>
<input
type="checkbox"
checked={exportDefaultMedia.voices}
onChange={async (e) => {
const next = { ...exportDefaultMedia, voices: e.target.checked }
setExportDefaultMedia(next)
await configService.setExportDefaultMedia(next)
onDefaultsChanged?.({ media: next })
notify(`${e.target.checked ? '开启' : '关闭'}默认导出语音`, true)
}}
/>
</label>
<label>
<input
type="checkbox"
checked={exportDefaultMedia.videos}
onChange={async (e) => {
const next = { ...exportDefaultMedia, videos: e.target.checked }
setExportDefaultMedia(next)
await configService.setExportDefaultMedia(next)
onDefaultsChanged?.({ media: next })
notify(`${e.target.checked ? '开启' : '关闭'}默认导出视频`, true)
}}
/>
</label>
<label>
<input
type="checkbox"
checked={exportDefaultMedia.emojis}
onChange={async (e) => {
const next = { ...exportDefaultMedia, emojis: e.target.checked }
setExportDefaultMedia(next)
await configService.setExportDefaultMedia(next)
onDefaultsChanged?.({ media: next })
notify(`${e.target.checked ? '开启' : '关闭'}默认导出表情包`, true)
}}
/>
</label> </label>
</div> </div>
</div> </div>

View File

@@ -1288,7 +1288,12 @@ function ExportPage() {
const [exportDefaultFormat, setExportDefaultFormat] = useState<TextExportFormat>('excel') const [exportDefaultFormat, setExportDefaultFormat] = useState<TextExportFormat>('excel')
const [exportDefaultAvatars, setExportDefaultAvatars] = useState(true) const [exportDefaultAvatars, setExportDefaultAvatars] = useState(true)
const [exportDefaultDateRangeSelection, setExportDefaultDateRangeSelection] = useState<ExportDateRangeSelection>(() => createDefaultExportDateRangeSelection()) const [exportDefaultDateRangeSelection, setExportDefaultDateRangeSelection] = useState<ExportDateRangeSelection>(() => createDefaultExportDateRangeSelection())
const [exportDefaultMedia, setExportDefaultMedia] = useState(false) const [exportDefaultMedia, setExportDefaultMedia] = useState<configService.ExportDefaultMediaConfig>({
images: true,
videos: true,
voices: true,
emojis: true
})
const [exportDefaultVoiceAsText, setExportDefaultVoiceAsText] = useState(false) const [exportDefaultVoiceAsText, setExportDefaultVoiceAsText] = useState(false)
const [exportDefaultExcelCompactColumns, setExportDefaultExcelCompactColumns] = useState(true) const [exportDefaultExcelCompactColumns, setExportDefaultExcelCompactColumns] = useState(true)
const [exportDefaultConcurrency, setExportDefaultConcurrency] = useState(2) const [exportDefaultConcurrency, setExportDefaultConcurrency] = useState(2)
@@ -1301,7 +1306,7 @@ function ExportPage() {
}, },
useAllTime: false, useAllTime: false,
exportAvatars: true, exportAvatars: true,
exportMedia: false, exportMedia: true,
exportImages: true, exportImages: true,
exportVoices: true, exportVoices: true,
exportVideos: true, exportVideos: true,
@@ -1831,7 +1836,12 @@ function ExportPage() {
setLastSnsExportPostCount(savedSnsPostCount) setLastSnsExportPostCount(savedSnsPostCount)
setExportDefaultFormat((savedFormat as TextExportFormat) || 'excel') setExportDefaultFormat((savedFormat as TextExportFormat) || 'excel')
setExportDefaultAvatars(savedAvatars ?? true) setExportDefaultAvatars(savedAvatars ?? true)
setExportDefaultMedia(savedMedia ?? false) setExportDefaultMedia(savedMedia ?? {
images: true,
videos: true,
voices: true,
emojis: true
})
setExportDefaultVoiceAsText(savedVoiceAsText ?? false) setExportDefaultVoiceAsText(savedVoiceAsText ?? false)
setExportDefaultExcelCompactColumns(savedExcelCompactColumns ?? true) setExportDefaultExcelCompactColumns(savedExcelCompactColumns ?? true)
setExportDefaultConcurrency(savedConcurrency ?? 2) setExportDefaultConcurrency(savedConcurrency ?? 2)
@@ -1854,7 +1864,16 @@ function ExportPage() {
...prev, ...prev,
format: ((savedFormat as TextExportFormat) || 'excel'), format: ((savedFormat as TextExportFormat) || 'excel'),
exportAvatars: savedAvatars ?? true, exportAvatars: savedAvatars ?? true,
exportMedia: savedMedia ?? prev.exportMedia, exportMedia: Boolean(
(savedMedia?.images ?? prev.exportImages) ||
(savedMedia?.voices ?? prev.exportVoices) ||
(savedMedia?.videos ?? prev.exportVideos) ||
(savedMedia?.emojis ?? prev.exportEmojis)
),
exportImages: savedMedia?.images ?? prev.exportImages,
exportVoices: savedMedia?.voices ?? prev.exportVoices,
exportVideos: savedMedia?.videos ?? prev.exportVideos,
exportEmojis: savedMedia?.emojis ?? prev.exportEmojis,
exportVoiceAsText: savedVoiceAsText ?? prev.exportVoiceAsText, exportVoiceAsText: savedVoiceAsText ?? prev.exportVoiceAsText,
excelCompactColumns: savedExcelCompactColumns ?? prev.excelCompactColumns, excelCompactColumns: savedExcelCompactColumns ?? prev.excelCompactColumns,
txtColumns, txtColumns,
@@ -3214,7 +3233,16 @@ function ExportPage() {
exportAvatars: exportDefaultAvatars, exportAvatars: exportDefaultAvatars,
useAllTime: exportDefaultDateRangeSelection.useAllTime, useAllTime: exportDefaultDateRangeSelection.useAllTime,
dateRange: nextDateRange, dateRange: nextDateRange,
exportMedia: exportDefaultMedia, exportMedia: Boolean(
exportDefaultMedia.images ||
exportDefaultMedia.voices ||
exportDefaultMedia.videos ||
exportDefaultMedia.emojis
),
exportImages: exportDefaultMedia.images,
exportVoices: exportDefaultMedia.voices,
exportVideos: exportDefaultMedia.videos,
exportEmojis: exportDefaultMedia.emojis,
exportVoiceAsText: exportDefaultVoiceAsText, exportVoiceAsText: exportDefaultVoiceAsText,
excelCompactColumns: exportDefaultExcelCompactColumns, excelCompactColumns: exportDefaultExcelCompactColumns,
exportConcurrency: exportDefaultConcurrency exportConcurrency: exportDefaultConcurrency
@@ -3772,7 +3800,12 @@ function ExportPage() {
await configService.setExportDefaultFormat(options.format) await configService.setExportDefaultFormat(options.format)
await configService.setExportDefaultAvatars(options.exportAvatars) await configService.setExportDefaultAvatars(options.exportAvatars)
await configService.setExportDefaultMedia(Boolean(options.exportImages || options.exportVoices || options.exportVideos || options.exportEmojis)) await configService.setExportDefaultMedia({
images: options.exportImages,
voices: options.exportVoices,
videos: options.exportVideos,
emojis: options.exportEmojis
})
await configService.setExportDefaultVoiceAsText(options.exportVoiceAsText) await configService.setExportDefaultVoiceAsText(options.exportVoiceAsText)
await configService.setExportDefaultExcelCompactColumns(options.excelCompactColumns) await configService.setExportDefaultExcelCompactColumns(options.excelCompactColumns)
await configService.setExportDefaultTxtColumns(options.txtColumns) await configService.setExportDefaultTxtColumns(options.txtColumns)
@@ -5215,8 +5248,17 @@ function ExportPage() {
if (patch.dateRange) { if (patch.dateRange) {
setExportDefaultDateRangeSelection(patch.dateRange) setExportDefaultDateRangeSelection(patch.dateRange)
} }
if (typeof patch.media === 'boolean') { if (patch.media) {
setExportDefaultMedia(patch.media) const mediaPatch = patch.media
setExportDefaultMedia(mediaPatch)
setOptions(prev => ({
...prev,
exportMedia: Boolean(mediaPatch.images || mediaPatch.voices || mediaPatch.videos || mediaPatch.emojis),
exportImages: mediaPatch.images,
exportVoices: mediaPatch.voices,
exportVideos: mediaPatch.videos,
exportEmojis: mediaPatch.emojis
}))
} }
if (typeof patch.voiceAsText === 'boolean') { if (typeof patch.voiceAsText === 'boolean') {
setExportDefaultVoiceAsText(patch.voiceAsText) setExportDefaultVoiceAsText(patch.voiceAsText)

View File

@@ -78,6 +78,20 @@ export interface WxidConfig {
updatedAt?: number updatedAt?: number
} }
export interface ExportDefaultMediaConfig {
images: boolean
videos: boolean
voices: boolean
emojis: boolean
}
const DEFAULT_EXPORT_MEDIA_CONFIG: ExportDefaultMediaConfig = {
images: true,
videos: true,
voices: true,
emojis: true
}
// 获取解密密钥 // 获取解密密钥
export async function getDecryptKey(): Promise<string | null> { export async function getDecryptKey(): Promise<string | null> {
const value = await config.get(CONFIG_KEYS.DECRYPT_KEY) const value = await config.get(CONFIG_KEYS.DECRYPT_KEY)
@@ -364,15 +378,36 @@ export async function setExportDefaultDateRange(range: ExportDefaultDateRangeCon
} }
// 获取导出默认媒体设置 // 获取导出默认媒体设置
export async function getExportDefaultMedia(): Promise<boolean | null> { export async function getExportDefaultMedia(): Promise<ExportDefaultMediaConfig | null> {
const value = await config.get(CONFIG_KEYS.EXPORT_DEFAULT_MEDIA) const value = await config.get(CONFIG_KEYS.EXPORT_DEFAULT_MEDIA)
if (typeof value === 'boolean') return value if (typeof value === 'boolean') {
return {
images: value,
videos: value,
voices: value,
emojis: value
}
}
if (value && typeof value === 'object') {
const raw = value as Partial<Record<keyof ExportDefaultMediaConfig, unknown>>
return {
images: typeof raw.images === 'boolean' ? raw.images : DEFAULT_EXPORT_MEDIA_CONFIG.images,
videos: typeof raw.videos === 'boolean' ? raw.videos : DEFAULT_EXPORT_MEDIA_CONFIG.videos,
voices: typeof raw.voices === 'boolean' ? raw.voices : DEFAULT_EXPORT_MEDIA_CONFIG.voices,
emojis: typeof raw.emojis === 'boolean' ? raw.emojis : DEFAULT_EXPORT_MEDIA_CONFIG.emojis
}
}
return null return null
} }
// 设置导出默认媒体设置 // 设置导出默认媒体设置
export async function setExportDefaultMedia(enabled: boolean): Promise<void> { export async function setExportDefaultMedia(media: ExportDefaultMediaConfig): Promise<void> {
await config.set(CONFIG_KEYS.EXPORT_DEFAULT_MEDIA, enabled) await config.set(CONFIG_KEYS.EXPORT_DEFAULT_MEDIA, {
images: media.images,
videos: media.videos,
voices: media.voices,
emojis: media.emojis
})
} }
// 获取导出默认语音转文字 // 获取导出默认语音转文字