refactor(web): align resource APIs and forms with the starter conventions

Centralize typed resource functions under common/apis and return complete JSON envelopes while preserving raw responses, bearer session isolation, and single-owner errors.

Keep settings state and queues inside the setup store. Use Element Plus forms and dialogs for login, settings, imports, moves, and images; preserve editor interactions and themes. Update callers, tests, generated declarations, and request import constraints together.
This commit is contained in:
eezd committed 2026-10-26 11:53:37 +08:00
1 parent cef0298c34
commit 6b537c03e0
114 files changed
+2876 -2217

No files matched your search

@@ -1,9 +1,9 @@
<script setup lang="ts">
import type { Entry } from '@@/apis/vault/type'
import type { Entry } from '@@/apis/tree/type'
import Icon from '@@/components/Icon/index.vue'
import { toast } from '@@/utils/notifications'
import { basename, hiddenAssetPath, imageInFolder, isImage } from '@@/utils/paths'
import { computed, onMounted, ref } from 'vue'
import Icon from '../../../common/components/Icon/index.vue'
import { basename, hiddenAssetPath, imageInFolder, isImage } from '../../../common/utils/paths'
import { metadata, refreshMetadata } from '../metadata'
import { cleanupImages, imageUrl, openReference, uploadFolderImages, ws } from '../workspace'
@@ -106,21 +106,11 @@ async function clean() {
</el-button>
</header>
<div class="rm-media-mobile-filters">
<select v-model="ws.imagesAll" aria-label="资源范围">
<option :value="false">
当前目录
</option><option :value="true">
整个知识库
</option>
</select><select v-model="usage" aria-label="引用状态">
<option value="all">
全部图片
</option><option value="used" :disabled="!certain">
已被引用
</option><option value="unused" :disabled="!certain">
未被引用
</option>
</select><el-button class="rm-btn" :disabled="busy || !certain" @click="proposeCleanup">
<el-select v-model="ws.imagesAll" aria-label="资源范围">
<el-option :value="false" label="当前目录" /><el-option :value="true" label="整个知识库" />
</el-select><el-select v-model="usage" aria-label="引用状态">
<el-option value="all" label="全部图片" /><el-option value="used" :disabled="!certain" label="已被引用" /><el-option value="unused" :disabled="!certain" label="未被引用" />
</el-select><el-button class="rm-btn" :disabled="busy || !certain" @click="proposeCleanup">
清理孤立图片
</el-button>
</div>
@@ -138,26 +128,20 @@ async function clean() {
</aside>
<div class="rm-media-content">
<div class="rm-media-toolbar">
<label class="rm-media-search"><Icon name="search" /><input v-model="query" type="search" placeholder="搜索图片名称或路径…" aria-label="搜索图片"></label><select v-model="format" aria-label="图片格式">
<option value="">
所有格式
</option><option v-for="type in types" :key="type" :value="type">
{{ type.toUpperCase() }}
</option>
</select><select v-model="sort" aria-label="图片排序">
<option value="modified">
最近修改
</option><option value="name">
名称
</option><option value="size">
文件大小
</option>
</select><button class="rm-icon-btn" :title="list ? '切换网格' : '切换列表'" @click="list = !list">
<el-input v-model="query" class="rm-media-search" type="search" clearable placeholder="搜索图片名称或路径…" aria-label="搜索图片">
<template #prefix>
<Icon name="search" />
</template>
</el-input><el-select v-model="format" aria-label="图片格式">
<el-option value="" label="所有格式" /><el-option v-for="type in types" :key="type" :value="type" :label="type.toUpperCase()" />
</el-select><el-select v-model="sort" aria-label="图片排序">
<el-option value="modified" label="最近修改" /><el-option value="name" label="名称" /><el-option value="size" label="文件大小" />
</el-select><button class="rm-icon-btn" :title="list ? '切换网格' : '切换列表'" @click="list = !list">
<Icon :name="list ? 'grid' : 'outline'" />
</button>
</div>
<div class="rm-media-upload">
<label>上传至 <select v-model="uploadDir" aria-label="上传目录"><option value="">根目录 / assets</option><option v-for="folder in directories" :key="folder.path" :value="folder.path">{{ folder.path }} / assets</option></select></label><input ref="picker" type="file" accept="image/*" multiple hidden @change="upload"><el-button class="rm-btn rm-btn-primary" :disabled="busy" @click="picker?.click()">
<label>上传至 <el-select v-model="uploadDir" aria-label="上传目录"><el-option value="" label="根目录 / assets" /><el-option v-for="folder in directories" :key="folder.path" :value="folder.path" :label="`${folder.path} / assets`" /></el-select></label><input ref="picker" type="file" accept="image/*" multiple hidden @change="upload"><el-button class="rm-btn rm-btn-primary" :disabled="busy" @click="picker?.click()">
<Icon name="plus" />{{ busy ? '处理中…' : '上传图片' }}
</el-button>
</div>
@@ -198,33 +182,52 @@ async function clean() {
</p>
</aside>
</div>
<div v-if="cleanupCandidates" class="rm-dialog-backdrop" @click.self="!busy && (cleanupCandidates = null)">
<section class="rm-dialog rm-cleanup-dialog" role="dialog" aria-label="确认清理孤立图片">
<div class="rm-dialog-head">
<h2>清理候选 · {{ cleanupCandidates.length }} 张</h2><button class="rm-icon-btn" :disabled="busy" title="关闭" @click="cleanupCandidates = null">
<Icon name="close" />
</button>
</div><p class="rm-share-note">
服务端会再次读取最新笔记;仍有引用的图片将跳过。确认后移入回收站。
</p><ul>
<li v-for="image in cleanupCandidates" :key="image.path">
<label><input v-model="cleanupSelected" type="checkbox" :value="image.path" :disabled="busy">{{ image.path }} <small>{{ size(image.size) }}</small></label>
</li>
</ul><p v-if="!cleanupCandidates.length" class="rm-share-note">
当前范围没有可清理的孤立图片。
</p><div class="rm-cleanup-actions">
<el-button class="rm-btn" :disabled="busy" @click="cleanupCandidates = null">
取消
</el-button><el-button class="rm-btn rm-btn-primary" :disabled="busy || !cleanupSelected.length" @click="clean">
移入回收站({{ cleanupSelected.length }})
</el-button>
</div>
</section>
</div>
<el-dialog :model-value="cleanupCandidates !== null" :title="`清理候选 · ${cleanupCandidates?.length ?? 0} 张`" width="640px" class="rm-cleanup-dialog" :close-on-click-modal="!busy" :close-on-press-escape="!busy" :show-close="!busy" :before-close="() => { if (!busy) cleanupCandidates = null }" @close="cleanupCandidates = null">
<p class="rm-share-note">
服务端会再次读取最新笔记;仍有引用的图片将跳过。确认后移入回收站。
</p><ul>
<li v-for="image in cleanupCandidates ?? []" :key="image.path">
<el-checkbox-group v-model="cleanupSelected">
<el-checkbox :value="image.path" :disabled="busy">
{{ image.path }} <small>{{ size(image.size) }}</small>
</el-checkbox>
</el-checkbox-group>
</li>
</ul><p v-if="!cleanupCandidates?.length" class="rm-share-note">
当前范围没有可清理的孤立图片。
</p><div class="rm-cleanup-actions">
<el-button class="rm-btn" :disabled="busy" @click="cleanupCandidates = null">
取消
</el-button><el-button class="rm-btn rm-btn-primary" :disabled="busy || !cleanupSelected.length" @click="clean">
移入回收站({{ cleanupSelected.length }})
</el-button>
</div>
</el-dialog>
</section>
</template>
<style scoped>
<style scoped lang="scss">
.rm-media-toolbar :deep(.el-select) {
width: 130px;
}
.rm-media-upload :deep(.el-select) {
width: 240px;
max-width: 100%;
}
.rm-media-mobile-filters :deep(.el-select) {
width: 130px;
}
.rm-media-search.el-input {
padding: 0;
border: 0;
}
:deep(.el-checkbox) {
height: auto;
white-space: normal;
}
:deep(.el-checkbox__label) {
overflow-wrap: anywhere;
}
.rm-media-library {
display: flex;
flex-direction: column;