PKC音色管理后台v1.0.0
This commit is contained in:
parent
154b1a45cc
commit
05bd4aa961
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM python:3.10-slim
|
||||
COPY PKCYsManage ./app
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
VOLUME ["/app"]
|
||||
# 设置环境变量
|
||||
ENV PKC_VERSION=v1.0.0
|
||||
# 安装依赖
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
EXPOSE 39900
|
||||
# 设置容器启动时执行的命令
|
||||
CMD ["python", "main.py"]
|
||||
10
PKCYsManage/config.json
Normal file
10
PKCYsManage/config.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"标题": "PKC音色管理后台",
|
||||
"端口": "39900",
|
||||
"users": [
|
||||
{
|
||||
"username": "pkc",
|
||||
"password": "pkc"
|
||||
}
|
||||
]
|
||||
}
|
||||
330
PKCYsManage/main.py
Normal file
330
PKCYsManage/main.py
Normal file
@ -0,0 +1,330 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from flask import Flask, render_template, request, jsonify, send_from_directory, redirect, url_for, abort, Response, session, flash
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from werkzeug.utils import secure_filename
|
||||
import datetime
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'pkc' # 用于会话管理
|
||||
|
||||
# 设置文件路径
|
||||
JSON_FILE = 'ys.json'
|
||||
# BG_SOUND_DIR = 'bgSound' # 背景音文件夹
|
||||
|
||||
# 读取 JSON 文件
|
||||
def read_json_file(file):
|
||||
if os.path.exists(file):
|
||||
with open(file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
# 加载用户数据
|
||||
def load_users():
|
||||
with open('config.json', 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data['users']
|
||||
|
||||
def get_config():
|
||||
with open('config.json', 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data
|
||||
|
||||
userConfig = get_config()
|
||||
PKC_USER = os.environ.get('PKC_USER')
|
||||
PKC_PASSWORD = os.environ.get('PKC_PASSWORD')
|
||||
PKC_VERSION = os.environ.get('PKC_VERSION')
|
||||
PKC_TITLE = os.environ.get('PKC_TITLE')
|
||||
|
||||
if PKC_USER is None:
|
||||
PKC_USER = userConfig['users'][0]['username']
|
||||
if PKC_PASSWORD is None:
|
||||
PKC_PASSWORD = userConfig['users'][0]['password']
|
||||
if PKC_VERSION is None:
|
||||
PKC_VERSION = 'v1.0.0'
|
||||
if PKC_TITLE is None:
|
||||
PKC_TITLE = userConfig['标题']
|
||||
# 导出 JSON 文件
|
||||
@app.route('/ysList')
|
||||
def printYsList():
|
||||
# return jsonify(read_json_file(JSON_FILE))
|
||||
# 将数据转换为格式化的 JSON 字符串
|
||||
json_data = json.dumps(read_json_file(JSON_FILE), ensure_ascii=False, indent=4)
|
||||
|
||||
# 创建响应对象,设置内容类型为 JSON
|
||||
return Response(json_data, mimetype='application/json; charset=utf-8')
|
||||
# 保存 JSON 文件
|
||||
def save_json_file(file, data):
|
||||
with open(file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
# 添加分类
|
||||
def add_category(audio_colors, name, token, sort, desc, url, alias, type):
|
||||
if name not in audio_colors:
|
||||
audio_colors[name] = {
|
||||
'token': token,
|
||||
'sort': sort,
|
||||
'desc': desc,
|
||||
'url': url,
|
||||
'alias': alias,
|
||||
'type': type,
|
||||
'list': []
|
||||
}
|
||||
save_json_file(JSON_FILE, audio_colors)
|
||||
return f"分类【{name}】 已添加。"
|
||||
else:
|
||||
return f"分类【{name}】 已存在。"
|
||||
|
||||
# 修改分类
|
||||
def edit_category(audio_colors, old_name, new_name, token, sort, desc, url, alias, type):
|
||||
if old_name in audio_colors:
|
||||
audio_colors[new_name] = {
|
||||
'token': token,
|
||||
'sort': sort,
|
||||
'desc': desc,
|
||||
'url': url,
|
||||
'alias': alias,
|
||||
'type': type,
|
||||
'list': audio_colors[old_name]['list']
|
||||
}
|
||||
if new_name != old_name:
|
||||
del audio_colors[old_name]
|
||||
save_json_file(JSON_FILE, audio_colors)
|
||||
return f"分类【{old_name}】 已修改为【{new_name}】。"
|
||||
else:
|
||||
return f"分类【{old_name}】 不存在。"
|
||||
|
||||
# 删除分类
|
||||
def delete_category(audio_colors, name):
|
||||
if name in audio_colors:
|
||||
del audio_colors[name]
|
||||
save_json_file(JSON_FILE, audio_colors)
|
||||
return f"分类【{name}】 已删除。"
|
||||
else:
|
||||
return f"分类【{name}】 不存在。"
|
||||
|
||||
# 管理分类中的 list
|
||||
def add_to_list(audio_colors, category_name, name, desc, vid, img):
|
||||
if category_name in audio_colors:
|
||||
if vid_exists(audio_colors, vid):
|
||||
return f"音色 ID【{vid}】 已存在。"
|
||||
else:
|
||||
audio_colors[category_name]['list'].append({
|
||||
'name': name,
|
||||
'desc': desc,
|
||||
'img': img,
|
||||
'vid': vid
|
||||
})
|
||||
save_json_file(JSON_FILE, audio_colors)
|
||||
return f"音色【{name}】 已添加到分类【{category_name}】。"
|
||||
else:
|
||||
return f"分类【{category_name}】 不存在。"
|
||||
|
||||
def edit_list_item(audio_colors, category_name, old_name, new_name, desc, vid, img):
|
||||
if category_name in audio_colors:
|
||||
for i, item in enumerate(audio_colors[category_name]['list']):
|
||||
if item['name'] == old_name:
|
||||
if item['vid'] != vid and vid_exists(audio_colors, vid):
|
||||
return f"音色 ID【{vid}】 已存在。"
|
||||
else:
|
||||
audio_colors[category_name]['list'][i] = {
|
||||
'name': new_name,
|
||||
'desc': desc,
|
||||
'img': img,
|
||||
'vid': vid
|
||||
}
|
||||
save_json_file(JSON_FILE, audio_colors)
|
||||
return f"音色【{old_name}】 已修改为【{new_name}】。"
|
||||
return f"音色【{old_name}】 不存在于分类【{category_name}】。"
|
||||
else:
|
||||
return f"分类【{category_name}】不存在。"
|
||||
|
||||
# 删除 list 中的音色
|
||||
def delete_from_list(audio_colors, category_name, name):
|
||||
if category_name in audio_colors:
|
||||
audio_colors[category_name]['list'] = [
|
||||
item for item in audio_colors[category_name]['list'] if item['name'] != name
|
||||
]
|
||||
save_json_file(JSON_FILE, audio_colors)
|
||||
return f"音色【{name}】已从分类【{category_name}】删除。"
|
||||
else:
|
||||
return f"分类【{category_name}】 不存在。"
|
||||
|
||||
# 检查音色 ID 是否已存在
|
||||
def vid_exists(audio_colors, vid):
|
||||
for category_data in audio_colors.values():
|
||||
for item in category_data['list']:
|
||||
if item['vid'] == vid:
|
||||
return True
|
||||
return False
|
||||
|
||||
def backup_json_file(file):
|
||||
# 获取当前时间
|
||||
backup_time = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
|
||||
# 创建备份目录
|
||||
backup_dir = 'backup'
|
||||
if not os.path.exists(backup_dir):
|
||||
os.makedirs(backup_dir)
|
||||
# 生成备份文件名
|
||||
backup_file = os.path.join(backup_dir, f"{os.path.basename(file)}_{backup_time}.json")
|
||||
# 复制文件到备份目录
|
||||
shutil.copyfile(file, backup_file)
|
||||
return f"备份成功,备份文件名为:{backup_dir}/{file}_{backup_time}.json"
|
||||
|
||||
# 导出 JSON 文件
|
||||
@app.route('/export')
|
||||
def export_json():
|
||||
if 'username' not in session:
|
||||
flash('请先登录!', 'warning')
|
||||
return redirect(url_for('index'))
|
||||
return send_from_directory(os.path.dirname(JSON_FILE), os.path.basename(JSON_FILE), as_attachment=True)
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('username', None)
|
||||
flash('已成功注销!', 'info')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('login.html', titleName=PKC_TITLE, PKC_VERSION=PKC_VERSION)
|
||||
|
||||
@app.route('/login', methods=['POST'])
|
||||
def login():
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
# users = load_users()
|
||||
users = [{"username": PKC_USER, "password": PKC_PASSWORD}]
|
||||
|
||||
# 验证用户
|
||||
for user in users:
|
||||
if user['username'] == username and user['password'] == password:
|
||||
session['username'] = username
|
||||
flash('登录成功!', 'success')
|
||||
return redirect(url_for('dashboard'))
|
||||
|
||||
flash('用户名或密码错误!', 'danger')
|
||||
return render_template('login.html', titleName=PKC_TITLE, PKC_VERSION=PKC_VERSION)
|
||||
|
||||
# 处理表单提交
|
||||
@app.route('/dashboard', methods=['POST', 'GET'])
|
||||
def dashboard():
|
||||
audio_colors = read_json_file(JSON_FILE)
|
||||
response = None
|
||||
curtabName = None
|
||||
if 'username' not in session:
|
||||
flash('请先登录!', 'warning')
|
||||
return redirect(url_for('index'))
|
||||
# if request.method == 'GET':
|
||||
# # 获取 URL 参数 y 的值
|
||||
# y = request.args.get('y', type=int)
|
||||
if request.method == 'POST':
|
||||
action = request.form.get('action')
|
||||
|
||||
# 添加分类
|
||||
if action == 'add_category':
|
||||
name = request.form.get('name')
|
||||
token = request.form.get('token')
|
||||
sort = request.form.get('sort')
|
||||
desc = request.form.get('desc')
|
||||
url = request.form.get('url')
|
||||
alias = request.form.get('alias')
|
||||
type = request.form.get('type')
|
||||
curtabName='addCategory'
|
||||
response = add_category(audio_colors, name, token, sort, desc, url, alias, type)
|
||||
|
||||
# 修改分类
|
||||
elif action == 'edit_category':
|
||||
old_name = request.form.get('old_name')
|
||||
new_name = request.form.get('new_name')
|
||||
token = request.form.get('token')
|
||||
sort = request.form.get('sort')
|
||||
desc = request.form.get('desc')
|
||||
url = request.form.get('url')
|
||||
alias = request.form.get('alias')
|
||||
type = request.form.get('type')
|
||||
response = edit_category(audio_colors, old_name, new_name, token, sort, desc, url, alias, type)
|
||||
curtabName='editCategory'
|
||||
|
||||
# 删除分类
|
||||
elif action == 'delete_category':
|
||||
name = request.form.get('name')
|
||||
response = delete_category(audio_colors, name)
|
||||
curtabName='deleteCategory'
|
||||
|
||||
# 添加到 list
|
||||
elif action == 'add_to_list':
|
||||
category_name = request.form.get('category_name')
|
||||
name = request.form.get('name')
|
||||
desc = request.form.get('desc')
|
||||
vid = request.form.get('vid')
|
||||
img = request.form.get('img')
|
||||
response = add_to_list(audio_colors, category_name, name, desc, vid, img)
|
||||
curtabName='addToList'
|
||||
|
||||
# 修改 list 中的音色
|
||||
elif action == 'edit_list_item':
|
||||
category_name = request.form.get('category_name')
|
||||
old_name = request.form.get('old_name')
|
||||
new_name = request.form.get('new_name')
|
||||
desc = request.form.get('desc')
|
||||
vid = request.form.get('vid')
|
||||
img = request.form.get('img')
|
||||
response = edit_list_item(audio_colors, category_name, old_name, new_name, desc, vid, img)
|
||||
curtabName='editListItem'
|
||||
|
||||
# 删除 list 中的音色
|
||||
elif action == 'delete_from_list':
|
||||
category_name = request.form.get('category_name')
|
||||
name = request.form.get('name')
|
||||
response = delete_from_list(audio_colors, category_name, name)
|
||||
curtabName='deleteFromList'
|
||||
# 备份
|
||||
elif action == 'backup':
|
||||
response = backup_json_file(JSON_FILE)
|
||||
curtabName='backup'
|
||||
# 导出
|
||||
elif action == 'export':
|
||||
# 重定向给 当前路由/export
|
||||
return redirect(url_for('export_json')) # 重定向到 export_json 路由
|
||||
# 导入
|
||||
elif action == 'import':
|
||||
if 'import_file' in request.files:
|
||||
file = request.files['import_file']
|
||||
if file.filename.endswith('.json'):
|
||||
backup_time = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
|
||||
backup_dir = 'backup'
|
||||
if not os.path.exists(backup_dir):
|
||||
os.makedirs(backup_dir)
|
||||
# 生成备份文件名
|
||||
backup_file = os.path.join(backup_dir, f"{os.path.basename(JSON_FILE)}_{backup_time}.json")
|
||||
os.rename(JSON_FILE, backup_file)
|
||||
file.save(JSON_FILE)
|
||||
response = f"导入成功,原文件已备份为:{backup_file}"
|
||||
else:
|
||||
response = "导入失败,请上传正确的 JSON 文件。"
|
||||
else:
|
||||
response = "导入失败,请上传文件。"
|
||||
curtabName='import'
|
||||
|
||||
# 读取所有音色
|
||||
audio_colors = read_json_file(JSON_FILE)
|
||||
# 获取第一个分类的名称和音色列表
|
||||
first_category_name = list(audio_colors.keys())[0] if audio_colors else None
|
||||
first_category_audio_colors = audio_colors.get(first_category_name, {}).get('list', [])
|
||||
return render_template('index.html',
|
||||
audio_colors=audio_colors,
|
||||
ysCount=len(audio_colors),
|
||||
first_category_name=first_category_name,
|
||||
first_category_audio_colors=first_category_audio_colors,
|
||||
curtabName=curtabName,
|
||||
titleName=PKC_TITLE,
|
||||
PKC_VERSION=PKC_VERSION,
|
||||
response=response) # 将 response 传递到模板
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
protValue = userConfig['端口']
|
||||
port = protValue if len(protValue) > 0 else "39900"
|
||||
app.run(host='0.0.0.0', port=port, debug=False)
|
||||
707
PKCYsManage/templates/index.html
Normal file
707
PKCYsManage/templates/index.html
Normal file
@ -0,0 +1,707 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ titleName }}{{ PKC_VERSION }}</title>
|
||||
<style>
|
||||
/* CSS 代码 */
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif; /* 使用现代字体 Roboto */
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #e0eafc, #cfdef3); /* 更柔和的渐变背景 */
|
||||
/*background: linear-gradient(120deg, #00ccff, #ff00ff);*/
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
display: flex; /* 使用 flexbox 布局 */
|
||||
flex-direction: column;
|
||||
align-items: center; /* 居中内容 */
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2.5rem; /* 更大的标题 */
|
||||
font-weight: 600; /* 更粗的标题 */
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 添加阴影 */
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
max-width: 500px; /* 限制表单宽度 */
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
input, select {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 2px solid #ccc;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
font-size: 16px;
|
||||
background-color: #f9f9f9; /* 添加更浅的背景色 */
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 0 8px rgba(52, 152, 219, 0.5);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 20px;
|
||||
background-color: #2ecc71;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, transform 0.3s;
|
||||
font-size: 16px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* 添加阴影 */
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #27ae60;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.2); /* 悬停时阴影更明显 */
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.tab {
|
||||
overflow: hidden;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
max-width: 100%; /* 限制 Tab 宽度 */
|
||||
margin-bottom: 20px; /* 添加 Tab 与内容之间的间隔 */
|
||||
}
|
||||
|
||||
.tab button {
|
||||
background-color: inherit;
|
||||
float: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
padding: 14px 16px;
|
||||
transition: 0.3s;
|
||||
font-size: 17px;
|
||||
color: #2c3e50;
|
||||
border-right: 1px solid #ccc;
|
||||
font-weight: bold; /* 加粗 Tab 标题 */
|
||||
}
|
||||
|
||||
.tab button:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.tab button:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.tab button.active {
|
||||
background-color: #2c3e50; /* 使用深蓝色作为激活状态 */
|
||||
color: #fff;
|
||||
}
|
||||
.tabcontent {
|
||||
display: block; /* 确保内容可见 */
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
/*background-color: #fff;*/
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
max-height: 500px; /* 设置最大高度 */
|
||||
overflow-y: auto; /* 允许垂直滚动 */
|
||||
width: 100%; /* 使宽度适应父容器 */
|
||||
box-sizing: border-box; /* 包括内边距和边框在宽度计算内 */
|
||||
}
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.custom-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#search_input_edit, #search_input_delete {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 2px solid #ccc;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
background-color: #f9f9f9; /* 添加更浅的背景色 */
|
||||
}
|
||||
|
||||
.dropdown-options {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
background-color: white;
|
||||
display: none;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 显示下拉框 */
|
||||
#search_input_edit:focus + .dropdown-options,
|
||||
#search_input_delete:focus + .dropdown-options {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#urlModal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
text-align: center;
|
||||
}
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>{{ titleName }} {{ PKC_VERSION }}</h2>
|
||||
<div class="tab">
|
||||
<button class="tablinks active" onclick="openTab(event, 'addCategory')">添加分类</button>
|
||||
<button class="tablinks" onclick="openTab(event, 'editCategory')">修改分类</button>
|
||||
<button class="tablinks" onclick="openTab(event, 'deleteCategory')">删除分类</button>
|
||||
<button class="tablinks" onclick="openTab(event, 'addToList')">添加音色</button>
|
||||
<button class="tablinks" onclick="openTab(event, 'editListItem')">修改音色</button>
|
||||
<button class="tablinks" onclick="openTab(event, 'deleteFromList')">删除音色</button>
|
||||
<button class="tablinks" onclick="openTab(event, 'backup')">备份</button>
|
||||
<button class="tablinks" onclick="openTab(event, 'export')">导出</button>
|
||||
<button class="tablinks" onclick="openTab(event, 'import')">导入</button>
|
||||
<button class="tablinks" onclick="getYsList()">音色接口</button>
|
||||
<button class="tablinks" onclick="loginout()">退出</button>
|
||||
</div>
|
||||
|
||||
<div id="addCategory" class="tabcontent" style="display: block;">
|
||||
<h3>添加分类</h3>
|
||||
<form method="POST" onsubmit="return validateForm('add_category','addCategory')">
|
||||
<input type="hidden" name="action" value="add_category">
|
||||
<div class="form-group">
|
||||
<label for="type">音色接口:</label>
|
||||
<select id="type" name="type">
|
||||
<option value="fish.audio" selected>fish.audio</option>
|
||||
<option value="FineVoice">FineVoice</option>
|
||||
<option value="琅琅">琅琅</option>
|
||||
<option value="讯飞">讯飞</option>
|
||||
<option value="acgn.ttson.cn">二次元(原神)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">*分类名称:</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="token">是否需要Token:</label>
|
||||
<select id="token" name="token">
|
||||
<option value="yes" selected>Yes</option>
|
||||
<option value="no">No</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sort">*排序:</label>
|
||||
<input type="number" id="sort" name="sort" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="url">URL:</label>
|
||||
<input type="text" id="url" name="url">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="alias">别名:</label>
|
||||
<input type="text" id="alias" name="alias">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="desc">描述:</label>
|
||||
<input type="text" id="desc" name="desc">
|
||||
</div>
|
||||
<button type="submit">添加分类</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="editCategory" class="tabcontent">
|
||||
<h3>修改分类</h3>
|
||||
<form method="POST" onsubmit="return validateForm('edit_category','editCategory')">
|
||||
<input type="hidden" name="action" value="edit_category">
|
||||
<div class="form-group">
|
||||
<label for="old_name">选择分类:</label>
|
||||
<select id="old_name" name="old_name" required onchange="fillCategoryDetails()" onkeyup="filterSelectOptions('old_name')">
|
||||
{% for category_name in audio_colors.keys() %}
|
||||
<option value="{{ category_name }}">{{ category_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="type">音色接口:</label>
|
||||
<select id="type" name="type">
|
||||
<option value="fish.audio">fish.audio</option>
|
||||
<option value="FineVoice">FineVoice</option>
|
||||
<option value="琅琅" selected>琅琅</option>
|
||||
<option value="讯飞">讯飞</option>
|
||||
<option value="acgn.ttson.cn">二次元(原神)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new_name">*新分类名称:</label>
|
||||
<input type="text" id="new_name" name="new_name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="token">是否需要Token:</label>
|
||||
<select id="token" name="token">
|
||||
<option value="yes" selected>Yes</option>
|
||||
<option value="no">No</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sort">*排序:</label>
|
||||
<input type="number" id="sort" name="sort" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="url">URL:</label>
|
||||
<input type="text" id="url" name="url">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="alias">别名:</label>
|
||||
<input type="text" id="alias" name="alias">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="desc">描述:</label>
|
||||
<input type="text" id="desc" name="desc">
|
||||
</div>
|
||||
<button type="submit">修改分类</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="deleteCategory" class="tabcontent">
|
||||
<h3>删除分类</h3>
|
||||
<form method="POST" onsubmit="return validateForm('delete_category','deleteCategory')">
|
||||
<input type="hidden" name="action" value="delete_category">
|
||||
<div class="form-group">
|
||||
<label for="name">选择分类:</label>
|
||||
<select id="name" name="name" required>
|
||||
{% for category_name in audio_colors.keys() %}
|
||||
<option value="{{ category_name }}">{{ category_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit">删除分类</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="addToList" class="tabcontent">
|
||||
<h3>添加音色到分类</h3>
|
||||
<form method="POST" onsubmit="return validateForm('add_to_list','addToList')">
|
||||
<input type="hidden" name="action" value="add_to_list">
|
||||
<div class="form-group">
|
||||
<label for="category_name_add">选择分类:</label>
|
||||
<select id="category_name_add" name="category_name" required>
|
||||
{% for category_name in audio_colors.keys() %}
|
||||
<option value="{{ category_name }}">{{ category_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">*音色名称:</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="desc">*描述:</label>
|
||||
<input type="text" id="desc" name="desc" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="vid">*音色 ID:</label>
|
||||
<input type="text" id="vid" name="vid" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="img">图片链接:</label>
|
||||
<input type="text" id="img" name="img">
|
||||
</div>
|
||||
<button type="submit">添加音色</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="editListItem" class="tabcontent">
|
||||
<h3>修改音色</h3>
|
||||
<form method="POST" onsubmit="return validateForm('edit_list_item','editListItem')">
|
||||
<input type="hidden" name="action" value="edit_list_item">
|
||||
<div class="form-group">
|
||||
<label for="category_name_edit">选择分类:</label>
|
||||
<select id="category_name_edit" name="category_name" required onchange="updateAudioColorsList('edit')">
|
||||
{% for category_name in audio_colors.keys() %}
|
||||
<option value="{{ category_name }}" {% if category_name == first_category_name %}selected{% endif %}>{{ category_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="old_name_edit">选择音色:</label>
|
||||
<div class="custom-dropdown">
|
||||
<input type="text" id="search_input_edit" placeholder="搜索音色(输入要搜索的内容然后点下面的下拉菜单)..." onkeyup="filterSelectOptions('edit')" autocomplete="off" />
|
||||
<select id="old_name_edit" name="old_name" onchange="fillAudioColorDetails('edit')">
|
||||
<!-- 音色选项将在这里动态生成 -->
|
||||
</select>
|
||||
<div id="dropdown_options_edit" class="dropdown-options">
|
||||
<!-- 音色选项将在这里动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new_name">新音色名称:</label>
|
||||
<input type="text" id="new_name" name="new_name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="desc">*描述:</label>
|
||||
<input type="text" id="desc" name="desc" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="vid">*音色 ID:</label>
|
||||
<input type="text" id="vid" name="vid" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="img">图片链接:</label>
|
||||
<input type="text" id="img" name="img">
|
||||
</div>
|
||||
<button type="submit">修改音色</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="deleteFromList" class="tabcontent">
|
||||
<h3>删除音色</h3>
|
||||
<form method="POST" onsubmit="return validateForm('old_name_delete','deleteFromList')">
|
||||
<input type="hidden" name="action" value="delete_from_list">
|
||||
<div class="form-group">
|
||||
<label for="category_name_delete">选择分类:</label>
|
||||
<select id="category_name_delete" name="category_name" required onchange="updateAudioColorsList('delete')">
|
||||
{% for category_name in audio_colors.keys() %}
|
||||
<option value="{{ category_name }}" {% if category_name == first_category_name %}selected{% endif %}>{{ category_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="old_name_delete">选择音色:</label>
|
||||
<div class="custom-dropdown">
|
||||
<input type="text" id="search_input_delete" placeholder="搜索音色(输入要搜索的内容然后点下面的下拉菜单)..." onkeyup="filterSelectOptions('delete')" autocomplete="off" />
|
||||
<select id="old_name_delete" name="name" onchange="fillAudioColorDetailsForDelete('delete')">
|
||||
<!-- 音色选项将在这里动态生成 -->
|
||||
</select>
|
||||
<div id="dropdown_options_delete" class="dropdown-options">
|
||||
<!-- 音色选项将在这里动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit">删除音色</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="backup" class="tabcontent">
|
||||
<h3>备份</h3>
|
||||
<form method="POST" onsubmit="return validateForm('backup','backup')">
|
||||
<input type="hidden" name="action" value="backup">
|
||||
<button type="submit">备份</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="export" class="tabcontent">
|
||||
<h3>导出</h3>
|
||||
<form method="POST" onsubmit="return validateForm('export','export')">
|
||||
<input type="hidden" name="action" value="export">
|
||||
<button type="submit">导出</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="import" class="tabcontent">
|
||||
<h3>导入</h3>
|
||||
<form method="POST" enctype="multipart/form-data" onsubmit="return validateForm('import','import')">
|
||||
<input type="hidden" name="action" value="import">
|
||||
<div class="form-group">
|
||||
<label for="import_file">选择 PKC音色JSON文件:</label>
|
||||
<input type="file" id="import_file" name="import_file" accept=".json" required>
|
||||
</div>
|
||||
<button type="submit">导入</button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="urlModal" style="display:none;">
|
||||
<div class="modal-content">
|
||||
<p id="urlText"></p>
|
||||
<button id="copyButton">复制</button>
|
||||
<button id="openButton">访问</button>
|
||||
<button id="cancelButton">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
function showResponseMessage(message) {
|
||||
// 导航到新URL
|
||||
alert(message); // 使用 alert() 函数显示弹窗提示
|
||||
var currentUrl = window.location.href;
|
||||
window.location.href = currentUrl;
|
||||
}
|
||||
function openTab(evt, tabName) {
|
||||
var i, tabcontent, tablinks;
|
||||
tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].style.display = "none";
|
||||
}
|
||||
tablinks = document.getElementsByClassName("tablinks");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||||
}
|
||||
document.getElementById(tabName).style.display = "block";
|
||||
evt.currentTarget.className += " active";
|
||||
}
|
||||
|
||||
function updateAudioColorsList(action) {
|
||||
var categorySelect = document.getElementById("category_name_" + action);
|
||||
var selectedCategory = categorySelect.value;
|
||||
|
||||
// 获取对应分类的音色列表
|
||||
var audioColors = {{ audio_colors | tojson | safe }}; // 使用 JSON.stringify 转换
|
||||
var categoryData = audioColors[selectedCategory];
|
||||
var dropdownOptions = document.getElementById('dropdown_options_' + action);
|
||||
dropdownOptions.innerHTML = ''; // 清空下拉选项
|
||||
var oldNameSelect = document.getElementById('old_name_' + action);
|
||||
oldNameSelect.innerHTML = ''; // 清空下拉选项
|
||||
if (categoryData && categoryData['list']) {
|
||||
categoryData['list'].forEach(function(item) {
|
||||
var dropdownItem = document.createElement('div');
|
||||
dropdownItem.classList.add('dropdown-item');
|
||||
dropdownItem.textContent = item['name'] + ' | ' + item['desc'];
|
||||
dropdownItem.dataset.desc = item['desc'] + '|' + item['img'] + '|' + item['vid'];
|
||||
dropdownItem.addEventListener('click', function() {
|
||||
document.getElementById('search_input_' + action).value = this.textContent;
|
||||
if (action === 'edit') {
|
||||
fillAudioColorDetails(action);
|
||||
} else if (action === 'delete') {
|
||||
fillAudioColorDetailsForDelete('delete');
|
||||
}
|
||||
});
|
||||
dropdownOptions.appendChild(dropdownItem);
|
||||
|
||||
var option = document.createElement('option');
|
||||
option.value = item['name'];
|
||||
option.textContent = item['name'] + ' | ' + item['desc'];
|
||||
option.dataset.desc = item['desc'] + '|' + item['img'] + '|' + item['vid'];
|
||||
oldNameSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function fillCategoryDetails() {
|
||||
var selectedCategory = document.getElementById('old_name').value;
|
||||
var categoryData = {{ audio_colors | tojson | safe }};
|
||||
document.getElementById('editCategory').querySelector('#new_name').value = selectedCategory;
|
||||
document.getElementById('editCategory').querySelector('#desc').value = categoryData[selectedCategory]['desc'];
|
||||
document.getElementById('editCategory').querySelector('#token').value = categoryData[selectedCategory]['token'];
|
||||
document.getElementById('editCategory').querySelector('#sort').value = categoryData[selectedCategory]['sort']; // 填充 sort 值
|
||||
document.getElementById('editCategory').querySelector('#url').value = categoryData[selectedCategory]['url']; // 填充 sort 值
|
||||
document.getElementById('editCategory').querySelector('#type').value = categoryData[selectedCategory]['type']; // 填充 sort 值
|
||||
document.getElementById('editCategory').querySelector('#alias').value = categoryData[selectedCategory]['alias']; // 填充 sort 值
|
||||
}
|
||||
function fillCategoryAdd() {
|
||||
document.getElementById('addCategory').querySelector('#sort').value = {{ ysCount }};
|
||||
}
|
||||
|
||||
function fillAudioColorDetails(action) {
|
||||
var selectedItemText = document.getElementById('old_name_edit').value;
|
||||
var descAndImgAndVid = document.getElementById('old_name_edit').querySelector('option[value="' + selectedItemText + '"]').dataset.desc.split('|');
|
||||
document.getElementById('editListItem').querySelector('#new_name').value = selectedItemText;
|
||||
document.getElementById('editListItem').querySelector('#desc').value = descAndImgAndVid[0];
|
||||
document.getElementById('editListItem').querySelector('#img').value = descAndImgAndVid[1];
|
||||
document.getElementById('editListItem').querySelector('#vid').value = descAndImgAndVid[2];
|
||||
}
|
||||
|
||||
function fillAudioColorDetailsForDelete(action) {
|
||||
var selectedItemText = document.getElementById('old_name_delete').value;
|
||||
var descAndImgAndVid = document.getElementById('old_name_delete').querySelector('option[value="' + selectedItemText + '"]').dataset.desc.split('|');
|
||||
document.getElementById('deleteFromList').querySelector('#old_name_delete').value = selectedItemText;
|
||||
// document.getElementById('deleteFromList').querySelector('#desc').value = descAndImgAndVid[0]; // 填充描述
|
||||
// document.getElementById('deleteFromList').querySelector('#img').value = descAndImgAndVid[1]; // 填充图片链接
|
||||
// document.getElementById('deleteFromList').querySelector('#vid').value = descAndImgAndVid[2]; // 填充音色 ID
|
||||
}
|
||||
|
||||
// 页面加载时,初始化修改和删除音色的音色列表
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updateAudioColorsList('edit');
|
||||
updateAudioColorsList('delete');
|
||||
fillCategoryAdd();
|
||||
var response = '{{ response }}'; // 获取 response 变量的值
|
||||
if (response && response !== 'None') {
|
||||
showResponseMessage(response);
|
||||
}
|
||||
let cacheValue = localStorage.getItem('curtabName');
|
||||
if (cacheValue && cacheValue.length >1){
|
||||
openTab(event, cacheValue)
|
||||
}
|
||||
});
|
||||
|
||||
function filterSelectOptions(action) {
|
||||
var input = document.getElementById('search_input_' + action);
|
||||
const select = document.getElementById('old_name_' + action);
|
||||
var filter = input.value.toLocaleUpperCase('en-US');
|
||||
var options = document.getElementById('old_name_' + action).getElementsByTagName('option');
|
||||
var isINList = 0;
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var optionText = options[i].textContent;
|
||||
if (optionText.toLocaleUpperCase('en-US').indexOf(filter) > -1) {
|
||||
options[i].style.display = "";
|
||||
isINList++;
|
||||
} else {
|
||||
options[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
// 设置下拉框大小
|
||||
select.size = isINList > 0 ? Math.min(optionText.length, 5) : 1;
|
||||
// 如果没有匹配项,清空选择
|
||||
if (isINList === 0 && filter.length > 0) {
|
||||
select.selectedIndex = -1; // 清空选择
|
||||
}
|
||||
}
|
||||
|
||||
// 为搜索框添加 onkeyup 事件
|
||||
document.getElementById('search_input_edit').addEventListener('keyup', function() {
|
||||
filterSelectOptions('edit');
|
||||
});
|
||||
|
||||
document.getElementById('search_input_delete').addEventListener('keyup', function() {
|
||||
filterSelectOptions('delete');
|
||||
});
|
||||
|
||||
// 选择项点击事件
|
||||
document.getElementById('dropdown_options_edit').addEventListener('click', function(event) {
|
||||
var selectedItem = event.target;
|
||||
if (selectedItem.classList.contains('dropdown-item')) {
|
||||
document.getElementById('search_input_edit').value = selectedItem.textContent; // 设置输入框的值为选中的项
|
||||
fillAudioColorDetails('edit');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('dropdown_options_delete').addEventListener('click', function(event) {
|
||||
var selectedItem = event.target;
|
||||
if (selectedItem.classList.contains('dropdown-item')) {
|
||||
document.getElementById('search_input_delete').value = selectedItem.textContent; // 设置输入框的值为选中的项
|
||||
fillAudioColorDetailsForDelete('delete');
|
||||
}
|
||||
});
|
||||
function loginout() {
|
||||
var currentUrl = window.location.origin + '/logout';
|
||||
// 提示用户确认登出
|
||||
var confirmation = confirm("您确定要登出吗?");
|
||||
// 如果用户确认登出,则跳转到登出页面
|
||||
if (confirmation) {
|
||||
window.location.href = currentUrl;
|
||||
}
|
||||
// else {
|
||||
// // 用户选择取消,可以选择在此处添加其他逻辑
|
||||
// console.log("用户取消了登出操作");
|
||||
// }
|
||||
}
|
||||
|
||||
function getYsList() {
|
||||
{#var domainName = '{{ domainName }}';#}
|
||||
{#var mainUrl = domainName.length > 0 ? window.location.protocol+'//'+domainName+':'+window.location.port:window.location.origin;#}
|
||||
var currentUrl = window.location.origin + '/ysList';
|
||||
document.getElementById('urlText').textContent = currentUrl;
|
||||
document.getElementById('urlModal').style.display = 'flex';
|
||||
|
||||
document.getElementById('copyButton').onclick = function() {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(currentUrl).then(() => {
|
||||
alert('链接已复制到剪贴板!');
|
||||
}).catch(err => {
|
||||
alert('复制链接失败:' + err);
|
||||
});
|
||||
} else {
|
||||
// 备选方案
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = currentUrl;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
alert('链接已复制到剪贴板!');
|
||||
} catch (err) {
|
||||
alert('复制链接失败:' + err);
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('openButton').onclick = function() {
|
||||
window.open(currentUrl, '_blank');
|
||||
};
|
||||
|
||||
document.getElementById('cancelButton').onclick = function() {
|
||||
document.getElementById('urlModal').style.display = 'none';
|
||||
};
|
||||
}
|
||||
|
||||
function validateForm(a,b) {
|
||||
localStorage.setItem('curtabName', b);
|
||||
if (a === 'old_name_delete'){
|
||||
var select = document.getElementById(a);
|
||||
if (select.selectedIndex === -1 || select.value === "") {
|
||||
alert("未选中");
|
||||
return false; // 阻止表单提交
|
||||
}
|
||||
return true; // 允许表单提交
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
123
PKCYsManage/templates/login.html
Normal file
123
PKCYsManage/templates/login.html
Normal file
@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ titleName }}{{ PKC_VERSION }}</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Arial', sans-serif;
|
||||
background-color: #0c0c0c;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
background: rgba(20, 20, 20, 0.9);
|
||||
border-radius: 10px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
width: 400px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-container h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 2rem;
|
||||
color: #00ccff;
|
||||
}
|
||||
|
||||
.login-container input {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
margin: 0.5rem 0;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: #222;
|
||||
color: #ffffff;
|
||||
font-size: 1rem;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.login-container input:focus {
|
||||
background: #333;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.login-container button {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
background: #00ccff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1.2rem;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.login-container button:hover {
|
||||
background: #0099cc;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: #ff0000;
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.background-animation {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
background: linear-gradient(120deg, #00ccff, #ff00ff);
|
||||
animation: gradient 6s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="background-animation"></div>
|
||||
<div class="login-container">
|
||||
<h1>{{ titleName }}</h1>
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="message">
|
||||
{% for category, message in messages %}
|
||||
{% if category != 'success' %}
|
||||
<p class="{{ category }}">{{ message }}</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<form method="POST" action="/login">
|
||||
<input type="text" id="username" name="username" placeholder="用户名" required>
|
||||
<input type="password" id="password" name="password" placeholder="密码" required>
|
||||
<button type="submit">登录</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
28904
PKCYsManage/ys.json
Normal file
28904
PKCYsManage/ys.json
Normal file
File diff suppressed because it is too large
Load Diff
103
README.md
103
README.md
@ -1,2 +1,101 @@
|
||||
# pkc-ys
|
||||
PKC音色管理后台
|
||||
# PKC音色管理后台
|
||||
```
|
||||
仅用于PKC音色管理,可自定义音色接口地址
|
||||
```
|
||||
## 一、启动项目
|
||||
|
||||
## 1. 使用 Python 启动
|
||||
|
||||
首先,确保你已安装 Python 和相关依赖项。可以使用 `pip` 安装所需的库:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
然后,使用以下命令启动应用:
|
||||
```bash
|
||||
# 进入主目录
|
||||
cd PKCYsManage
|
||||
# 启动项目
|
||||
nohup python3 main.py &
|
||||
# 查看日志
|
||||
tail -f nohup.out
|
||||
```
|
||||
|
||||
## 2. 使用 Docker 启动
|
||||
确保你已安装 Docker。使用以下命令构建镜像并运行容器:
|
||||
```bash
|
||||
docker run -d -p 39900:39900 -e PKC_USER=pkc -e PKC_PASSWORD=pkc --name pkc-ys curtinlv/pkc-ys
|
||||
```
|
||||
|
||||
## 3. 使用 Docker Compose 启动
|
||||
确保你已安装 Docker Compose。创建一个 `docker-compose.yml` 文件并填入以下内容:
|
||||
|
||||
```yaml
|
||||
version: '3.3'
|
||||
|
||||
services:
|
||||
pkc-ys:
|
||||
image: curtinlv/pkc-ys
|
||||
container_name: pkc-ys
|
||||
ports:
|
||||
- "39900:39900"
|
||||
environment:
|
||||
- PKC_TITLE=PKC音色管理系统 # 系统名称
|
||||
- PKC_USER=pkc # 用户名
|
||||
- PKC_PASSWORD=pkc # 密码,如需带特殊字符用.env引入
|
||||
volumes:
|
||||
- ./backup:/app/backup # 音色备份目录
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
restart: unless-stopped
|
||||
|
||||
```
|
||||
然后,在包含 docker-compose.yml 的目录下运行以下命令启动服务:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 二、访问地址
|
||||
音色管理后台访问地址:
|
||||
```http request
|
||||
http://ip:39900
|
||||
```
|
||||
音色列表接口地址:
|
||||
```http request
|
||||
http://ip:39900/ysList
|
||||
```
|
||||
|
||||
## 三、常用 Docker 命令
|
||||
以下是一些常用的 Docker 命令,用于管理容器和查看日志等:
|
||||
- 查看运行中的容器:
|
||||
```bash
|
||||
docker ps
|
||||
```
|
||||
- 更新容器,或修改`docker-compose.yml`配置需执行生效:
|
||||
```bash
|
||||
docker up -d
|
||||
```
|
||||
- 查看所有容器(包括停止的):
|
||||
```bash
|
||||
docker ps -a
|
||||
```
|
||||
- 查看容器日志:
|
||||
```bash
|
||||
docker logs -f pkc-ys
|
||||
```
|
||||
- 重启容器:
|
||||
```bash
|
||||
docker restart pkc-ys
|
||||
```
|
||||
- 停止容器:
|
||||
```bash
|
||||
docker stop pkc-ys
|
||||
```
|
||||
|
||||
- 启动已停止的容器:
|
||||
```bash
|
||||
docker start pkc-ys
|
||||
```
|
||||
- 删除容器:
|
||||
```bash
|
||||
docker rm pkc-ys
|
||||
```
|
||||
16
docker-compose.yml
Normal file
16
docker-compose.yml
Normal file
@ -0,0 +1,16 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
pkc-ys:
|
||||
image: curtinlv/pkc-ys
|
||||
container_name: pkc-ys
|
||||
ports:
|
||||
- "39900:39900"
|
||||
environment:
|
||||
- PKC_TITLE=PKC音色管理系统 # 系统名称
|
||||
- PKC_USER=Curtin # 用户名
|
||||
- PKC_PASSWORD=${PKC_PASSWORD} # 密码
|
||||
volumes:
|
||||
# - ./ys.json:/app/ys.json # 音色文件
|
||||
- ./backup:/app/backup # 音色备份目录
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
restart: unless-stopped
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Flask==2.3.2
|
||||
Werkzeug==2.3.6
|
||||
Loading…
Reference in New Issue
Block a user