check page

This commit is contained in:
Julian Freeman
2025-12-07 20:27:00 -04:00
parent ffd6916313
commit f42ee4ec9e
2 changed files with 227 additions and 27 deletions

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import { ref, watch, onMounted, computed, onUnmounted } from "vue";
import { ref, watch, onMounted, computed, onUnmounted, nextTick } from "vue";
import { open } from "@tauri-apps/plugin-dialog";
import { invoke } from "@tauri-apps/api/core";
import { ElMessage } from 'element-plus';
import { Document, VideoPlay, CircleCheck, Moon, Sunny, Delete, UploadFilled } from '@element-plus/icons-vue';
import { Document, VideoPlay, CircleCheck, Moon, Sunny, Delete, UploadFilled, Refresh } from '@element-plus/icons-vue';
import { listen, UnlistenFn } from '@tauri-apps/api/event';
// Navigation
@@ -37,7 +37,11 @@ const importedFiles = ref<string[]>([]);
const videoNameInput = ref("");
const historyList = ref<string[]>([]);
const historyMap = ref<Record<string, string[]>>({});
const currentHistoryKey = ref(""); // Derived from dropped files relative to currentDir
const currentHistoryKey = ref("");
// Check Data
const checkLogs = ref<string[]>([]);
const logContainerRef = ref<HTMLElement | null>(null);
// Lifecycle
let unlistenDrop: UnlistenFn | null = null;
@@ -140,35 +144,21 @@ const updateHistoryList = () => {
}
const handleFileDrop = (paths: string[]) => {
// Filter for common video extensions if needed, or just accept all
// For now, accept all but assume they are videos
importedFiles.value = paths;
if (paths.length > 0 && currentDir.value) {
// Calculate Key: Path relative to Current Dir
// Logic: Take first file's parent dir. Remove currentDir prefix.
// Example: Current=C:\A, File=C:\A\B\vid.mp4. Parent=C:\A\B. Key=B.
// Normalize paths for comparison (remove trailing slashes, handle win/unix separators)
// Simple string manipulation for now.
const firstFile = paths[0];
// We need the parent directory of the file
// Since we don't have node 'path' module easily, we do simple string parsing
const separator = firstFile.includes('\\') ? '\\' : '/';
const lastSeparatorIndex = firstFile.lastIndexOf(separator);
const parentDir = firstFile.substring(0, lastSeparatorIndex);
if (parentDir.startsWith(currentDir.value)) {
let relative = parentDir.substring(currentDir.value.length);
// Remove leading separator if exists
if (relative.startsWith(separator)) {
relative = relative.substring(1);
}
currentHistoryKey.value = relative;
} else {
// File is outside current dir?
// Requirement says: "video file path minus current directory prefix"
// If outside, maybe use full path or just handle gracefully
currentHistoryKey.value = "";
}
updateHistoryList();
@@ -199,7 +189,6 @@ const handleRename = async () => {
return;
}
// Save to history
if (currentHistoryKey.value) {
try {
const newMap = await invoke<Record<string, string[]>>('save_history_item', {
@@ -213,7 +202,6 @@ const handleRename = async () => {
}
}
// Rename Files
try {
const msg = await invoke<string>('rename_videos', {
files: importedFiles.value,
@@ -221,8 +209,6 @@ const handleRename = async () => {
baseName: videoNameInput.value
});
ElMessage.success(msg);
// Clear imported files after success? Or keep them?
// Usually clearing is better feedback that "job done".
importedFiles.value = [];
} catch (e) {
ElMessage.error("重命名失败: " + e);
@@ -233,6 +219,61 @@ const useHistoryItem = (val: string) => {
videoNameInput.value = val;
}
// Check Logic
const appendLog = (msg: string) => {
checkLogs.value.push(msg);
nextTick(() => {
if (logContainerRef.value) {
logContainerRef.value.scrollTop = logContainerRef.value.scrollHeight;
}
});
}
const handleDeleteEmptyDirs = async () => {
checkLogs.value = [];
appendLog("正在扫描并删除空目录...");
try {
const deleted = await invoke<string[]>('delete_empty_dirs', { path: currentDir.value });
if (deleted.length === 0) {
appendLog("未发现空目录。");
} else {
deleted.forEach(path => appendLog(`已删除: ${path}`));
appendLog(`删除完毕,共删除 ${deleted.length} 个空目录。`);
}
} catch (e) {
appendLog(`错误: ${e}`);
}
}
const handleCheckNaming = async () => {
checkLogs.value = [];
appendLog(`正在检查文件命名,前缀要求: ${videoNamePrefix.value} ...`);
try {
const mismatches = await invoke<string[]>('check_file_naming', {
path: currentDir.value,
prefix: videoNamePrefix.value
});
if (mismatches.length === 0) {
appendLog("检查完毕,所有文件均符合命名规范。");
} else {
appendLog(`发现 ${mismatches.length} 个文件不符合规范:`);
// Simple tree view simulation
// Sort to group by dir
mismatches.sort();
mismatches.forEach(path => {
// Determine relative path to show cleaner tree-like structure
let displayPath = path;
if (path.startsWith(currentDir.value)) {
displayPath = "." + path.substring(currentDir.value.length);
}
appendLog(displayPath);
});
}
} catch (e) {
appendLog(`错误: ${e}`);
}
}
// Computed
const isReviewDisabled = computed(() => {
@@ -306,7 +347,7 @@ const handleCopy = async () => {
// Reactivity for Current Directory Prefix
watch(currentDir, (newPath) => {
if (!newPath) return;
const dirName = newPath.split(/[\\/]/).pop();
const dirName = newPath.split(/[/\\]/).pop();
if (!dirName) return;
const regex = /^(\d{2})(\d{2})视频$/;
@@ -454,8 +495,6 @@ watch(currentDir, (newPath) => {
</div>
</div>
<!-- Removed el-divider here -->
<!-- Action Area -->
<div class="action-area">
<el-input v-model="videoNameInput" placeholder="输入视频名称 (例如: 文本)" class="name-input">
@@ -483,10 +522,39 @@ watch(currentDir, (newPath) => {
</div>
</div>
<!-- Check Page -->
<div v-else-if="activeMenu === 'check'">
<h2>检查</h2>
<el-empty description="功能待定" />
<el-alert
v-if="isReviewDisabled"
:title="reviewWarningMessage"
type="warning"
show-icon
:closable="false"
style="margin-bottom: 20px"
/>
<div :class="{ 'disabled-area': isReviewDisabled }">
<div class="check-actions">
<el-button type="danger" :icon="Delete" @click="handleDeleteEmptyDirs">删除空目录</el-button>
<el-button type="primary" :icon="Refresh" @click="handleCheckNaming">检查命名</el-button>
</div>
<div class="log-container">
<div class="log-header">日志输出</div>
<div class="log-content" ref="logContainerRef">
<div v-for="(log, index) in checkLogs" :key="index" class="log-item">
{{ log }}
</div>
<div v-if="checkLogs.length === 0" class="log-empty">
暂无日志
</div>
</div>
</div>
</div>
</div>
</el-main>
</el-container>
</template>
@@ -606,4 +674,44 @@ body {
text-align: center;
color: var(--el-text-color-secondary);
}
/* Check Page Styles */
.check-actions {
margin-bottom: 20px;
display: flex;
gap: 10px;
}
.log-container {
border: 1px solid var(--el-border-color);
border-radius: 4px;
background-color: var(--el-bg-color);
display: flex;
flex-direction: column;
height: 400px;
}
.log-header {
padding: 10px 15px;
background-color: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color);
font-weight: bold;
font-size: 14px;
}
.log-content {
flex: 1;
overflow-y: auto;
padding: 10px;
font-family: monospace;
font-size: 13px;
white-space: pre-wrap;
}
.log-item {
margin-bottom: 4px;
line-height: 1.4;
}
.log-empty {
color: var(--el-text-color-secondary);
font-style: italic;
text-align: center;
margin-top: 20px;
}
</style>