refactor: 重构设置、API、图库与存储页面前端结构。
将操作日志并入设置侧栏,补齐检查更新提示与版本接口刷新;拆分各模块组件并修复图库搜索分页变量。
This commit is contained in:
1 parent
270af4e707
commit
ac65c1eb9d
51 files changed
+5119
-2059
No files matched your search
@@ -0,0 +1,323 @@
|
||||
export type ApiDocMethod = 'GET' | 'POST' | 'DELETE'
|
||||
export type ApiDocCategory = 'image' | 'extension' | 'system'
|
||||
export type ApiParamLocation = 'form-data' | 'query' | 'header' | 'body'
|
||||
|
||||
export interface ApiDocParam {
|
||||
name: string
|
||||
type: string
|
||||
location: ApiParamLocation
|
||||
required: boolean
|
||||
descriptionKey: string
|
||||
}
|
||||
|
||||
export interface ApiDocEndpoint {
|
||||
id: string
|
||||
category: ApiDocCategory
|
||||
method: ApiDocMethod
|
||||
path: string
|
||||
titleKey: string
|
||||
descKey: string
|
||||
params: ApiDocParam[]
|
||||
requiresAuth: boolean
|
||||
curl: string
|
||||
responseExample: string
|
||||
}
|
||||
|
||||
interface ApiDocsContext {
|
||||
baseUrl: string
|
||||
authHeader: string
|
||||
}
|
||||
|
||||
function authFlag(authHeader: string) {
|
||||
return `-H 'Auth-Token: ${authHeader}'`
|
||||
}
|
||||
|
||||
export function buildApiDocs(ctx: ApiDocsContext): ApiDocEndpoint[] {
|
||||
const { baseUrl, authHeader } = ctx
|
||||
const auth = authFlag(authHeader)
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'upload',
|
||||
category: 'image',
|
||||
method: 'POST',
|
||||
path: '/api/images/upload',
|
||||
titleKey: 'api.docs.upload.title',
|
||||
descKey: 'api.docs.upload.desc',
|
||||
requiresAuth: true,
|
||||
params: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'file',
|
||||
location: 'form-data',
|
||||
required: true,
|
||||
descriptionKey: 'api.params.upload.image'
|
||||
},
|
||||
{
|
||||
name: 'file',
|
||||
type: 'file',
|
||||
location: 'form-data',
|
||||
required: false,
|
||||
descriptionKey: 'api.params.upload.file'
|
||||
},
|
||||
{
|
||||
name: 'files',
|
||||
type: 'file',
|
||||
location: 'form-data',
|
||||
required: false,
|
||||
descriptionKey: 'api.params.upload.files'
|
||||
},
|
||||
{
|
||||
name: 'token',
|
||||
type: 'string',
|
||||
location: 'form-data',
|
||||
required: false,
|
||||
descriptionKey: 'api.params.upload.token'
|
||||
}
|
||||
],
|
||||
curl: `curl -X POST "${baseUrl}/api/images/upload" \\
|
||||
${auth} \\
|
||||
-F "image=@./demo.png"`,
|
||||
responseExample: `{
|
||||
"success": true,
|
||||
"items": [
|
||||
{
|
||||
"key": "images/2026/08/abc123.webp",
|
||||
"url": "https://pic.example.com/abc123.webp",
|
||||
"originalName": "demo.png",
|
||||
"contentType": "image/webp",
|
||||
"size": 12345,
|
||||
"uploadedAt": "2026-08-01T12:00:00.000Z",
|
||||
"markdown": "",
|
||||
"html": "<img src=\\"https://pic.example.com/abc123.webp\\" alt=\\"demo.png\\">"
|
||||
}
|
||||
],
|
||||
"errors": []
|
||||
}`
|
||||
},
|
||||
{
|
||||
id: 'list',
|
||||
category: 'image',
|
||||
method: 'GET',
|
||||
path: '/api/images',
|
||||
titleKey: 'api.docs.list.title',
|
||||
descKey: 'api.docs.list.desc',
|
||||
requiresAuth: true,
|
||||
params: [
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
location: 'query',
|
||||
required: false,
|
||||
descriptionKey: 'api.params.list.limit'
|
||||
},
|
||||
{
|
||||
name: 'page',
|
||||
type: 'number',
|
||||
location: 'query',
|
||||
required: false,
|
||||
descriptionKey: 'api.params.list.page'
|
||||
},
|
||||
{
|
||||
name: 'backendId',
|
||||
type: 'string',
|
||||
location: 'query',
|
||||
required: false,
|
||||
descriptionKey: 'api.params.backendId'
|
||||
}
|
||||
],
|
||||
curl: `curl "${baseUrl}/api/images?limit=20&page=1" \\
|
||||
${auth}`,
|
||||
responseExample: `{
|
||||
"items": [
|
||||
{
|
||||
"key": "images/2026/08/abc123.webp",
|
||||
"url": "https://pic.example.com/abc123.webp",
|
||||
"originalName": "demo.png",
|
||||
"contentType": "image/webp",
|
||||
"size": 12345,
|
||||
"uploadedAt": "2026-08-01T12:00:00.000Z",
|
||||
"markdown": "",
|
||||
"html": "<img src=\\"https://pic.example.com/abc123.webp\\" alt=\\"demo.png\\">"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"totalPages": 1
|
||||
}`
|
||||
},
|
||||
{
|
||||
id: 'search',
|
||||
category: 'image',
|
||||
method: 'GET',
|
||||
path: '/api/images/search',
|
||||
titleKey: 'api.docs.search.title',
|
||||
descKey: 'api.docs.search.desc',
|
||||
requiresAuth: true,
|
||||
params: [
|
||||
{
|
||||
name: 'q',
|
||||
type: 'string',
|
||||
location: 'query',
|
||||
required: true,
|
||||
descriptionKey: 'api.params.search.q'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
location: 'query',
|
||||
required: false,
|
||||
descriptionKey: 'api.params.list.limit'
|
||||
},
|
||||
{
|
||||
name: 'page',
|
||||
type: 'number',
|
||||
location: 'query',
|
||||
required: false,
|
||||
descriptionKey: 'api.params.list.page'
|
||||
},
|
||||
{
|
||||
name: 'backendId',
|
||||
type: 'string',
|
||||
location: 'query',
|
||||
required: false,
|
||||
descriptionKey: 'api.params.backendId'
|
||||
}
|
||||
],
|
||||
curl: `curl "${baseUrl}/api/images/search?q=demo&limit=20&page=1" \\
|
||||
${auth}`,
|
||||
responseExample: `{
|
||||
"items": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"totalPages": 0
|
||||
}`
|
||||
},
|
||||
{
|
||||
id: 'delete',
|
||||
category: 'image',
|
||||
method: 'DELETE',
|
||||
path: '/api/images',
|
||||
titleKey: 'api.docs.delete.title',
|
||||
descKey: 'api.docs.delete.desc',
|
||||
requiresAuth: true,
|
||||
params: [
|
||||
{
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
location: 'query',
|
||||
required: true,
|
||||
descriptionKey: 'api.params.delete.key'
|
||||
}
|
||||
],
|
||||
curl: `curl -X DELETE "${baseUrl}/api/images?key=images/2026/08/xxxx.webp" \\
|
||||
${auth}`,
|
||||
responseExample: `{
|
||||
"success": true
|
||||
}`
|
||||
},
|
||||
{
|
||||
id: 'batchDelete',
|
||||
category: 'image',
|
||||
method: 'POST',
|
||||
path: '/api/images/batch-delete',
|
||||
titleKey: 'api.docs.batchDelete.title',
|
||||
descKey: 'api.docs.batchDelete.desc',
|
||||
requiresAuth: true,
|
||||
params: [
|
||||
{
|
||||
name: 'keys',
|
||||
type: 'string[]',
|
||||
location: 'body',
|
||||
required: true,
|
||||
descriptionKey: 'api.params.batchDelete.keys'
|
||||
}
|
||||
],
|
||||
curl: `curl -X POST "${baseUrl}/api/images/batch-delete" \\
|
||||
${auth} \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{"keys":["images/2026/08/a.webp","images/2026/08/b.webp"]}'`,
|
||||
responseExample: `{
|
||||
"success": true,
|
||||
"deleted": ["images/2026/08/a.webp"],
|
||||
"failed": []
|
||||
}`
|
||||
},
|
||||
{
|
||||
id: 'twikoo',
|
||||
category: 'extension',
|
||||
method: 'POST',
|
||||
path: '/api/index.php',
|
||||
titleKey: 'api.docs.twikoo.title',
|
||||
descKey: 'api.docs.twikoo.desc',
|
||||
requiresAuth: false,
|
||||
params: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'file',
|
||||
location: 'form-data',
|
||||
required: true,
|
||||
descriptionKey: 'api.params.twikoo.image'
|
||||
},
|
||||
{
|
||||
name: 'token',
|
||||
type: 'string',
|
||||
location: 'form-data',
|
||||
required: true,
|
||||
descriptionKey: 'api.params.twikoo.token'
|
||||
}
|
||||
],
|
||||
curl: `curl -X POST "${baseUrl}/api/index.php" \\
|
||||
-F "token=${authHeader}" \\
|
||||
-F "image=@./demo.png"`,
|
||||
responseExample: `{
|
||||
"result": "success",
|
||||
"code": 200,
|
||||
"url": "https://pic.example.com/abc123.webp"
|
||||
}`
|
||||
},
|
||||
{
|
||||
id: 'config',
|
||||
category: 'system',
|
||||
method: 'GET',
|
||||
path: '/api/auth/config',
|
||||
titleKey: 'api.docs.config.title',
|
||||
descKey: 'api.docs.config.desc',
|
||||
requiresAuth: false,
|
||||
params: [],
|
||||
curl: `curl "${baseUrl}/api/auth/config"`,
|
||||
responseExample: `{
|
||||
"initialized": true,
|
||||
"allowRegistration": false,
|
||||
"legacyMode": false,
|
||||
"adminSecretConfigured": false,
|
||||
"apiUploadTokenConfigured": true
|
||||
}`
|
||||
},
|
||||
{
|
||||
id: 'count',
|
||||
category: 'system',
|
||||
method: 'GET',
|
||||
path: '/api/images/count',
|
||||
titleKey: 'api.docs.count.title',
|
||||
descKey: 'api.docs.count.desc',
|
||||
requiresAuth: true,
|
||||
params: [
|
||||
{
|
||||
name: 'backendId',
|
||||
type: 'string',
|
||||
location: 'query',
|
||||
required: false,
|
||||
descriptionKey: 'api.params.backendId'
|
||||
}
|
||||
],
|
||||
curl: `curl "${baseUrl}/api/images/count" \\
|
||||
${auth}`,
|
||||
responseExample: `{
|
||||
"total": 42
|
||||
}`
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,12 +3,13 @@ import type {
|
||||
ImageListResponse
|
||||
} from '~/types/image'
|
||||
|
||||
const PAGE_SIZE = 12
|
||||
const PAGE_SIZE_DEFAULT = 10
|
||||
|
||||
export function useImageList() {
|
||||
const { t } = useI18n()
|
||||
const items = ref<ImageItem[]>([])
|
||||
const page = ref(1)
|
||||
const pageSize = ref(PAGE_SIZE_DEFAULT)
|
||||
const totalPages = ref(1)
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
@@ -17,6 +18,8 @@ export function useImageList() {
|
||||
const searchQuery = ref('')
|
||||
const activeSearch = ref('')
|
||||
const activeStorageBackend = ref('all')
|
||||
const activeContentType = ref('all')
|
||||
const activeUploadSource = ref('all')
|
||||
const storageBackendOptions = ref<Array<{ id: string, name: string }>>([])
|
||||
|
||||
function storageQueryParams(): Record<string, string> {
|
||||
@@ -25,8 +28,21 @@ export function useImageList() {
|
||||
return { backendId }
|
||||
}
|
||||
|
||||
function filterQueryParams(): Record<string, string> {
|
||||
const params: Record<string, string> = { ...storageQueryParams() }
|
||||
const contentType = activeContentType.value.trim()
|
||||
if (contentType && contentType !== 'all') {
|
||||
params.contentType = contentType
|
||||
}
|
||||
const uploadSource = activeUploadSource.value.trim()
|
||||
if (uploadSource && uploadSource !== 'all') {
|
||||
params.uploadSource = uploadSource
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
function listQueryParams(): Record<string, string> {
|
||||
return storageQueryParams()
|
||||
return filterQueryParams()
|
||||
}
|
||||
|
||||
function matchesActiveStorage(item: ImageItem) {
|
||||
@@ -72,7 +88,7 @@ export function useImageList() {
|
||||
const response = await $fetch<ImageListResponse>('/api/images', {
|
||||
credentials: 'include',
|
||||
query: {
|
||||
limit: PAGE_SIZE,
|
||||
limit: pageSize.value,
|
||||
page: targetPage,
|
||||
...listQueryParams()
|
||||
}
|
||||
@@ -100,7 +116,7 @@ export function useImageList() {
|
||||
credentials: 'include',
|
||||
query: {
|
||||
q,
|
||||
limit: PAGE_SIZE,
|
||||
limit: pageSize.value,
|
||||
page: targetPage,
|
||||
...listQueryParams()
|
||||
}
|
||||
@@ -141,6 +157,40 @@ export function useImageList() {
|
||||
}
|
||||
}
|
||||
|
||||
async function setActiveContentType(contentType: string) {
|
||||
const next = contentType.trim() || 'all'
|
||||
if (next === activeContentType.value) return
|
||||
activeContentType.value = next
|
||||
page.value = 1
|
||||
if (activeSearch.value) {
|
||||
await Promise.all([fetchSearch(1), fetchTotal()])
|
||||
} else {
|
||||
await Promise.all([fetchList(1), fetchTotal()])
|
||||
}
|
||||
}
|
||||
|
||||
async function setActiveUploadSource(uploadSource: string) {
|
||||
const next = uploadSource.trim() || 'all'
|
||||
if (next === activeUploadSource.value) return
|
||||
activeUploadSource.value = next
|
||||
page.value = 1
|
||||
if (activeSearch.value) {
|
||||
await Promise.all([fetchSearch(1), fetchTotal()])
|
||||
} else {
|
||||
await Promise.all([fetchList(1), fetchTotal()])
|
||||
}
|
||||
}
|
||||
|
||||
async function resetFilters() {
|
||||
searchQuery.value = ''
|
||||
activeSearch.value = ''
|
||||
activeStorageBackend.value = 'all'
|
||||
activeContentType.value = 'all'
|
||||
activeUploadSource.value = 'all'
|
||||
page.value = 1
|
||||
await Promise.all([fetchList(1), fetchTotal()])
|
||||
}
|
||||
|
||||
async function submitSearch() {
|
||||
const q = searchQuery.value.trim()
|
||||
activeSearch.value = q
|
||||
@@ -161,6 +211,17 @@ export function useImageList() {
|
||||
}
|
||||
}
|
||||
|
||||
async function setPageSize(size: number) {
|
||||
if (!Number.isFinite(size) || size <= 0 || size === pageSize.value) return
|
||||
pageSize.value = size
|
||||
page.value = 1
|
||||
if (activeSearch.value) {
|
||||
await Promise.all([fetchSearch(1), fetchTotal()])
|
||||
} else {
|
||||
await Promise.all([fetchList(1), fetchTotal()])
|
||||
}
|
||||
}
|
||||
|
||||
function prependItems(newItems: ImageItem[]) {
|
||||
const matched = newItems.filter(item => matchesActiveStorage(item))
|
||||
if (!matched.length || page.value !== 1) return
|
||||
@@ -220,10 +281,13 @@ export function useImageList() {
|
||||
return {
|
||||
items,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages,
|
||||
total,
|
||||
loading,
|
||||
activeStorageBackend,
|
||||
activeContentType,
|
||||
activeUploadSource,
|
||||
storageBackendOptions,
|
||||
searchQuery,
|
||||
activeSearch,
|
||||
@@ -234,9 +298,13 @@ export function useImageList() {
|
||||
refreshList,
|
||||
initializeList,
|
||||
setActiveStorageBackend,
|
||||
setActiveContentType,
|
||||
setActiveUploadSource,
|
||||
resetFilters,
|
||||
loadStorageBackendOptions,
|
||||
submitSearch,
|
||||
goToPage,
|
||||
setPageSize,
|
||||
prependItems,
|
||||
removeItems
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user