feat: Library 批量注册 + PeopleView 修复 + 国际化
Library: - 添加 checkbox 和鼠标框选功能 - Unregistered 卡片添加快速注册按钮 - 状态标签移至左下方,人脸图标移至左侧中间 - Deep Scan 改为 unregister -> register -> process - 完整国际化(日语、繁体中文、英语) PeopleView: - 修复卡片不显示问题(使用 get_file_traces API) - 移除对 identity_id 字段的依赖 其他: - 添加日语语言包 - 添加 useDragSelect composable - 新增文档:Face Bbox Null Bug Report
This commit is contained in:
@@ -2,19 +2,19 @@
|
||||
<div id="ms-view-client" class="client-layout">
|
||||
<div class="client-header">
|
||||
<div class="header-top-row">
|
||||
<h1>File Manager</h1>
|
||||
<h1>{{ t('client.title') }}</h1>
|
||||
<div class="header-buttons">
|
||||
<button @click="goUp" :disabled="currentPath === '/'">Up</button>
|
||||
<button @click="showCreateFolderDialog">New Folder</button>
|
||||
<button @click="showUploadDialog">Upload</button>
|
||||
<button @click="confirmDelete" :disabled="!selectedFile">Delete</button>
|
||||
<button @click="showMoveDialog" :disabled="!selectedFile">Move</button>
|
||||
<button @click="refreshFiles">Refresh</button>
|
||||
<button @click="showCreateFolderDialog">{{ t('client.button.newFolder') }}</button>
|
||||
<button @click="showUploadDialog">{{ t('client.button.upload') }}</button>
|
||||
<button @click="confirmDelete" :disabled="!selectedFile">{{ t('client.button.delete') }}</button>
|
||||
<button @click="showMoveDialog" :disabled="!selectedFile">{{ t('client.button.move') }}</button>
|
||||
<button @click="refreshFiles">{{ t('client.button.refresh') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-path-row">Path: {{ currentPath }}</div>
|
||||
<div class="client-search">
|
||||
<input v-model="searchQuery" type="text" placeholder="Search files..." @input="onSearch" />
|
||||
<input v-model="searchQuery" type="text" :placeholder="t('client.search.placeholder')" @input="onSearch" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
<table v-else class="file-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th @click="sortBy('name')" class="sortable">Name <span v-if="sortField==='name'">{{ sortAsc ? '▲' : '▼' }}</span></th>
|
||||
<th @click="sortBy('size')" class="sortable">Size <span v-if="sortField==='size'">{{ sortAsc ? '▲' : '▼' }}</span></th>
|
||||
<th>Type</th>
|
||||
<th>Modified</th>
|
||||
<th @click="sortBy('name')" class="sortable">{{ t('client.table.name') }} <span v-if="sortField==='name'">{{ sortAsc ? '▲' : '▼' }}</span></th>
|
||||
<th @click="sortBy('size')" class="sortable">{{ t('client.table.size') }} <span v-if="sortField==='size'">{{ sortAsc ? '▲' : '▼' }}</span></th>
|
||||
<th>{{ t('client.table.type') }}</th>
|
||||
<th>{{ t('client.table.modified') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -78,11 +78,11 @@
|
||||
<!-- Create Folder Dialog -->
|
||||
<div v-if="showCreateFolder" class="dialog-overlay" @click="hideCreateFolderDialog">
|
||||
<div class="dialog" @click.stop>
|
||||
<h3>Create New Folder</h3>
|
||||
<input v-model="newFolderName" type="text" placeholder="Folder name" @keyup.enter="createFolder" />
|
||||
<h3>{{ t('client.dialog.createFolder') }}</h3>
|
||||
<input v-model="newFolderName" type="text" :placeholder="t('client.dialog.folderName')" @keyup.enter="createFolder" />
|
||||
<div class="dialog-buttons">
|
||||
<button @click="createFolder">Create</button>
|
||||
<button @click="hideCreateFolderDialog">Cancel</button>
|
||||
<button @click="createFolder">{{ t('client.button.create') }}</button>
|
||||
<button @click="hideCreateFolderDialog">{{ t('client.button.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,14 +90,14 @@
|
||||
<!-- Delete Confirm Dialog -->
|
||||
<div v-if="showDeleteConfirm" class="dialog-overlay" @click="cancelDelete">
|
||||
<div class="dialog" @click.stop>
|
||||
<h3>Delete "{{ deleteTarget }}"</h3>
|
||||
<h3>{{ t('client.dialog.deleteTitle', { name: deleteTarget }) }}</h3>
|
||||
<p v-if="selectedFile?.isDir && selectedFile.fileCount">
|
||||
This folder contains <strong>{{ selectedFile.fileCount }}</strong> file(s). They will be permanently deleted.
|
||||
</p>
|
||||
<p v-else>Are you sure you want to delete this item?</p>
|
||||
<p v-else>{{ t('client.dialog.deleteMessage', { type: selectedFile?.isDir ? t('client.type.folder') : t('client.type.file') }) }}</p>
|
||||
<div class="dialog-buttons">
|
||||
<button @click="doDelete" class="danger-btn">Delete</button>
|
||||
<button @click="cancelDelete">Cancel</button>
|
||||
<button @click="doDelete" class="danger-btn">{{ t('client.button.delete') }}</button>
|
||||
<button @click="cancelDelete">{{ t('client.button.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -105,11 +105,11 @@
|
||||
<!-- Rename Dialog -->
|
||||
<div v-if="showRename" class="dialog-overlay" @click="hideRenameDialog">
|
||||
<div class="dialog" @click.stop>
|
||||
<h3>Rename "{{ renameOldName }}"</h3>
|
||||
<input v-model="renameNewName" type="text" placeholder="New name" @keyup.enter="doRename" autofocus />
|
||||
<h3>{{ t('client.dialog.renameTitle', { name: renameOldName }) }}</h3>
|
||||
<input v-model="renameNewName" type="text" :placeholder="t('client.dialog.newName')" @keyup.enter="doRename" autofocus />
|
||||
<div class="dialog-buttons">
|
||||
<button @click="doRename">Rename</button>
|
||||
<button @click="hideRenameDialog">Cancel</button>
|
||||
<button @click="doRename">{{ t('client.button.rename') }}</button>
|
||||
<button @click="hideRenameDialog">{{ t('client.button.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,7 +117,7 @@
|
||||
<!-- File Preview Dialog -->
|
||||
<div v-if="showPreview" class="dialog-overlay" @click="hidePreview">
|
||||
<div class="dialog preview-dialog" @click.stop>
|
||||
<h3>File Preview: {{ selectedFile?.name }}</h3>
|
||||
<h3>{{ t('client.dialog.previewTitle', { name: selectedFile?.name }) }}</h3>
|
||||
<div v-if="previewLoading" class="loading">Loading preview...</div>
|
||||
<div v-else-if="previewContent" class="preview-content">
|
||||
<img v-if="isImage" :src="previewContent" alt="preview" />
|
||||
@@ -131,8 +131,8 @@
|
||||
<pre v-else>{{ previewContent }}</pre>
|
||||
</div>
|
||||
<div class="dialog-buttons">
|
||||
<button @click="downloadFile">Download</button>
|
||||
<button @click="hidePreview">Close</button>
|
||||
<button @click="downloadFile">{{ t('client.button.download') }}</button>
|
||||
<button @click="hidePreview">{{ t('common.close') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -140,7 +140,7 @@
|
||||
<!-- Upload Dialog -->
|
||||
<div v-if="showUpload" class="dialog-overlay" @click="hideUploadDialog">
|
||||
<div class="dialog upload-dialog" @click.stop>
|
||||
<h3>Upload File{{ uploadFiles.length > 1 ? 's' : '' }}</h3>
|
||||
<h3>{{ t('client.dialog.uploadTitle', { s: uploadFiles.length > 1 ? 's' : '' }) }}</h3>
|
||||
<input ref="fileInput" type="file" multiple @change="selectUploadFile" />
|
||||
<div v-if="uploadFiles.length > 0" class="upload-info">
|
||||
<p v-if="!uploading">{{ uploadFiles.length }} file(s) selected</p>
|
||||
@@ -155,9 +155,9 @@
|
||||
</div>
|
||||
<div class="dialog-buttons">
|
||||
<button @click="uploadFileToServer" :disabled="uploadFiles.length === 0 || uploading">
|
||||
{{ uploading ? 'Uploading...' : 'Upload' }}
|
||||
{{ uploading ? t('client.status.uploading') : t('client.button.upload') }}
|
||||
</button>
|
||||
<button @click="hideUploadDialog">Cancel</button>
|
||||
<button @click="hideUploadDialog">{{ t('client.button.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,7 +165,7 @@
|
||||
<!-- Move Dialog -->
|
||||
<div v-if="showMove" class="dialog-overlay" @click="showMove = false">
|
||||
<div class="dialog" @click.stop>
|
||||
<h3>{{ selectedFile?.isDir ? 'Move Folder' : 'Move File' }} "{{ selectedFile?.name }}"</h3>
|
||||
<h3>{{ t('client.dialog.moveTitle', { type: selectedFile?.isDir ? t('client.type.folder') : t('client.type.file'), name: selectedFile?.name }) }}</h3>
|
||||
<select v-model="moveTarget" class="move-select">
|
||||
<option value="" disabled>Select a folder...</option>
|
||||
<option v-for="folder in availableFolders" :key="folder.name" :value="folder.name">
|
||||
@@ -173,8 +173,8 @@
|
||||
</option>
|
||||
</select>
|
||||
<div class="dialog-buttons">
|
||||
<button @click="doMove" :disabled="!moveTarget">Move</button>
|
||||
<button @click="showMove = false">Cancel</button>
|
||||
<button @click="doMove" :disabled="!moveTarget">{{ t('client.button.move') }}</button>
|
||||
<button @click="showMove = false">{{ t('client.button.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -183,6 +183,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const currentPath = ref('/')
|
||||
const files = ref<any[]>([])
|
||||
@@ -1305,4 +1308,9 @@ onMounted(() => {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
table { display: block; overflow-x: auto; white-space: nowrap; }
|
||||
.client-view { padding: 8px; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user