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);
}
.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 {
font-size: 13px;
color: var(--text-secondary);
@@ -354,6 +377,11 @@
margin-bottom: 0;
}
.media-default-grid {
max-width: 360px;
margin-bottom: 0;
}
.concurrency-inline-options {
max-width: 360px;
margin-bottom: 0;
@@ -391,6 +419,7 @@
.select-field,
.settings-time-range-field,
.log-toggle-line,
.media-default-grid,
.concurrency-inline-options,
.format-grid {
max-width: none;

View File

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

View File

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

View File

@@ -78,6 +78,20 @@ export interface WxidConfig {
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> {
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)
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
}
// 设置导出默认媒体设置
export async function setExportDefaultMedia(enabled: boolean): Promise<void> {
await config.set(CONFIG_KEYS.EXPORT_DEFAULT_MEDIA, enabled)
export async function setExportDefaultMedia(media: ExportDefaultMediaConfig): Promise<void> {
await config.set(CONFIG_KEYS.EXPORT_DEFAULT_MEDIA, {
images: media.images,
videos: media.videos,
voices: media.voices,
emojis: media.emojis
})
}
// 获取导出默认语音转文字