feat(project): 初始化项目结构和配置

- 添加.gitignore和.flaskenv环境配置文件
- 创建Flask应用基础架构,包括models、services、utils模块
- 配置数据库模型User和Setting,集成SQLAlchemy和Alembic迁移
- 添加前端Vue项目结构,包含Element Plus组件库
- 配置前后端API路由和蓝prints模块
- 实现书籍下载服务BookService功能模块
- 添加代码规范配置.editorconfig、.oxfmtrc.json、.oxlintrc.json
- 配置VSCode推荐插件和前端构建工具链
- 实现文件服务FileService和相关业务逻辑
This commit is contained in:
2026-04-19 15:43:45 +08:00
parent b9fad6344f
commit ade1b6c9b0
63 changed files with 9610 additions and 1 deletions
+147
View File
@@ -0,0 +1,147 @@
<script setup lang="ts">
import {ref, onUnmounted} from "vue";
import {
files_rename,
music_rename,
image_hasTorrent,
video_hasTorrent,
video_deduplication,
torrent_deduplication,
video_findDeduplication,
torrent_statistics
} from "@/api"
// 警告项接口
interface messageItem {
id: number
text: string
}
interface BtnItem {
id: number
title: string
handle?: () => Promise<any>
}
let loading = ref(false)
let eventSource: EventSource // 保存 EventSource 实例
const status = ref('closed') // 连接状态:closed, connecting, connected
const messages = ref<messageItem[]>([{id: Date.now(), text: '输出结果'}])
const buttons = [
{id: 0, title: "小说下载"},
{id: 1, title: "文件重命名", handle: files_rename},
{id: 2, title: "视频查找重复", handle: video_findDeduplication},
{id: 3, title: "视频去重", handle: video_deduplication},
{id: 4, title: "种子去重", handle: torrent_deduplication},
{id: 5, title: "判断视频是否有torrent", handle: video_hasTorrent},
{id: 6, title: "判断图片是否有torrent", handle: image_hasTorrent},
{id: 7, title: "音乐文件重命名", handle: music_rename},
{id: 8, title: "种子统计", handle: torrent_statistics}
]
function closeAlert(id: number) {
console.log(id)
messages.value = messages.value.filter(item => item.id !== id)
}
const handleClick = async (btnObj: BtnItem) => {
if (btnObj.title === "断开") return
if (btnObj.title === "小说下载") {
connect("http://localhost:5000/api/book/download")
return
}
loading.value = true
try {
const res = await btnObj.handle?.()
const res_msg = Array.isArray(res.message) ? res.message : [res.message]
messages.value = res_msg.map((msg: string, index: number) => ({
id: Date.now() + index, // 简单生成唯一 ID,生产环境建议使用更可靠的 ID 生成器
text: msg
}))
messages.value.push({id: Date.now() + messages.value.length, text: '已完成'})
messages.value.reverse()
} finally {
loading.value = false
}
}
const connect = (url: string) => {
messages.value = []
if (eventSource) {
eventSource.close()
}
eventSource = new EventSource(url)
// 连接成功
eventSource.addEventListener("open", (event) => {
console.log('SSE 连接已打开')
status.value = 'connected'
})
// 监听普通消息
eventSource.addEventListener('message', (event) => {
messages.value.unshift({id: Date.now() + messages.value.length, text: event.data})
})
// 监听关闭事件
eventSource.addEventListener('close', (event) => {
messages.value.unshift({id: Date.now() + messages.value.length, text: event.data})
closeConnection()
})
// 错误处理
eventSource.addEventListener('error', (event) => {
console.log('连接断开或失败', new Date());
// 如果这里也频繁触发,确认了是断连重连
if (eventSource.readyState === EventSource.CLOSED) {
console.log('状态为 CLOSED');
}
})
}
const closeConnection = () => {
if (eventSource) {
eventSource.close()
// eventSource = null
status.value = 'closed'
console.log('SSE 连接已关闭')
}
}
onUnmounted(() => {
closeConnection()
})
</script>
<template>
<el-main>
<el-card>
<template #default>
<div v-loading="loading" :style="{height: '100%'}">
<el-alert v-for="item in messages" :key="item.id" :title="item.text"
type="primary"
@close="closeAlert(item.id)"/>
</div>
</template>
<template #footer>
<el-button-group>
<el-button type="primary" v-for="btn in buttons" :key="btn.id"
@click="handleClick(btn)">
{{ btn.title }}
</el-button>
</el-button-group>
</template>
</el-card>
</el-main>
</template>
<style scoped lang="scss">
.el-alert + .el-alert {
margin-top: 20px;
}
:deep(.el-card__footer) {
display: flex;
justify-content: center;
}
:deep(.el-card__body) {
//height: calc(100vh - 211px + 60px);
height: calc(100vh - 211px);
}
</style>