release: v1.4.0 — 标签系统、操作日志扩展与文档截图更新
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s

- 每用户标签与彩色打标,图库 OR/AND 筛选与批量打标
- 操作日志记录登录、删除、设置与标签操作
- API 标签/打标接口;README 与文档站截图同步
This commit is contained in:
O96u committed 2026-09-09 14:46:52 +08:00
1 parent f17d79b236
commit 737235acff
110 files changed
+5881 -778

No files matched your search

+61 -1
View File
@@ -20,6 +20,9 @@ export function useImageList() {
const activeStorageBackend = ref('all')
const activeContentType = ref('all')
const activeUploadSource = ref('all')
const activeTagIds = ref<number[]>([])
const activeTagMode = ref<'or' | 'and'>('or')
const activeUntaggedOnly = ref(false)
const storageBackendOptions = ref<Array<{ id: string, name: string }>>([])
function storageQueryParams(): Record<string, string> {
@@ -38,6 +41,14 @@ export function useImageList() {
if (uploadSource && uploadSource !== 'all') {
params.uploadSource = uploadSource
}
if (activeUntaggedOnly.value) {
params.untagged = '1'
} else if (activeTagIds.value.length) {
params.tagIds = activeTagIds.value.join(',')
if (activeTagMode.value === 'and' && activeTagIds.value.length > 1) {
params.tagMode = 'and'
}
}
return params
}
@@ -181,12 +192,48 @@ export function useImageList() {
}
}
async function setActiveTagFilter(options: {
tagIds?: number[]
tagMode?: 'or' | 'and'
untaggedOnly?: boolean
}) {
if (options.tagIds !== undefined) {
activeTagIds.value = [...options.tagIds]
}
if (options.tagMode !== undefined) {
activeTagMode.value = options.tagMode
}
if (options.untaggedOnly !== undefined) {
activeUntaggedOnly.value = options.untaggedOnly
}
page.value = 1
if (activeSearch.value) {
await Promise.all([fetchSearch(1), fetchTotal()])
} else {
await Promise.all([fetchList(1), fetchTotal()])
}
}
async function toggleTagFilter(tagId: number) {
const next = new Set(activeTagIds.value)
activeUntaggedOnly.value = false
if (next.has(tagId)) {
next.delete(tagId)
} else {
next.add(tagId)
}
await setActiveTagFilter({ tagIds: [...next] })
}
async function resetFilters() {
searchQuery.value = ''
activeSearch.value = ''
activeStorageBackend.value = 'all'
activeContentType.value = 'all'
activeUploadSource.value = 'all'
activeTagIds.value = []
activeTagMode.value = 'or'
activeUntaggedOnly.value = false
page.value = 1
await Promise.all([fetchList(1), fetchTotal()])
}
@@ -278,6 +325,12 @@ export function useImageList() {
return t('stats.summaryTotal', { total: count })
})
function updateItemTags(key: string, tags: ImageItem['tags']) {
items.value = items.value.map(item =>
item.key === key ? { ...item, tags } : item
)
}
return {
items,
page,
@@ -288,6 +341,9 @@ export function useImageList() {
activeStorageBackend,
activeContentType,
activeUploadSource,
activeTagIds,
activeTagMode,
activeUntaggedOnly,
storageBackendOptions,
searchQuery,
activeSearch,
@@ -300,12 +356,16 @@ export function useImageList() {
setActiveStorageBackend,
setActiveContentType,
setActiveUploadSource,
setActiveTagFilter,
toggleTagFilter,
resetFilters,
loadStorageBackendOptions,
submitSearch,
goToPage,
setPageSize,
prependItems,
removeItems
removeItems,
updateItemTags,
listQueryParams
}
}