316 lines
8.5 KiB
Vue
316 lines
8.5 KiB
Vue
<script setup lang="ts">
|
|
import { ref, watch, onMounted } 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 } from '@element-plus/icons-vue';
|
|
|
|
// Navigation
|
|
const activeMenu = ref("preparation");
|
|
|
|
// Theme
|
|
const isDark = ref(false);
|
|
|
|
const toggleTheme = (val: string | number | boolean) => {
|
|
// val comes from el-switch change event which is boolean | string | number
|
|
const isDarkMode = val === true;
|
|
isDark.value = isDarkMode;
|
|
if (isDarkMode) {
|
|
document.documentElement.classList.add('dark');
|
|
localStorage.setItem('theme', 'dark');
|
|
} else {
|
|
document.documentElement.classList.remove('dark');
|
|
localStorage.setItem('theme', 'light');
|
|
}
|
|
};
|
|
|
|
onMounted(async () => {
|
|
const savedTheme = localStorage.getItem('theme');
|
|
if (savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
isDark.value = true;
|
|
document.documentElement.classList.add('dark');
|
|
} else {
|
|
isDark.value = false;
|
|
document.documentElement.classList.remove('dark');
|
|
}
|
|
|
|
await loadConfig();
|
|
});
|
|
|
|
// Preparation Data
|
|
const workingDir = ref("");
|
|
const templateDir = ref("");
|
|
const selectedDate = ref<Date | null>(new Date());
|
|
const currentDir = ref("");
|
|
const videoNamePrefix = ref("");
|
|
|
|
// Configuration Types
|
|
interface AppConfig {
|
|
working_dir: string;
|
|
template_dir: string;
|
|
}
|
|
|
|
// Load/Save Config Logic
|
|
const loadConfig = async () => {
|
|
try {
|
|
const config = await invoke<AppConfig>('load_config');
|
|
if (config.working_dir) workingDir.value = config.working_dir;
|
|
if (config.template_dir) templateDir.value = config.template_dir;
|
|
} catch (e) {
|
|
console.error("Failed to load config:", e);
|
|
}
|
|
}
|
|
|
|
const saveConfig = async () => {
|
|
try {
|
|
await invoke('save_config', {
|
|
config: {
|
|
working_dir: workingDir.value,
|
|
template_dir: templateDir.value
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console.error("Failed to save config:", e);
|
|
}
|
|
}
|
|
|
|
// Watchers for Auto-Save
|
|
watch(workingDir, () => {
|
|
saveConfig();
|
|
});
|
|
|
|
watch(templateDir, () => {
|
|
saveConfig();
|
|
});
|
|
|
|
// Actions
|
|
const selectWorkingDir = async () => {
|
|
const selected = await open({
|
|
directory: true,
|
|
multiple: false,
|
|
});
|
|
if (selected) {
|
|
workingDir.value = selected as string;
|
|
}
|
|
};
|
|
|
|
const selectTemplateDir = async () => {
|
|
const selected = await open({
|
|
directory: true,
|
|
multiple: false,
|
|
});
|
|
if (selected) {
|
|
templateDir.value = selected as string;
|
|
}
|
|
};
|
|
|
|
const selectCurrentDir = async () => {
|
|
const selected = await open({
|
|
directory: true,
|
|
multiple: false,
|
|
});
|
|
if (selected) {
|
|
currentDir.value = selected as string;
|
|
}
|
|
};
|
|
|
|
const handleCopy = async () => {
|
|
if (!workingDir.value || !templateDir.value || !selectedDate.value) {
|
|
ElMessage.error("请填写完整信息(工作目录、模板目录、日期)");
|
|
return;
|
|
}
|
|
|
|
const date = selectedDate.value;
|
|
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
|
const dd = String(date.getDate()).padStart(2, '0');
|
|
const folderName = `${mm}${dd}视频`;
|
|
|
|
try {
|
|
const newPath = await invoke<string>("copy_directory", {
|
|
templatePath: templateDir.value,
|
|
targetPath: workingDir.value,
|
|
newFolderName: folderName,
|
|
});
|
|
|
|
ElMessage.success("目录拷贝并重命名成功!");
|
|
|
|
// Update Section 2
|
|
currentDir.value = newPath;
|
|
// Auto-update prefix is handled by watcher
|
|
} catch (error) {
|
|
ElMessage.error(`错误: ${error}`);
|
|
}
|
|
};
|
|
|
|
// Reactivity for Current Directory
|
|
watch(currentDir, (newPath) => {
|
|
if (!newPath) return;
|
|
|
|
// Check if path ends with "ddmm视频"
|
|
// Robust split for Windows/Unix paths
|
|
const dirName = newPath.split(/[/\\]/).pop();
|
|
|
|
if (!dirName) return;
|
|
|
|
const regex = /^(\d{2})(\d{2})视频$/; // matches mmdd视频
|
|
const match = dirName.match(regex);
|
|
|
|
if (match) {
|
|
const mm = match[1];
|
|
const dd = match[2];
|
|
const year = new Date().getFullYear();
|
|
// Format: AI-yyyymmdd-
|
|
videoNamePrefix.value = `AI-${year}${mm}${dd}-`;
|
|
}
|
|
});
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<el-container class="layout-container" style="height: 100vh">
|
|
<el-aside width="200px" class="main-aside">
|
|
<el-menu
|
|
:default-active="activeMenu"
|
|
class="el-menu-vertical-demo"
|
|
@select="(index: string) => activeMenu = index"
|
|
style="border-right: none; flex: 1;"
|
|
>
|
|
<el-menu-item index="preparation">
|
|
<el-icon><Document /></el-icon>
|
|
<span>准备</span>
|
|
</el-menu-item>
|
|
<el-menu-item index="review">
|
|
<el-icon><VideoPlay /></el-icon>
|
|
<span>审核</span>
|
|
</el-menu-item>
|
|
<el-menu-item index="check">
|
|
<el-icon><CircleCheck /></el-icon>
|
|
<span>检查</span>
|
|
</el-menu-item>
|
|
</el-menu>
|
|
|
|
<div class="aside-footer">
|
|
<el-switch
|
|
v-model="isDark"
|
|
inline-prompt
|
|
:active-icon="Moon"
|
|
:inactive-icon="Sunny"
|
|
@change="toggleTheme"
|
|
/>
|
|
</div>
|
|
</el-aside>
|
|
|
|
<el-main>
|
|
<div v-if="activeMenu === 'preparation'">
|
|
<h2>准备工作</h2>
|
|
|
|
<el-card class="box-card" shadow="hover">
|
|
<template #header>
|
|
<div class="card-header">
|
|
<span>新建任务</span>
|
|
</div>
|
|
</template>
|
|
|
|
<el-form label-position="top">
|
|
<el-form-item label="工作目录">
|
|
<el-input v-model="workingDir" placeholder="请选择工作目录" readonly>
|
|
<template #append>
|
|
<el-button @click="selectWorkingDir">选择</el-button>
|
|
</template>
|
|
</el-input>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="分类模板">
|
|
<el-input v-model="templateDir" placeholder="请选择分类模板" readonly>
|
|
<template #append>
|
|
<el-button @click="selectTemplateDir">选择</el-button>
|
|
</template>
|
|
</el-input>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="日期">
|
|
<el-date-picker
|
|
v-model="selectedDate"
|
|
type="date"
|
|
placeholder="选择日期"
|
|
format="YYYY-MM-DD"
|
|
style="width: 100%"
|
|
/>
|
|
</el-form-item>
|
|
|
|
<el-form-item>
|
|
<el-button type="primary" @click="handleCopy" style="width: 100%">拷贝并重命名</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
</el-card>
|
|
|
|
<el-divider />
|
|
|
|
<el-card class="box-card" shadow="hover">
|
|
<template #header>
|
|
<div class="card-header">
|
|
<span>当前任务</span>
|
|
</div>
|
|
</template>
|
|
|
|
<el-form label-position="top">
|
|
<el-form-item label="当前目录">
|
|
<el-input v-model="currentDir" placeholder="请选择或生成目录" readonly>
|
|
<template #append>
|
|
<el-button @click="selectCurrentDir">选择</el-button>
|
|
</template>
|
|
</el-input>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="文件前缀">
|
|
<el-input v-model="videoNamePrefix" placeholder="AI-yyyymmdd-" />
|
|
</el-form-item>
|
|
</el-form>
|
|
</el-card>
|
|
|
|
</div>
|
|
|
|
<div v-else-if="activeMenu === 'review'">
|
|
<h2>审核</h2>
|
|
<el-empty description="功能待定" />
|
|
</div>
|
|
|
|
<div v-else-if="activeMenu === 'check'">
|
|
<h2>检查</h2>
|
|
<el-empty description="功能待定" />
|
|
</div>
|
|
</el-main>
|
|
</el-container>
|
|
</template>
|
|
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
|
background-color: var(--el-bg-color-page);
|
|
color: var(--el-text-color-primary);
|
|
transition: background-color 0.3s, color 0.3s;
|
|
}
|
|
.layout-container {
|
|
height: 100vh;
|
|
}
|
|
.main-aside {
|
|
background-color: var(--el-bg-color);
|
|
border-right: 1px solid var(--el-border-color);
|
|
display: flex;
|
|
flex-direction: column;
|
|
transition: background-color 0.3s, border-color 0.3s;
|
|
}
|
|
.aside-footer {
|
|
padding: 16px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
border-top: 1px solid var(--el-border-color);
|
|
}
|
|
.box-card {
|
|
max-width: 800px;
|
|
margin-bottom: 20px;
|
|
}
|
|
</style>
|