Compare commits

...
10 Commits
Author SHA1 Message Date
YanLongChangAn 35dcc24096 config(core): 更新API服务器地址配置
- 将小说下载接口地址从 http://47.236.5.114:5000/api/book/download
  更新为 http://43.159.32.210:5000/api/book/download
- 将默认API基础地址从 http://47.236.5.114:5000/api/
  更新为 http://43.159.32.210:5000/api/
2026-08-16 17:31:29 +08:00
YanLongChangAn 4c4de7a5cb refactor(ByteToHexView): 使用 Element Plus 组件重构界面布局
- 将原始 div 结构替换为 el-main 和 el-card 组件
- 移除自定义容器样式,采用 Element Plus 默认样式
- 调整文本域边框样式从 border 改为 outline
- 禁用文本域调整大小功能
- 优化按钮状态样式过渡效果
- 移除页面底部说明区域
- 更新侧边栏菜单项路由配置为实际路径
2026-08-16 16:15:38 +08:00
YanLongChangAn cb67026821 feat(router): 添加字节对象转Hex功能页面
- 新增 ByteToHexView.vue 页面组件,实现JSON对象到十六进制字符串转换
- 在路由配置中注册 byte-to-hex 路径
- 在侧边栏菜单中添加字节对象转Hex导航项
- 实现JSON对象解析和十六进制转换核心逻辑
- 添加示例数据和自动转换功能
- 集成复制结果到剪贴板功能
2026-08-15 09:31:09 +08:00
YanLongChangAn 93dcffc983 config(app): 更新API服务器地址配置
- 将小说下载接口地址从 http://192.168.1.32:5000/api/book/download
  更新为 http://47.236.5.114:5000/api/book/download
- 将默认API基础地址从 http://192.168.1.32:5000/api/
  更新为 http://47.236.5.114:5000/api/
2026-08-14 21:48:43 +08:00
YanLongChangAn 837bd4df76 fix(book): 修复书籍下载服务中的文件类型验证和错误处理
- 添加 application/vnd.rar 到允许的内容类型列表
- 在响应中添加 raise_for_status() 以处理 HTTP 错误
- 添加 Content-Type 打印用于调试目的
- 改进文件类型验证逻辑
2026-08-14 19:07:33 +08:00
YanLongChangAn 26e1690f84 feat(constants): 更新常量配置以支持新前缀
- 添加 T38 到特殊前缀列表
- 优化代码格式增加可读性
2026-06-20 16:21:47 +08:00
YanLongChangAn bd88ca76de fix(book): 修复文件名合法性检查问题
- 添加了去除文件名末尾空格和点号的功能
- 确保文件名不会以非法字符结尾
2026-06-11 11:23:07 +08:00
YanLongChangAn 912eebddee remove(magnet): 删除磁力链接相关功能模块
- 移除 magnet_bp 蓝图及其路由配置
- 删除 app/__init__.py 中的 magnet_bp 导入和注册
- 移除 app/services/magnet_service.py 服务文件
- 从 requirements.txt 中删除 libtorrent 依赖
- 保留 Flask-CORS 但移除其他无关依赖项位置调整
2026-06-10 18:57:07 +08:00
YanLongChangAn 6cbacab98b feat(app): 添加 ASMR 和磁力链接功能模块
- 新增 asmr_bp 蓝图模块和 AsmrService 服务
- 新增 magnet_bp 蓝图模块和 MagnetService 服务
- 在应用初始化时注册新的蓝图模块
- 将 switch 语句替换为 if-elif 语句进行配置管理
- 在前端界面添加 ASMR 功能入口
- 增加 libtorrent 依赖包
- 修复字符串引号使用问题
- 增加文件重命名异常处理
- 扩展类型注解支持 Union 类型
- 增加请求超时时间到 30 秒
- 优化前端错误处理逻辑
2026-06-01 18:43:06 +08:00
YanLongChangAn f7539a33b0 fix(book_service): 修复帖子详情页面请求错误处理
- 添加requests.RequestException异常捕获和错误消息返回
- 重构URL构建逻辑到变量中提高代码可读性
- 移除调试打印语句中的baseUrl重复显示
- 在下载方法前添加注释分隔符增强代码结构
2026-05-18 19:37:42 +08:00
14 changed files with 487 additions and 42 deletions
+9 -8
View File
@@ -2,7 +2,7 @@ from flask import Flask
from flask_cors import CORS from flask_cors import CORS
from .extensions import db, migrate from .extensions import db, migrate
from .config import DevelopmentConfig, ProductionConfig, TestingConfig from .config import DevelopmentConfig, ProductionConfig, TestingConfig
from .blueprints import torrent_bp, book_bp, music_bp, file_bp, video_bp, image_bp from .blueprints import torrent_bp, book_bp, music_bp, file_bp, video_bp, image_bp, asmr_bp
def create_app(config_name="development"): def create_app(config_name="development"):
@@ -10,13 +10,13 @@ def create_app(config_name="development"):
CORS(app) CORS(app)
app.config.from_object(DevelopmentConfig) app.config.from_object(DevelopmentConfig)
match config_name: if config_name == 'production':
case 'production': app.config.from_object(ProductionConfig)
app.config.from_object(ProductionConfig) elif config_name == 'testing':
case 'testing': app.config.from_object(TestingConfig)
app.config.from_object(TestingConfig) else:
case _: app.config.from_object(DevelopmentConfig)
app.config.from_object(DevelopmentConfig)
db.init_app(app) db.init_app(app)
migrate.init_app(app, db) migrate.init_app(app, db)
@@ -27,5 +27,6 @@ def create_app(config_name="development"):
app.register_blueprint(file_bp.bp, url_prefix='/api/file') app.register_blueprint(file_bp.bp, url_prefix='/api/file')
app.register_blueprint(video_bp.bp, url_prefix='/api/video') app.register_blueprint(video_bp.bp, url_prefix='/api/video')
app.register_blueprint(image_bp.bp, url_prefix='/api/image') app.register_blueprint(image_bp.bp, url_prefix='/api/image')
app.register_blueprint(asmr_bp.bp, url_prefix='/api/asmr')
return app return app
+11
View File
@@ -0,0 +1,11 @@
from flask import Blueprint, Response, jsonify
from app.services.asmr_service import AsmrService
bp = Blueprint("asmr", __name__)
@bp.route("/")
def index():
asmr_object = AsmrService()
return jsonify({"code": 200, "message": asmr_object.get_all()})
+2 -1
View File
@@ -1,4 +1,5 @@
import os import os
IGNORE_DIRS = { IGNORE_DIRS = {
"System Volume Information", "System Volume Information",
"$RECYCLE.BIN", "$RECYCLE.BIN",
@@ -8,7 +9,7 @@ IGNORE_DIRS = {
"done_images", "done_images",
"女同" "女同"
} }
SPECIAL_PREFIXES = {"T28", "FC2"} SPECIAL_PREFIXES = {"T28", "FC2", "T38"}
SEPARATORS = {"♀  ", "", "+"} SEPARATORS = {"♀  ", "", "+"}
SPECIAL_DIRS = {"川村まや", "上原亜衣", "上原志織"} SPECIAL_DIRS = {"川村まや", "上原亜衣", "上原志織"}
IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.svg'} IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.svg'}
+16
View File
@@ -0,0 +1,16 @@
import requests
class AsmrService:
API_URL = "https://www.asmrgay.com/api/fs/list"
base_params = {
"path": "/asmr/中文音声",
"password": "",
"per_page": 30,
"refresh": False
}
def __init__(self):
pass
def get_all(self):
return [self.API_URL]
+21 -10
View File
@@ -11,7 +11,7 @@ from app.models import Setting
class BookService: class BookService:
ALLOWED_CONTENT_TYPES = ['"application/octet-stream"', "application/octet-stream", "text/plain", ALLOWED_CONTENT_TYPES = ['"application/octet-stream"', "application/octet-stream", "text/plain",
"application/zip"] "application/zip", "application/vnd.rar"]
def __init__(self): def __init__(self):
setting_record = Setting.query.filter_by(name="book_download").first() setting_record = Setting.query.filter_by(name="book_download").first()
@@ -51,6 +51,7 @@ class BookService:
@staticmethod @staticmethod
def legitimate_naming(name: str) -> str: def legitimate_naming(name: str) -> str:
"""将文件名中的非法字符替换为合法字符""" """将文件名中的非法字符替换为合法字符"""
name = name.rstrip(" .")
replacements = {":": "", "<": "", ">": "", "/": " ", "\\": " ", "?": ""} replacements = {":": "", "<": "", ">": "", "/": " ", "\\": " ", "?": ""}
for old, new in replacements.items(): for old, new in replacements.items():
name = name.replace(old, new) name = name.replace(old, new)
@@ -64,12 +65,18 @@ class BookService:
return target return target
def post_page(self, name, url): # 帖子页面 def post_page(self, name, url): # 帖子页面
print(f"详情页面 {self.setting["baseUrl"]}{url}") full_url = f"{self.setting['baseUrl']}{url}"
response = self.session.get(f"{self.setting["baseUrl"]}{url}") try:
response.raise_for_status() response = self.session.get(full_url)
print(f"详情页面状态码:{str(response.status_code)}") response.raise_for_status()
except requests.RequestException as e:
yield f"event: error\ndata: 请求帖子详情失败 {url}: {str(e)}\n\n"
return
print(f"详情页面 {str(response.status_code)} {full_url}")
soup = BeautifulSoup(response.text, 'lxml') soup = BeautifulSoup(response.text, 'lxml')
resource_boxs = soup.select('ignore_js_op') resource_boxs = soup.select('ignore_js_op')
for i in resource_boxs: for i in resource_boxs:
download_dir_path = self.download_path / self.setting["targetDate"] download_dir_path = self.download_path / self.setting["targetDate"]
self.create_folder(download_dir_path, name) self.create_folder(download_dir_path, name)
@@ -81,8 +88,7 @@ class BookService:
if privilege_level > self.setting["privilegeLevel"]: if privilege_level > self.setting["privilegeLevel"]:
print("下载失败,权限等级不够") print("下载失败,权限等级不够")
return return
yield from self.download_file(f"{self.setting["baseUrl"]}{i.select_one( yield from self.download_file(f"{self.setting['baseUrl']}{i.select_one('a')['href']}", name, i.select_one('a').string)
'a')['href']}", name, i.select_one('a').string)
# yield from self.download_file(f"{self.setting["baseUrl"]}{i.select_one( # yield from self.download_file(f"{self.setting["baseUrl"]}{i.select_one(
# 'a')['href']}", name, self.legitimate_naming(i.select_one('a').string)) # 'a')['href']}", name, self.legitimate_naming(i.select_one('a').string))
except RetryError as e: except RetryError as e:
@@ -97,10 +103,12 @@ class BookService:
print(f'跳转页面了:{file_url}') print(f'跳转页面了:{file_url}')
response = self.session.get(file_url, headers={"referer": file_url}, stream=True) response = self.session.get(file_url, headers={"referer": file_url}, stream=True)
print(f"下载状态码:{response.status_code} {file_url}") print(f"下载状态码:{response.status_code} {file_url}")
response.raise_for_status()
print(dir_name + "/" + file_name) print(dir_name + "/" + file_name)
content_type = response.headers.get('Content-Type', '') content_type = response.headers.get('Content-Type', '')
if content_type not in self.ALLOWED_CONTENT_TYPES: if content_type not in self.ALLOWED_CONTENT_TYPES:
yield f"event: mes_error\ndata: {self.setting["targetDate"]} {file_name}\n\n" yield f"event: mes_error\ndata: {self.setting['targetDate']} {file_name}\n\n"
print(content_type)
print("不是可下载文件") print("不是可下载文件")
return return
target_dir = self.download_path / self.setting["targetDate"] / dir_name / file_name target_dir = self.download_path / self.setting["targetDate"] / dir_name / file_name
@@ -109,7 +117,7 @@ class BookService:
for chunk in response.iter_content(chunk_size=1024): for chunk in response.iter_content(chunk_size=1024):
if chunk: if chunk:
f.write(chunk) f.write(chunk)
yield f"data: {self.setting["targetDate"]} {file_name}\n\n" yield f"data: {self.setting['targetDate']} {file_name}\n\n"
except requests.RequestException as e: except requests.RequestException as e:
yield f"event: error\ndata: {self.setting['targetDate']} {file_name} 网络错误\n\n" yield f"event: error\ndata: {self.setting['targetDate']} {file_name} 网络错误\n\n"
raise # 触发 tenacity 重试 raise # 触发 tenacity 重试
@@ -122,7 +130,7 @@ class BookService:
yield f"data: 不允许下载当天的\n\n" yield f"data: 不允许下载当天的\n\n"
return None return None
# yield f"data: 当前页面 {page}\n\n" # yield f"data: 当前页面 {page}\n\n"
url = f"{self.setting["baseUrl"]}forum.php?mod={self.setting["mod"]}&fid={self.setting["fid"]}&page={page}" # 版块页面地址 url = f"{self.setting['baseUrl']}forum.php?mod={self.setting['mod']}&fid={self.setting['fid']}&page={page}" # 版块页面地址
response = self.session.get(url) # 请求 response = self.session.get(url) # 请求
response.raise_for_status() response.raise_for_status()
soup = BeautifulSoup(response.text, 'lxml') # 解析 soup = BeautifulSoup(response.text, 'lxml') # 解析
@@ -193,6 +201,9 @@ class BookService:
else: else:
return element.select_one(".by em span").string return element.select_one(".by em span").string
# ------------------------------------------------------------
# 下载
# ------------------------------------------------------------
def book_download(self): def book_download(self):
print(os.getenv('aa')) print(os.getenv('aa'))
self.create_folder(self.download_path, self.setting["targetDate"]) # 创建文件夹 self.create_folder(self.download_path, self.setting["targetDate"]) # 创建文件夹
+5 -2
View File
@@ -16,7 +16,10 @@ class FileService:
return None return None
new_path = os.path.join(os.path.dirname(entry.path), new_name) new_path = os.path.join(os.path.dirname(entry.path), new_name)
os.rename(entry.path, new_path) try:
os.rename(entry.path, new_path)
except Exception as e:
print(e)
self.result_list.insert(0, new_path) self.result_list.insert(0, new_path)
# return new_path # return new_path
@@ -73,4 +76,4 @@ class FileService:
def rename(self) -> List[str]: def rename(self) -> List[str]:
FolderService(paths=self.paths, ignore_dirs=IGNORE_DIRS, folder_callback=self.process_folder_name, FolderService(paths=self.paths, ignore_dirs=IGNORE_DIRS, folder_callback=self.process_folder_name,
file_callback=self.process_file_name).process_folder() file_callback=self.process_file_name).process_folder()
return self.result_list return self.result_list
+2 -2
View File
@@ -1,11 +1,11 @@
import os import os
from typing import Optional, Callable, List from typing import Optional, Callable, List, Union
class FolderService: class FolderService:
def __init__( def __init__(
self, self,
paths: List[str] | str, paths: Union[List[str], str],
ignore_dirs: Optional[set] = None, ignore_dirs: Optional[set] = None,
folder_callback: Optional[Callable] = None, folder_callback: Optional[Callable] = None,
file_callback: Optional[Callable] = None, file_callback: Optional[Callable] = None,
+15 -9
View File
@@ -2,25 +2,25 @@ import request from "../utils/request.ts"
type ResponseData = Promise<{ code: number, message: Array<never> }> type ResponseData = Promise<{ code: number, message: Array<never> }>
export function book_download():ResponseData { export function book_download(): ResponseData {
return request({ return request({
url: "/book/download", url: "/book/download",
}) })
} }
export function video_findDeduplication():ResponseData { export function video_findDeduplication(): ResponseData {
return request({ return request({
url: "/video/findDeduplication", url: "/video/findDeduplication",
}) })
} }
export function video_deduplication():ResponseData { export function video_deduplication(): ResponseData {
return request({ return request({
url: "/video/deduplication", url: "/video/deduplication",
}) })
} }
export function video_hasTorrent():ResponseData { export function video_hasTorrent(): ResponseData {
return request({ return request({
url: "/video/hasTorrent", url: "/video/hasTorrent",
}) })
@@ -32,31 +32,31 @@ export function file_rename(): ResponseData {
}) })
} }
export function torrent_deduplication():ResponseData { export function torrent_deduplication(): ResponseData {
return request({ return request({
url: "/torrent/deduplication", url: "/torrent/deduplication",
}) })
} }
export function image_export():ResponseData { export function image_export(): ResponseData {
return request({ return request({
url: "/image/export", url: "/image/export",
}) })
} }
export function image_hasTorrent():ResponseData { export function image_hasTorrent(): ResponseData {
return request({ return request({
url: "/image/hasTorrent", url: "/image/hasTorrent",
}) })
} }
export function music_rename():ResponseData { export function music_rename(): ResponseData {
return request({ return request({
url: "/music/rename", url: "/music/rename",
}) })
} }
export function test():ResponseData { export function test(): ResponseData {
return request({ return request({
url: "/test", url: "/test",
}) })
@@ -67,3 +67,9 @@ export function torrent_statistics(): ResponseData {
url: "/torrent/statistics", url: "/torrent/statistics",
}) })
} }
export function asmr(): ResponseData {
return request({
url: "/asmr/",
})
}
@@ -10,26 +10,32 @@ const {isCollapse} = storeToRefs(isCollapseStore)
<template> <template>
<el-aside :width="!isCollapse?'200px':'auto'"> <el-aside :width="!isCollapse?'200px':'auto'">
<el-scrollbar> <el-scrollbar>
<el-menu default-active="2" :collapse="isCollapse" router> <el-menu :default-active="$route.path" :collapse="isCollapse" router>
<el-menu-item index="1" route="index"> <el-menu-item index="/index" route="index">
<el-icon> <el-icon>
<Menu/> <Menu/>
</el-icon> </el-icon>
<span>首页</span> <span>首页</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="2" route="about"> <el-menu-item index="/about" route="about">
<el-icon> <el-icon>
<Menu/> <Menu/>
</el-icon> </el-icon>
<span>统计</span> <span>统计</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="3" disabled> <el-menu-item index="/byte-to-hex" route="byte-to-hex">
<el-icon>
<Menu/>
</el-icon>
<span>字节对象转Hex</span>
</el-menu-item>
<el-menu-item index="4" disabled>
<el-icon> <el-icon>
<document/> <document/>
</el-icon> </el-icon>
<span>Navigator Three</span> <span>Navigator Three</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="4"> <el-menu-item index="5">
<el-icon> <el-icon>
<setting/> <setting/>
</el-icon> </el-icon>
+5
View File
@@ -19,6 +19,11 @@ const router = createRouter({
name: 'About', name: 'About',
component: () => import('../views/AboutView.vue'), component: () => import('../views/AboutView.vue'),
}, },
{
path: 'byte-to-hex',
name: 'ByteToHex',
component: () => import('../views/ByteToHexView.vue'),
},
], ],
}, },
{ {
+2 -2
View File
@@ -23,9 +23,9 @@ interface RequestCache {
axios.defaults.headers["Content-Type"] = "application/json; charset=UTF-8" axios.defaults.headers["Content-Type"] = "application/json; charset=UTF-8"
const service: AxiosInstance = axios.create({ const service: AxiosInstance = axios.create({
baseURL: "http://192.168.1.32:5000/api/", baseURL: "http://43.159.32.210:5000/api/",
// baseURL: "/api", // baseURL: "/api",
timeout: 10000, timeout: 30000,
}) })
service.interceptors.request.use((config: InternalAxiosRequestConfig) => { service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
+380
View File
@@ -0,0 +1,380 @@
<template>
<el-main>
<el-card>
<h1>🔑 字节对象 Hex 密钥</h1>
<p class="sub">
将键为数字索引的 JSON 对象转换为 32 位十六进制字符串适用于 AES128 密钥
</p>
<label for="jsonInput">输入 JSON 对象</label>
<textarea
id="jsonInput"
v-model="inputText"
placeholder='例如:{"0":14,"1":3,"2":121,...}'
@keydown.ctrl.enter="performConversion"
@keydown.meta.enter="performConversion"
></textarea>
<div class="actions">
<button @click="performConversion">🔄 转换</button>
<button class="secondary" :disabled="!copyEnabled" @click="copyResult">
📋 复制 Hex
</button>
<span class="copy-feedback" :class="{ show: copyFeedbackVisible }"
>已复制</span
>
</div>
<div class="result-area">
<div class="result-label">
<span>转换结果</span>
</div>
<div
class="result-box"
:class="resultClass"
v-text="resultText"
></div>
</div>
<div class="example-hint">
💡 点击
<button class="link-btn" @click="loadExample">加载示例</button>
快速体验
</div>
</el-card>
</el-main>
</template>
<script setup lang="ts">
import {ref, onMounted} from 'vue'
// 示例数据
const EXAMPLE_OBJ: Record<string, number> = {
'0': 14,
'1': 3,
'2': 121,
'3': 117,
'4': 7,
'5': 245,
'6': 22,
'7': 34,
'8': 136,
'9': 163,
'10': 201,
'11': 114,
'12': 195,
'13': 230,
'14': 109,
'15': 204,
}
// 响应式数据
const inputText = ref<string>('')
const resultText = ref<string>('等待转换…')
const resultClass = ref<string>('') // 用于 success / error
const copyFeedbackVisible = ref<boolean>(false)
const copyEnabled = ref<boolean>(false)
// 核心转换函数
function convertToHex(obj: Record<string, unknown>): string {
// 获取所有键并转为数字排序
const keys = Object.keys(obj)
.map((k) => Number(k))
.sort((a, b) => a - b)
// 检查是否有非数字键
if (keys.some((k) => isNaN(k))) {
throw new Error('对象包含非数字键,请确保键为 "0", "1", … 格式')
}
// 检查长度是否为16
if (keys.length !== 16) {
throw new Error(`期望 16 个字节,实际获得 ${keys.length}`)
}
// 按顺序取值并转为两位十六进制
const hex = keys
.map((key) => {
const val = obj[key]
if (typeof val !== 'number' || isNaN(val)) {
throw new Error(`键 "${key}" 对应的值不是有效数字 (${val})`)
}
if (val < 0 || val > 255) {
throw new Error(`键 "${key}" 的值 ${val} 超出字节范围 (0-255)`)
}
return val.toString(16).padStart(2, '0')
})
.join('')
return hex
}
// 执行转换
function performConversion(): void {
const raw = inputText.value.trim()
if (!raw) {
resultText.value = '⚠️ 请输入 JSON 数据'
resultClass.value = 'error'
copyEnabled.value = false
copyFeedbackVisible.value = false
return
}
try {
const obj = JSON.parse(raw)
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
throw new Error('输入必须是 JSON 对象(不是数组或原始类型)')
}
const hex = convertToHex(obj)
resultText.value = hex
resultClass.value = 'success'
copyEnabled.value = true
copyFeedbackVisible.value = false
} catch (err: any) {
resultText.value = `❌ 转换失败:${err.message}`
resultClass.value = 'error'
copyEnabled.value = false
copyFeedbackVisible.value = false
}
}
// 加载示例
function loadExample(): void {
inputText.value = JSON.stringify(EXAMPLE_OBJ, null, 2)
performConversion() // 自动转换
}
// 复制结果
function copyResult(): void {
const text = resultText.value
if (!text || resultClass.value === 'error') return
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard
.writeText(text)
.then(() => {
copyFeedbackVisible.value = true
setTimeout(() => (copyFeedbackVisible.value = false), 2000)
})
.catch(() => {
fallbackCopy(text)
})
} else {
fallbackCopy(text)
}
}
function fallbackCopy(text: string): void {
const textarea = document.createElement('textarea')
textarea.value = text
document.body.appendChild(textarea)
textarea.select()
try {
document.execCommand('copy')
copyFeedbackVisible.value = true
setTimeout(() => (copyFeedbackVisible.value = false), 2000)
} catch (_) {
alert('复制失败,请手动复制')
}
document.body.removeChild(textarea)
}
// 组件挂载时加载示例
onMounted(() => {
loadExample()
})
</script>
<style lang="scss" scoped>
// 变量(可根据需要调整)
$primary: #3b82f6;
$primary-hover: #2563eb;
$bg: #f6f8fa;
$card-bg: #ffffff;
$text-dark: #1f2937;
$text-gray: #6b7280;
$border: #d1d5db;
$radius: 16px;
:deep(.el-card__body) {
//height: calc(100vh - 211px + 60px);
height: calc(100vh - 141px);
}
* {
box-sizing: border-box;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}
h1 {
font-size: 22px;
font-weight: 600;
margin-top: 0;
margin-bottom: 8px;
color: $text-dark;
}
.sub {
color: $text-gray;
font-size: 14px;
margin-top: -4px;
margin-bottom: 20px;
}
label {
font-weight: 500;
font-size: 14px;
color: #374151;
display: block;
margin-bottom: 6px;
}
textarea {
width: 100%;
height: 200px;
padding: 12px 14px;
outline: 1px solid $border;
border: none;
border-radius: 10px;
font-family: 'Menlo', 'Cascadia Code', 'Consolas', monospace;
font-size: 13px;
line-height: 1.6;
resize: none;
transition: border 0.2s, background 0.2s;
background: #fafbfc;
&:focus {
border-color: $primary;
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
background: white;
}
}
.actions {
display: flex;
gap: 12px;
margin: 18px 0 14px;
flex-wrap: wrap;
align-items: center;
}
button {
background: $primary;
color: white;
border: none;
padding: 10px 24px;
border-radius: 8px;
font-weight: 500;
font-size: 14px;
cursor: pointer;
transition: background 0.15s, transform 0.1s;
display: inline-flex;
align-items: center;
gap: 6px;
&:hover {
background: $primary-hover;
}
&:active {
transform: scale(0.96);
}
&.secondary {
background: #e5e7eb;
color: $text-dark;
&:hover {
background: #d1d5db;
}
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
&:hover {
background: #e5e7eb;
}
}
&.link-btn {
background: none;
border: none;
color: $primary;
font-weight: 500;
padding: 0;
font-size: 13px;
text-decoration: underline;
display: inline;
&:hover {
color: $primary-hover;
background: none;
transform: none;
}
}
}
.copy-feedback {
font-size: 13px;
color: #10b981;
margin-left: 12px;
opacity: 0;
transition: opacity 0.3s;
&.show {
opacity: 1;
}
}
.result-area {
margin-top: 8px;
}
.result-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
span {
font-weight: 500;
font-size: 14px;
color: #374151;
}
}
.result-box {
background: #f3f4f6;
border-radius: 10px;
padding: 14px 16px;
font-family: 'Menlo', 'Cascadia Code', 'Consolas', monospace;
font-size: 14px;
word-break: break-all;
min-height: 48px;
border: 1px solid #e5e7eb;
color: $text-dark;
transition: background 0.2s, border-color 0.2s;
&.error {
background: #fee2e2;
border-color: #fca5a5;
color: #991b1b;
}
&.success {
background: #ecfdf5;
border-color: #6ee7b7;
}
}
.example-hint {
font-size: 13px;
color: $text-gray;
margin-top: 6px;
}
</style>
+8 -3
View File
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref, onUnmounted} from "vue"; import {ref, onUnmounted} from "vue";
import { import {
asmr,
file_rename, file_rename,
music_rename, music_rename,
image_hasTorrent, image_hasTorrent,
@@ -38,7 +39,8 @@ const buttons = [
{id: 5, title: "判断视频是否有torrent", handle: video_hasTorrent}, {id: 5, title: "判断视频是否有torrent", handle: video_hasTorrent},
{id: 6, title: "判断图片是否有torrent", handle: image_hasTorrent}, {id: 6, title: "判断图片是否有torrent", handle: image_hasTorrent},
{id: 7, title: "音乐文件重命名", handle: music_rename}, {id: 7, title: "音乐文件重命名", handle: music_rename},
{id: 8, title: "种子统计", handle: torrent_statistics} {id: 8, title: "种子统计", handle: torrent_statistics},
{id: 9, title: "asmr", handle: asmr}
] ]
@@ -50,7 +52,7 @@ function closeAlert(id: number) {
const handleClick = async (btnObj: BtnItem) => { const handleClick = async (btnObj: BtnItem) => {
if (btnObj.title === "断开") return if (btnObj.title === "断开") return
if (btnObj.title === "小说下载") { if (btnObj.title === "小说下载") {
connect("http://192.168.1.32:5000/api/book/download") connect("http://43.159.32.210:5000/api/book/download")
return return
} }
loading.value = true loading.value = true
@@ -64,7 +66,10 @@ const handleClick = async (btnObj: BtnItem) => {
})) }))
messages.value.push({id: Date.now() + messages.value.length, text: '已完成'}) messages.value.push({id: Date.now() + messages.value.length, text: '已完成'})
messages.value.reverse() messages.value.reverse()
} finally { } catch (e) {
console.log(e)
}
finally {
loading.value = false loading.value = false
} }
} }
BIN
View File
Binary file not shown.