同步设置

This commit is contained in:
tianmo committed 2026-10-17 19:01:13 +08:00
1 parent 832261739f
commit 92aaf6fbb8
28 files changed
+705 -227

No files matched your search

+25 -13
View File
@@ -4,21 +4,22 @@ import type {
} from '~/types/image'
import { isUnauthorizedError } from './useAuth'
import { prepareFilesForUpload } from './useClipboardImage'
import { normalizeUploadTagNames } from './useUploadPreferences'
const MAX_FILES = 10
export function useImageUpload() {
const toast = useToast()
const { t } = useI18n()
const { compressEnabled, clientWebpQuality, lastTagIds } = useUploadPreferences()
const { items: tagItems } = useTags()
const { compressEnabled, clientWebpQuality, uploadTagNames } = useUploadPreferences()
const { load: loadUploadPolicy } = useUploadPolicy()
const uploading = ref(false)
const progressItems = ref<UploadProgressItem[]>([])
const lastUploadResult = ref<UploadResponse | null>(null)
async function uploadFiles(
files: File[],
options?: { notify?: boolean, tagIds?: number[] }
options?: { notify?: boolean }
) {
if (uploading.value) return
@@ -43,10 +44,27 @@ export function useImageUpload() {
}))
try {
const prepared = await prepareFilesForUpload(files, {
const preparedFiles = await prepareFilesForUpload(files, {
enabled: compressEnabled.value,
quality: clientWebpQuality.value / 100
})
const currentPolicy = await loadUploadPolicy().catch(() => null)
const maxFileSizeMb = currentPolicy?.imageMaxFileSizeMb ?? 10
const prepared = preparedFiles.filter((file) => {
if (file.size <= maxFileSizeMb * 1024 * 1024) return true
toast.add({
title: t('files.fileTooLarge', {
name: file.name,
limit: maxFileSizeMb
}),
color: 'error'
})
return false
})
if (!prepared.length) {
progressItems.value = []
return
}
progressItems.value = prepared.map((file, index) => ({
name: file.name,
@@ -59,15 +77,9 @@ export function useImageUpload() {
formData.append('files', file, file.name)
}
const tagIds = options?.tagIds ?? lastTagIds.value
if (tagIds.length) {
const names = tagIds
.map(id => tagItems.value.find(tag => tag.id === id)?.name)
.filter((name): name is string => Boolean(name))
if (names.length) {
formData.append('tagNames', JSON.stringify(names))
}
lastTagIds.value = tagIds
const tagNames = normalizeUploadTagNames(uploadTagNames.value).join(',')
if (tagNames) {
formData.append('tagNames', tagNames)
}
const response = await $fetch<UploadResponse>('/api/images/upload', {
+18 -10
View File
@@ -2,6 +2,7 @@ import type { UploadProgressItem } from '~/types/image'
import type { FileItem, FileListResponse, FileUploadResponse } from '~/types/file'
import type { CopyFormat, UploadMediaKind } from './useUploadPreferences'
import { isUnauthorizedError } from './useAuth'
import { normalizeUploadTagNames } from './useUploadPreferences'
const MAX_FILES = 10
@@ -54,10 +55,9 @@ export function useMediaUpload(kind: UploadMediaKind) {
autoCopyMarkdown,
copyFormat,
transcodeToHls,
lastTagIds,
uploadTagNames,
loadPreferences
} = useUploadPreferences(kind)
const { items: tagItems, fetchTags } = useTags()
const uploading = ref(false)
const progressItems = ref<UploadProgressItem[]>([])
@@ -209,6 +209,19 @@ export function useMediaUpload(kind: UploadMediaKind) {
})
return false
}
const maxFileSizeMb = kind === 'video'
? (currentPolicy?.videoMaxFileSizeMb ?? 500)
: (currentPolicy?.fileMaxFileSizeMb ?? 100)
if (file.size > maxFileSizeMb * 1024 * 1024) {
toast.add({
title: t('files.fileTooLarge', {
name: file.name,
limit: maxFileSizeMb
}),
color: 'error'
})
return false
}
return true
})
if (!acceptedFiles.length) return
@@ -234,14 +247,9 @@ export function useMediaUpload(kind: UploadMediaKind) {
if (kind === 'video' && transcodeToHls.value) {
formData.append('transcode', '1')
}
if (lastTagIds.value.length) {
await fetchTags()
const names = lastTagIds.value
.map(id => tagItems.value.find(tag => tag.id === id)?.name)
.filter((name): name is string => Boolean(name))
if (names.length) {
formData.append('tagNames', JSON.stringify(names))
}
const tagNames = normalizeUploadTagNames(uploadTagNames.value).join(',')
if (tagNames) {
formData.append('tagNames', tagNames)
}
progressItems.value = progressItems.value.map(item => ({
+7 -1
View File
@@ -2,6 +2,9 @@ interface UploadPolicy {
allowedMimeTypes: string[]
allowedVideoExtensions: string[]
forbiddenFileExtensions: string[]
imageMaxFileSizeMb: number
videoMaxFileSizeMb: number
fileMaxFileSizeMb: number
}
const policy = ref<UploadPolicy | null>(null)
@@ -21,7 +24,10 @@ export function useUploadPolicy() {
policy.value = {
allowedMimeTypes: [...data.allowedMimeTypes],
allowedVideoExtensions: [...data.allowedVideoExtensions],
forbiddenFileExtensions: [...data.forbiddenFileExtensions]
forbiddenFileExtensions: [...data.forbiddenFileExtensions],
imageMaxFileSizeMb: data.imageMaxFileSizeMb,
videoMaxFileSizeMb: data.videoMaxFileSizeMb,
fileMaxFileSizeMb: data.fileMaxFileSizeMb
}
return policy.value
})
+27 -10
View File
@@ -13,7 +13,7 @@ export interface UploadPreferences {
autoCopyMarkdown: boolean
copyFormat: CopyFormat
clientWebpQuality: number
lastTagIds: number[]
uploadTagNames: string[]
/** 视频床专属:上传后转码为 HLS(m3u8 + 分片) */
transcodeToHls: boolean
}
@@ -23,10 +23,21 @@ const DEFAULTS: UploadPreferences = {
autoCopyMarkdown: false,
copyFormat: 'markdown',
clientWebpQuality: 80,
lastTagIds: [],
uploadTagNames: [],
transcodeToHls: false
}
export function normalizeUploadTagNames(value: string | readonly string[]): string[] {
const parts = typeof value === 'string'
? value.split(/[,,\n]/)
: value.flatMap(item => String(item).split(/[,,\n]/))
return [...new Set(
parts
.map(name => name.trim())
.filter(name => Boolean(name) && name.length <= 32)
)]
}
function clampQuality(value: unknown): number {
const num = Number(value)
if (!Number.isFinite(num)) return DEFAULTS.clientWebpQuality
@@ -49,9 +60,8 @@ function readStored(kind: UploadMediaKind): UploadPreferences {
autoCopyMarkdown: parsed.autoCopyMarkdown === true,
copyFormat: isCopyFormat(parsed.copyFormat) ? parsed.copyFormat : DEFAULTS.copyFormat,
clientWebpQuality: clampQuality(parsed.clientWebpQuality),
lastTagIds: Array.isArray(parsed.lastTagIds)
? parsed.lastTagIds.filter(id => Number.isInteger(id) && id > 0)
: DEFAULTS.lastTagIds,
/** 标签输入只在当前页面会话内生效,刷新后默认为空。 */
uploadTagNames: [...DEFAULTS.uploadTagNames],
transcodeToHls: parsed.transcodeToHls === true
}
} catch {
@@ -69,17 +79,23 @@ export function useUploadPreferences(kind: UploadMediaKind = 'image') {
const autoCopyMarkdown = useState(`upload-pref-auto-md-${kind}`, () => DEFAULTS.autoCopyMarkdown)
const copyFormat = useState<CopyFormat>(`upload-pref-copy-format-${kind}`, () => DEFAULTS.copyFormat)
const clientWebpQuality = useState(`upload-pref-webp-quality-${kind}`, () => DEFAULTS.clientWebpQuality)
const lastTagIds = useState<number[]>(`upload-pref-last-tag-ids-${kind}`, () => DEFAULTS.lastTagIds)
const uploadTagNames = useState<string[]>(
`upload-pref-tag-names-${kind}`,
() => [...DEFAULTS.uploadTagNames]
)
const transcodeToHls = useState(`upload-pref-transcode-${kind}`, () => DEFAULTS.transcodeToHls)
const loaded = useState(`upload-pref-loaded-${kind}`, () => false)
function loadPreferences() {
const firstLoad = !loaded.value
const stored = readStored(kind)
compressEnabled.value = stored.compressEnabled
autoCopyMarkdown.value = stored.autoCopyMarkdown
copyFormat.value = stored.copyFormat
clientWebpQuality.value = stored.clientWebpQuality
lastTagIds.value = stored.lastTagIds
if (firstLoad) {
uploadTagNames.value = [...stored.uploadTagNames]
}
transcodeToHls.value = stored.transcodeToHls
loaded.value = true
}
@@ -94,7 +110,7 @@ export function useUploadPreferences(kind: UploadMediaKind = 'image') {
autoCopyMarkdown: autoCopyMarkdown.value,
copyFormat: copyFormat.value,
clientWebpQuality: clampQuality(clientWebpQuality.value),
lastTagIds: lastTagIds.value,
uploadTagNames: [...DEFAULTS.uploadTagNames],
transcodeToHls: transcodeToHls.value
})
}
@@ -104,12 +120,13 @@ export function useUploadPreferences(kind: UploadMediaKind = 'image') {
autoCopyMarkdown.value = DEFAULTS.autoCopyMarkdown
copyFormat.value = DEFAULTS.copyFormat
clientWebpQuality.value = DEFAULTS.clientWebpQuality
uploadTagNames.value = [...DEFAULTS.uploadTagNames]
transcodeToHls.value = DEFAULTS.transcodeToHls
savePreferences()
}
watch(
[compressEnabled, autoCopyMarkdown, copyFormat, clientWebpQuality, lastTagIds, transcodeToHls],
[compressEnabled, autoCopyMarkdown, copyFormat, clientWebpQuality, transcodeToHls],
() => {
if (loaded.value) {
savePreferences()
@@ -123,7 +140,7 @@ export function useUploadPreferences(kind: UploadMediaKind = 'image') {
autoCopyMarkdown,
copyFormat,
clientWebpQuality,
lastTagIds,
uploadTagNames,
transcodeToHls,
loaded,
loadPreferences,