first commit
8
.idea/AIDaohang.iml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
6
.idea/misc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PyCharmProfessionalAdvertiser">
|
||||
<option name="shown" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$APPLICATION_HOME_DIR$/jbr/bin/AIDaohang/.idea/AIDaohang.iml" filepath="$APPLICATION_HOME_DIR$/jbr/bin/AIDaohang/.idea/AIDaohang.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
33
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="bf6d7af9-34d9-40cc-9dc9-04c6564b86b3" name="变更" comment="" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="ProjectId" id="2zOBX2NNI8egQP5wk1TUn81oaXa" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="默认任务">
|
||||
<changelist id="bf6d7af9-34d9-40cc-9dc9-04c6564b86b3" name="变更" comment="" />
|
||||
<created>1751592275331</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1751592275331</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
</project>
|
||||
22
README.md
Normal file
@ -0,0 +1,22 @@
|
||||
## 待办
|
||||
1、首页标题支持自定义 - 已完成
|
||||
2、每行卡片数量支持自定义,4个、5个、6个、8个
|
||||
3、背景图片设置支持从已上传图片中选择,选择的时候提供预览 - 已完成
|
||||
4、应用的图标也支持上传图片自定义;
|
||||
5、增加附件管理页面,管理上传的图片,包括应用图标图片和背景图片 - 已完成
|
||||
6、首页增加页脚;
|
||||
7、首页悬浮气泡内的文字改为居左 - 已完成
|
||||
8、一级分类和二级分类固定宽度,超出宽度左右滚轮滑动查看;
|
||||
9、背景支持视频 - 已完成
|
||||
10、应用管理页支持分页、支持选择一级分类来筛选;
|
||||
11、修复密码明文传输 - 已完成
|
||||
12、不登录时一级分类和二级分类按钮没有颜色 - 已完成
|
||||
13、部分页面游客跳转登录 - 已完成
|
||||
14、私有应用在首页也要有标识;
|
||||
15、气泡的小箭头靠左;
|
||||
16、logo图标的设置也区分明亮和暗黑;
|
||||
17、应用分页、附件分页;
|
||||
18、应用批量选择,批量删除,批量私有化、公有化
|
||||
19、附件批量选择、批量删除
|
||||
20、网站图标自动获取
|
||||
21、书签收藏工具
|
||||
834
app.py
Normal file
@ -0,0 +1,834 @@
|
||||
import os
|
||||
import json
|
||||
import datetime
|
||||
import random
|
||||
import uuid
|
||||
from flask import Flask, render_template, request, redirect, url_for, jsonify, session, make_response, flash, send_from_directory
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from io import BytesIO
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from functools import wraps
|
||||
from werkzeug.utils import secure_filename
|
||||
import requests
|
||||
from urllib.parse import urlparse
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'your_secret_key_here' # 请更改为安全的密钥
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
DATA_DIR = os.path.join(BASE_DIR, 'data')
|
||||
|
||||
# 确保数据目录存在
|
||||
os.makedirs(DATA_DIR, exist_ok=True)
|
||||
|
||||
UPLOAD_FOLDER = os.path.join(BASE_DIR, 'upload')
|
||||
LOGO_FOLDER = os.path.join(UPLOAD_FOLDER, 'logo')
|
||||
BACKGROUND_FOLDER = os.path.join(UPLOAD_FOLDER, 'background')
|
||||
os.makedirs(LOGO_FOLDER, exist_ok=True)
|
||||
os.makedirs(BACKGROUND_FOLDER, exist_ok=True)
|
||||
|
||||
VIDEO_FOLDER = os.path.join(UPLOAD_FOLDER, 'video')
|
||||
os.makedirs(VIDEO_FOLDER, exist_ok=True)
|
||||
|
||||
# 允许的文件扩展名
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'ico', 'svg', 'mp4'}
|
||||
|
||||
ATTACHMENTS_FILE = os.path.join(DATA_DIR, 'attachments.json')
|
||||
|
||||
# 系统设置文件路径
|
||||
SETTINGS_FILE = os.path.join(DATA_DIR, 'settings.json')
|
||||
|
||||
|
||||
def migrate_settings(settings):
|
||||
"""迁移旧版设置到新版格式"""
|
||||
if 'admin_password' in settings:
|
||||
# 如果存在旧版明文密码,转换为哈希
|
||||
settings['admin_password_hash'] = generate_password_hash(settings['admin_password'])
|
||||
del settings['admin_password']
|
||||
elif 'admin_password_hash' not in settings:
|
||||
# 如果既没有旧版密码也没有哈希,生成默认密码哈希
|
||||
settings['admin_password_hash'] = generate_password_hash('123456')
|
||||
return settings
|
||||
|
||||
|
||||
def init_settings():
|
||||
"""初始化或迁移设置文件"""
|
||||
if not os.path.exists(SETTINGS_FILE):
|
||||
# 全新安装的默认设置
|
||||
default_settings = {
|
||||
"theme": "auto",
|
||||
"card_style": "normal",
|
||||
"bg_image": "none",
|
||||
"dark_bg_image": "none",
|
||||
"admin_password_hash": generate_password_hash("123456"),
|
||||
"site_title": "应用导航中心",
|
||||
"show_logo": True,
|
||||
"logo_type": "icon",
|
||||
"logo_icon": "fa-th-list",
|
||||
"logo_image": "",
|
||||
"uploaded_backgrounds": [],
|
||||
"uploaded_logos": []
|
||||
}
|
||||
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_settings, f, ensure_ascii=False, indent=2)
|
||||
else:
|
||||
# 已有设置文件,检查是否需要迁移
|
||||
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
|
||||
settings = json.load(f)
|
||||
|
||||
if 'admin_password_hash' not in settings:
|
||||
# 需要迁移
|
||||
settings = migrate_settings(settings)
|
||||
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(settings, f, ensure_ascii=False, indent=2)
|
||||
|
||||
init_settings()
|
||||
|
||||
|
||||
def load_settings():
|
||||
"""加载设置并确保包含密码哈希"""
|
||||
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
|
||||
settings = json.load(f)
|
||||
|
||||
# 双重检查,确保始终有密码哈希
|
||||
if 'admin_password_hash' not in settings:
|
||||
settings = migrate_settings(settings)
|
||||
save_settings(settings)
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def save_settings(data):
|
||||
"""保存设置,确保不存储明文密码"""
|
||||
if 'admin_password' in data:
|
||||
del data['admin_password']
|
||||
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
def load_apps():
|
||||
with open(os.path.join(DATA_DIR, 'apps-data.json'), 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_apps(data):
|
||||
with open(os.path.join(DATA_DIR, 'apps-data.json'), 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
def load_categories():
|
||||
with open(os.path.join(DATA_DIR, 'categories.json'), 'r', encoding='utf-8') as f:
|
||||
categories = json.load(f)
|
||||
# 确保每个子分类都有weight属性
|
||||
for main_cat in categories.values():
|
||||
if 'sub' in main_cat:
|
||||
for sub_cat in main_cat['sub'].values():
|
||||
if 'weight' not in sub_cat:
|
||||
sub_cat['weight'] = 0
|
||||
if 'weight' not in main_cat:
|
||||
main_cat['weight'] = 0
|
||||
return categories
|
||||
|
||||
def load_icons():
|
||||
with open(os.path.join(BASE_DIR, 'data', 'icons.json'), 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_categories(data):
|
||||
with open(os.path.join(DATA_DIR, 'categories.json'), 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
def login_required(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if 'username' not in session:
|
||||
flash('请先登录', 'danger')
|
||||
return redirect(url_for('login', next=request.url))
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
def init_attachments():
|
||||
if not os.path.exists(ATTACHMENTS_FILE):
|
||||
with open(ATTACHMENTS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump([], f, ensure_ascii=False, indent=2)
|
||||
|
||||
init_attachments()
|
||||
|
||||
def load_attachments():
|
||||
with open(ATTACHMENTS_FILE, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_attachments(data):
|
||||
with open(ATTACHMENTS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
def add_attachment(filename, file_type):
|
||||
attachments = load_attachments()
|
||||
attachments.append({
|
||||
'filename': filename,
|
||||
'type': file_type,
|
||||
'upload_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
save_attachments(attachments)
|
||||
|
||||
|
||||
def delete_attachment(filename):
|
||||
attachments = load_attachments()
|
||||
attachments = [a for a in attachments if a['filename'] != filename]
|
||||
save_attachments(attachments)
|
||||
|
||||
# 从settings中移除引用
|
||||
settings = load_settings()
|
||||
if settings.get('bg_image', '').endswith(filename):
|
||||
settings['bg_image'] = ""
|
||||
if settings.get('dark_bg_image', '').endswith(filename):
|
||||
settings['dark_bg_image'] = ""
|
||||
if settings.get('logo_image', '').endswith(filename):
|
||||
settings['logo_image'] = ""
|
||||
save_settings(settings)
|
||||
|
||||
def allowed_file(filename):
|
||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
|
||||
def generate_random_filename(extension):
|
||||
return f"{uuid.uuid4().hex}.{extension}"
|
||||
|
||||
|
||||
def download_image(url, folder):
|
||||
try:
|
||||
response = requests.get(url, stream=True)
|
||||
if response.status_code == 200:
|
||||
# 获取文件扩展名
|
||||
content_type = response.headers.get('content-type', '')
|
||||
if 'image/jpeg' in content_type:
|
||||
ext = 'jpg'
|
||||
elif 'image/png' in content_type:
|
||||
ext = 'png'
|
||||
elif 'image/gif' in content_type:
|
||||
ext = 'gif'
|
||||
else:
|
||||
ext = 'jpg' # 默认扩展名
|
||||
|
||||
filename = generate_random_filename(ext)
|
||||
filepath = os.path.join(folder, filename)
|
||||
|
||||
with open(filepath, 'wb') as f:
|
||||
for chunk in response.iter_content(1024):
|
||||
f.write(chunk)
|
||||
|
||||
return filename
|
||||
except Exception as e:
|
||||
print(f"Error downloading image: {e}")
|
||||
return None
|
||||
|
||||
@app.route('/api/check_login')
|
||||
def check_login():
|
||||
return jsonify({'logged_in': 'username' in session})
|
||||
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
captcha = request.form['captcha']
|
||||
|
||||
if 'captcha' not in session or captcha.lower() != session['captcha'].lower():
|
||||
flash('验证码错误', 'danger')
|
||||
return render_template('login.html')
|
||||
|
||||
settings = load_settings()
|
||||
if username == 'admin' and 'admin_password_hash' in settings:
|
||||
if check_password_hash(settings['admin_password_hash'], password):
|
||||
session['username'] = username
|
||||
next_url = request.args.get('next', url_for('index'))
|
||||
flash('登录成功', 'success')
|
||||
return redirect(next_url)
|
||||
|
||||
flash('用户名或密码错误', 'danger')
|
||||
return render_template('login.html')
|
||||
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('username', None)
|
||||
return redirect(url_for('navigation'))
|
||||
|
||||
@app.route('/captcha')
|
||||
def captcha():
|
||||
# 生成随机4位验证码
|
||||
captcha_text = ''.join(random.choices('abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789', k=4))
|
||||
session['captcha'] = captcha_text
|
||||
|
||||
# 创建图片
|
||||
image = Image.new('RGB', (120, 40), color=(255, 255, 255))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
# 使用默认字体或指定字体路径
|
||||
try:
|
||||
font = ImageFont.truetype("arial.ttf", 24)
|
||||
except:
|
||||
font = ImageFont.load_default()
|
||||
|
||||
# 绘制验证码文本
|
||||
draw.text((10, 5), captcha_text, font=font, fill=(0, 0, 0))
|
||||
|
||||
# 添加干扰线
|
||||
for i in range(5):
|
||||
x1 = random.randint(0, 120)
|
||||
y1 = random.randint(0, 40)
|
||||
x2 = random.randint(0, 120)
|
||||
y2 = random.randint(0, 40)
|
||||
draw.line((x1, y1, x2, y2), fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
|
||||
|
||||
# 保存图片到内存
|
||||
img_io = BytesIO()
|
||||
image.save(img_io, 'PNG')
|
||||
img_io.seek(0)
|
||||
|
||||
response = make_response(img_io.getvalue())
|
||||
response.headers['Content-Type'] = 'image/png'
|
||||
return response
|
||||
|
||||
|
||||
@app.route('/settings', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def system_settings():
|
||||
settings = load_settings()
|
||||
attachments = load_attachments()
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
# 更新基本设置
|
||||
settings['theme'] = request.form.get('theme', 'auto')
|
||||
settings['card_style'] = request.form.get('card_style', 'normal')
|
||||
settings['site_title'] = request.form.get('site_title', '应用导航中心')
|
||||
settings['show_logo'] = 'show_logo' in request.form
|
||||
settings['logo_type'] = request.form.get('logo_type', 'icon')
|
||||
|
||||
# 处理logo设置
|
||||
if settings['logo_type'] == 'icon':
|
||||
settings['logo_icon'] = request.form.get('logo_icon', 'fa-th-list')
|
||||
else:
|
||||
logo_image_type = request.form.get('logo_image_type')
|
||||
if logo_image_type == 'existing':
|
||||
existing_logo = request.form.get('selected_logo')
|
||||
if existing_logo:
|
||||
settings['logo_image'] = f"/upload/logo/{existing_logo}"
|
||||
elif logo_image_type == 'none':
|
||||
settings['logo_image'] = ""
|
||||
|
||||
# 处理明亮模式背景
|
||||
bg_type = request.form.get('bg_type')
|
||||
if bg_type == 'existing':
|
||||
existing_bg = request.form.get('selected_bg')
|
||||
if existing_bg:
|
||||
settings['bg_image'] = f"/upload/background/{existing_bg}"
|
||||
else:
|
||||
settings['bg_image'] = "none"
|
||||
elif bg_type == 'video':
|
||||
existing_video = request.form.get('selected_video')
|
||||
if existing_video:
|
||||
settings['bg_image'] = f"/upload/video/{existing_video}"
|
||||
else:
|
||||
settings['bg_image'] = "none"
|
||||
elif bg_type == 'default':
|
||||
settings['bg_image'] = "/static/background_light.jpg"
|
||||
elif bg_type == 'none':
|
||||
settings['bg_image'] = "none"
|
||||
|
||||
# 处理暗黑模式背景
|
||||
dark_bg_type = request.form.get('dark_bg_type')
|
||||
if dark_bg_type == 'existing':
|
||||
existing_dark_bg = request.form.get('selected_dark_bg')
|
||||
if existing_dark_bg:
|
||||
settings['dark_bg_image'] = f"/upload/background/{existing_dark_bg}"
|
||||
else:
|
||||
settings['dark_bg_image'] = "none"
|
||||
elif dark_bg_type == 'video':
|
||||
existing_dark_video = request.form.get('selected_dark_video')
|
||||
if existing_dark_video:
|
||||
settings['dark_bg_image'] = f"/upload/video/{existing_dark_video}"
|
||||
else:
|
||||
settings['dark_bg_image'] = "none"
|
||||
elif dark_bg_type == 'default':
|
||||
settings['dark_bg_image'] = "/static/background_dark.jpg"
|
||||
elif dark_bg_type == 'none':
|
||||
settings['dark_bg_image'] = "none"
|
||||
|
||||
# 检查是否修改密码
|
||||
old_password = request.form.get('old_password')
|
||||
new_password = request.form.get('new_password')
|
||||
confirm_password = request.form.get('confirm_password')
|
||||
|
||||
if old_password and new_password and confirm_password:
|
||||
if not check_password_hash(settings.get('admin_password_hash', ''), old_password):
|
||||
flash('原密码错误', 'danger')
|
||||
return render_template('settings.html', settings=settings, icons=load_icons(), attachments=attachments)
|
||||
if new_password != confirm_password:
|
||||
flash('新密码不匹配', 'danger')
|
||||
return render_template('settings.html', settings=settings, icons=load_icons(), attachments=attachments)
|
||||
settings['admin_password_hash'] = generate_password_hash(new_password)
|
||||
|
||||
save_settings(settings)
|
||||
flash('设置已保存', 'success')
|
||||
return redirect(url_for('system_settings'))
|
||||
|
||||
except Exception as e:
|
||||
print(f"保存设置时出错: {e}")
|
||||
flash('保存设置时出错', 'danger')
|
||||
return render_template('settings.html', settings=settings, icons=load_icons(), attachments=attachments)
|
||||
|
||||
# 从返回的settings中移除密码哈希
|
||||
settings_to_return = settings.copy()
|
||||
settings_to_return.pop('admin_password_hash', None)
|
||||
return render_template('settings.html', settings=settings_to_return, icons=load_icons(), attachments=attachments)
|
||||
|
||||
|
||||
# 添加附件管理路由
|
||||
@app.route('/attachments')
|
||||
@login_required
|
||||
def manage_attachments():
|
||||
settings = load_settings()
|
||||
attachments = load_attachments()
|
||||
return render_template('attachments.html', settings=settings, attachments=attachments)
|
||||
|
||||
|
||||
@app.route('/upload_attachment', methods=['POST'])
|
||||
@login_required
|
||||
def upload_attachment():
|
||||
if 'file' not in request.files:
|
||||
flash('没有选择文件', 'danger')
|
||||
return redirect(url_for('manage_attachments'))
|
||||
|
||||
file = request.files['file']
|
||||
file_type = request.form.get('type')
|
||||
|
||||
if file.filename == '':
|
||||
flash('没有选择文件', 'danger')
|
||||
return redirect(url_for('manage_attachments'))
|
||||
|
||||
if file and allowed_file(file.filename):
|
||||
ext = file.filename.rsplit('.', 1)[1].lower()
|
||||
filename = generate_random_filename(ext)
|
||||
|
||||
if file_type == 'logo':
|
||||
folder = LOGO_FOLDER
|
||||
elif file_type == 'background':
|
||||
folder = BACKGROUND_FOLDER
|
||||
else: # video
|
||||
folder = VIDEO_FOLDER
|
||||
|
||||
filepath = os.path.join(folder, filename)
|
||||
file.save(filepath)
|
||||
|
||||
add_attachment(filename, file_type)
|
||||
flash('文件上传成功', 'success')
|
||||
else:
|
||||
flash('不允许的文件类型', 'danger')
|
||||
|
||||
return redirect(url_for('manage_attachments'))
|
||||
|
||||
# 添加视频路由
|
||||
@app.route('/upload/video/<filename>')
|
||||
def uploaded_video(filename):
|
||||
return send_from_directory(VIDEO_FOLDER, filename)
|
||||
|
||||
|
||||
@app.route('/delete_attachment/<filename>', methods=['POST'])
|
||||
@login_required
|
||||
def delete_attachment_route(filename):
|
||||
try:
|
||||
# 检查文件是否存在
|
||||
file_found = False
|
||||
for folder in [LOGO_FOLDER, BACKGROUND_FOLDER]:
|
||||
filepath = os.path.join(folder, filename)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
file_found = True
|
||||
|
||||
if file_found:
|
||||
delete_attachment(filename)
|
||||
flash('附件删除成功', 'success')
|
||||
else:
|
||||
flash('文件不存在', 'danger')
|
||||
except Exception as e:
|
||||
print(f"删除附件时出错: {e}")
|
||||
flash('删除附件时出错', 'danger')
|
||||
|
||||
return redirect(url_for('manage_attachments'))
|
||||
|
||||
@app.route('/upload/background/<filename>')
|
||||
def uploaded_background(filename):
|
||||
return send_from_directory(BACKGROUND_FOLDER, filename)
|
||||
|
||||
@app.route('/upload/logo/<filename>')
|
||||
def uploaded_logo(filename):
|
||||
return send_from_directory(LOGO_FOLDER, filename)
|
||||
|
||||
@app.route('/api/settings', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def handle_settings():
|
||||
settings = load_settings()
|
||||
|
||||
if request.method == 'POST':
|
||||
data = request.json
|
||||
# 确保不通过API修改密码哈希
|
||||
if 'admin_password_hash' in data:
|
||||
del data['admin_password_hash']
|
||||
settings.update(data)
|
||||
save_settings(settings)
|
||||
return jsonify({'status': 'success'})
|
||||
else:
|
||||
# 从API响应中移除密码哈希
|
||||
settings_to_return = settings.copy()
|
||||
settings_to_return.pop('admin_password_hash', None)
|
||||
return jsonify(settings_to_return)
|
||||
|
||||
|
||||
@app.route('/manage')
|
||||
@login_required
|
||||
def index():
|
||||
category_filter = request.args.get('category')
|
||||
apps = load_apps()
|
||||
categories = load_categories()
|
||||
settings = load_settings()
|
||||
|
||||
if category_filter:
|
||||
filtered_apps = []
|
||||
for app in apps:
|
||||
# 检查是否匹配主分类或子分类
|
||||
if (app['category']['main'] == category_filter or
|
||||
app['category']['sub'] == category_filter or
|
||||
(category_filter in categories and app['category']['main'] == category_filter)):
|
||||
filtered_apps.append(app)
|
||||
apps = filtered_apps
|
||||
|
||||
return render_template('manage.html', apps=apps, settings=settings, categories=categories)
|
||||
|
||||
@app.route('/')
|
||||
def navigation():
|
||||
settings = load_settings()
|
||||
return render_template('index.html',
|
||||
bg_image=settings.get('bg_image', ''),
|
||||
dark_bg_image=settings.get('dark_bg_image', ''),
|
||||
is_logged_in='username' in session,
|
||||
settings=settings) # 添加settings到模板上下文
|
||||
|
||||
@app.route('/api/search', methods=['POST'])
|
||||
def search_apps():
|
||||
keyword = request.json.get('keyword', '').lower()
|
||||
apps = load_apps()
|
||||
results = []
|
||||
|
||||
# 检查登录状态
|
||||
is_logged_in = 'username' in session
|
||||
|
||||
for app in apps:
|
||||
# 如果是私有应用且未登录,则跳过
|
||||
if app.get('private', False) and not is_logged_in:
|
||||
continue
|
||||
|
||||
if (keyword in app['title'].lower() or
|
||||
keyword in app['url'].lower() or
|
||||
keyword in app.get('description', '').lower() or
|
||||
keyword in app['category']['main'].lower() or
|
||||
keyword in app['category']['sub'].lower()):
|
||||
results.append(app)
|
||||
|
||||
return jsonify(results)
|
||||
|
||||
@app.route('/app/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_app():
|
||||
categories = load_categories()
|
||||
icons = load_icons()
|
||||
settings = load_settings()
|
||||
|
||||
if request.method == 'POST':
|
||||
title = request.form['title']
|
||||
url = request.form['url']
|
||||
icon = request.form['icon']
|
||||
description = request.form.get('description', '')
|
||||
private = 'private' in request.form
|
||||
main_category = request.form['main_category']
|
||||
sub_category = request.form['sub_category']
|
||||
|
||||
new_app = {
|
||||
"title": title,
|
||||
"url": url,
|
||||
"icon": icon,
|
||||
"description": description,
|
||||
"private": private,
|
||||
"category": {
|
||||
"main": main_category,
|
||||
"sub": sub_category
|
||||
}
|
||||
}
|
||||
|
||||
apps = load_apps()
|
||||
apps.append(new_app)
|
||||
save_apps(apps)
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
return render_template('add_app.html', categories=categories, settings=settings, icons=icons)
|
||||
|
||||
@app.route('/app/edit/<int:index>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_app(index):
|
||||
categories = load_categories()
|
||||
apps = load_apps()
|
||||
icons = load_icons()
|
||||
settings = load_settings()
|
||||
|
||||
if request.method == 'POST':
|
||||
apps[index]['title'] = request.form['title']
|
||||
apps[index]['url'] = request.form['url']
|
||||
apps[index]['icon'] = request.form['icon']
|
||||
apps[index]['description'] = request.form.get('description', '')
|
||||
apps[index]['private'] = 'private' in request.form
|
||||
apps[index]['category'] = {
|
||||
"main": request.form['main_category'],
|
||||
"sub": request.form['sub_category']
|
||||
}
|
||||
|
||||
save_apps(apps)
|
||||
return redirect(url_for('index'))
|
||||
|
||||
return render_template('edit_app.html', app=apps[index], index=index, settings=settings, categories=categories, icons=icons)
|
||||
|
||||
@app.route('/api/icons')
|
||||
def get_icons():
|
||||
icons = load_icons()
|
||||
return jsonify(icons)
|
||||
|
||||
@app.route('/app/delete/<int:index>')
|
||||
@login_required
|
||||
def delete_app(index):
|
||||
apps = load_apps()
|
||||
apps.pop(index)
|
||||
save_apps(apps)
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/categories')
|
||||
@login_required
|
||||
def manage_categories():
|
||||
categories = load_categories()
|
||||
icons = load_icons()
|
||||
settings = load_settings()
|
||||
return render_template('categories.html', categories=categories, settings=settings, icons=icons)
|
||||
|
||||
@app.route('/categories/add_main', methods=['POST'])
|
||||
@login_required
|
||||
def add_main_category():
|
||||
categories = load_categories()
|
||||
cat_id = request.form['id']
|
||||
cat_name = request.form['name']
|
||||
cat_color = request.form.get('color', '#4361ee')
|
||||
is_private = 'private' in request.form
|
||||
weight = int(request.form.get('weight', 0))
|
||||
|
||||
categories[cat_id] = {
|
||||
"name": cat_name,
|
||||
"color": cat_color,
|
||||
"sub": {},
|
||||
"private": is_private,
|
||||
"sub_private": {},
|
||||
"weight": weight
|
||||
}
|
||||
save_categories(categories)
|
||||
|
||||
return redirect(url_for('manage_categories'))
|
||||
|
||||
@app.route('/categories/add_sub', methods=['POST'])
|
||||
@login_required
|
||||
def add_sub_category():
|
||||
categories = load_categories()
|
||||
main_id = request.form['main_id']
|
||||
sub_id = request.form['sub_id']
|
||||
sub_name = request.form['sub_name']
|
||||
sub_color = request.form.get('color', '#4895ef')
|
||||
is_private = 'private' in request.form
|
||||
weight = int(request.form.get('weight', 0))
|
||||
|
||||
if main_id not in categories:
|
||||
flash('主分类不存在', 'danger')
|
||||
return redirect(url_for('manage_categories'))
|
||||
|
||||
categories[main_id]['sub'][sub_id] = {
|
||||
"name": sub_name,
|
||||
"color": sub_color,
|
||||
"weight": weight
|
||||
}
|
||||
|
||||
if is_private:
|
||||
if 'sub_private' not in categories[main_id]:
|
||||
categories[main_id]['sub_private'] = {}
|
||||
categories[main_id]['sub_private'][sub_id] = True
|
||||
|
||||
save_categories(categories)
|
||||
|
||||
return redirect(url_for('manage_categories'))
|
||||
|
||||
@app.route('/categories/delete_main/<string:main_id>')
|
||||
@login_required
|
||||
def delete_main_category(main_id):
|
||||
categories = load_categories()
|
||||
if main_id in categories:
|
||||
categories.pop(main_id)
|
||||
save_categories(categories)
|
||||
|
||||
# 删除相关应用
|
||||
apps = load_apps()
|
||||
apps = [app for app in apps if app['category']['main'] != main_id]
|
||||
save_apps(apps)
|
||||
|
||||
return redirect(url_for('manage_categories'))
|
||||
|
||||
@app.route('/categories/delete_sub/<string:main_id>/<string:sub_id>')
|
||||
@login_required
|
||||
def delete_sub_category(main_id, sub_id):
|
||||
categories = load_categories()
|
||||
if main_id in categories and sub_id in categories[main_id].get('sub', {}):
|
||||
categories[main_id]['sub'].pop(sub_id)
|
||||
# 同时删除私有标记
|
||||
if 'sub_private' in categories[main_id] and sub_id in categories[main_id]['sub_private']:
|
||||
categories[main_id]['sub_private'].pop(sub_id)
|
||||
save_categories(categories)
|
||||
|
||||
# 更新相关应用
|
||||
apps = load_apps()
|
||||
for app in apps:
|
||||
if app['category']['main'] == main_id and app['category']['sub'] == sub_id:
|
||||
app['category']['sub'] = ""
|
||||
|
||||
save_apps(apps)
|
||||
|
||||
return redirect(url_for('manage_categories'))
|
||||
|
||||
@app.route('/get_subcategories/<string:main_id>')
|
||||
def get_subcategories(main_id):
|
||||
categories = load_categories()
|
||||
if main_id in categories:
|
||||
subcategories = {}
|
||||
for sub_id, sub_data in categories[main_id].get('sub', {}).items():
|
||||
subcategories[sub_id] = sub_data['name'] if isinstance(sub_data, dict) else sub_data
|
||||
return jsonify(subcategories)
|
||||
return jsonify({})
|
||||
|
||||
@app.route('/categories/edit_main/<string:main_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_main_category(main_id):
|
||||
categories = load_categories()
|
||||
settings = load_settings()
|
||||
if main_id not in categories:
|
||||
return redirect(url_for('manage_categories'))
|
||||
|
||||
if request.method == 'POST':
|
||||
categories[main_id]['name'] = request.form['name']
|
||||
categories[main_id]['color'] = request.form.get('color', '#4361ee')
|
||||
categories[main_id]['private'] = 'private' in request.form
|
||||
categories[main_id]['weight'] = int(request.form.get('weight', 0))
|
||||
save_categories(categories)
|
||||
return redirect(url_for('manage_categories'))
|
||||
|
||||
return render_template('edit_main_category.html',
|
||||
category=categories[main_id],
|
||||
settings=settings,
|
||||
main_id=main_id)
|
||||
|
||||
@app.route('/categories/edit_sub/<string:main_id>/<string:sub_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_sub_category(main_id, sub_id):
|
||||
categories = load_categories()
|
||||
settings = load_settings()
|
||||
if main_id not in categories or sub_id not in categories[main_id]['sub']:
|
||||
return redirect(url_for('manage_categories'))
|
||||
|
||||
if request.method == 'POST':
|
||||
new_sub_id = request.form['sub_id']
|
||||
new_sub_name = request.form['sub_name']
|
||||
new_color = request.form.get('color', '#4895ef')
|
||||
is_private = 'private' in request.form
|
||||
weight = int(request.form.get('weight', 0))
|
||||
|
||||
# 如果ID改变了,需要先删除旧的
|
||||
if sub_id != new_sub_id:
|
||||
del categories[main_id]['sub'][sub_id]
|
||||
|
||||
# 更新或添加新的子分类
|
||||
categories[main_id]['sub'][new_sub_id] = {
|
||||
"name": new_sub_name,
|
||||
"color": new_color,
|
||||
"weight": weight
|
||||
}
|
||||
if 'sub_private' not in categories[main_id]:
|
||||
categories[main_id]['sub_private'] = {}
|
||||
categories[main_id]['sub_private'][new_sub_id] = is_private
|
||||
|
||||
save_categories(categories)
|
||||
return redirect(url_for('manage_categories'))
|
||||
|
||||
is_private = categories[main_id].get('sub_private', {}).get(sub_id, False)
|
||||
sub_data = categories[main_id]['sub'][sub_id]
|
||||
return render_template('edit_sub_category.html',
|
||||
main_id=main_id,
|
||||
sub_id=sub_id,
|
||||
sub_name=sub_data['name'],
|
||||
color=sub_data['color'],
|
||||
weight=sub_data.get('weight', 0),
|
||||
settings=settings,
|
||||
is_private=is_private)
|
||||
|
||||
@app.route('/api/apps')
|
||||
def api_apps():
|
||||
apps = load_apps()
|
||||
# 检查登录状态
|
||||
is_logged_in = 'username' in session
|
||||
|
||||
# 如果未登录,过滤掉私有应用
|
||||
if not is_logged_in:
|
||||
apps = [app for app in apps if not app.get('private', False)]
|
||||
|
||||
return jsonify(apps)
|
||||
|
||||
@app.route('/api/categories')
|
||||
def api_categories():
|
||||
categories = load_categories()
|
||||
# 检查登录状态
|
||||
is_logged_in = 'username' in session
|
||||
|
||||
# 按权重排序主分类
|
||||
sorted_categories = dict(sorted(categories.items(),
|
||||
key=lambda x: x[1].get('weight', 0),
|
||||
reverse=True))
|
||||
|
||||
# 如果未登录,过滤掉私有分类但保留颜色信息
|
||||
if not is_logged_in:
|
||||
filtered_categories = {}
|
||||
for main_id, main_data in sorted_categories.items():
|
||||
if not main_data.get('private', False):
|
||||
filtered_sub = {}
|
||||
# 按权重排序子分类
|
||||
sorted_sub = dict(sorted(main_data['sub'].items(),
|
||||
key=lambda x: x[1].get('weight', 0),
|
||||
reverse=True))
|
||||
for sub_id, sub_data in sorted_sub.items():
|
||||
if not main_data.get('sub_private', {}).get(sub_id, False):
|
||||
filtered_sub[sub_id] = sub_data
|
||||
if filtered_sub or main_data.get('sub', {}): # 只添加有子分类的主分类
|
||||
filtered_categories[main_id] = {
|
||||
'name': main_data['name'],
|
||||
'color': main_data['color'], # 保留颜色信息
|
||||
'sub': filtered_sub
|
||||
}
|
||||
return jsonify(filtered_categories)
|
||||
|
||||
# 对子分类也按权重排序
|
||||
for main_id, main_data in sorted_categories.items():
|
||||
main_data['sub'] = dict(sorted(main_data['sub'].items(),
|
||||
key=lambda x: x[1].get('weight', 0),
|
||||
reverse=True))
|
||||
|
||||
return jsonify(sorted_categories)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
113
data/apps-data.json
Normal file
@ -0,0 +1,113 @@
|
||||
[
|
||||
{
|
||||
"title": "SQL生成工具23",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=dbvns61a0glb2q6nwv9utd5v",
|
||||
"icon": "fa-solid fa-drumstick-bite",
|
||||
"description": "一个强大的SQL查询生成工具,支持多种数据库",
|
||||
"category": {
|
||||
"main": "dev",
|
||||
"sub": "data"
|
||||
},
|
||||
"private": true
|
||||
},
|
||||
{
|
||||
"title": "Nginx配置生成",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=3340xwddpp3xi48g1evv7r4m",
|
||||
"icon": "fa-server",
|
||||
"description": "快速生成Nginx配置文件",
|
||||
"category": {
|
||||
"main": "dev",
|
||||
"sub": "web"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "动物的一生",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=0e5kd8ged5w7oxrhfcztvt5b",
|
||||
"icon": "fa-paw",
|
||||
"description": "了解各种动物的生命周期",
|
||||
"category": {
|
||||
"main": "edu",
|
||||
"sub": "science"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Docker转换工具",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=9ylu7kn735vc7z69n3otryss",
|
||||
"icon": "fa-docker",
|
||||
"description": "将不同格式的容器配置相互转换",
|
||||
"category": {
|
||||
"main": "dev",
|
||||
"sub": "ops"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "日报工具",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=arylhdiwlr84qmzmublhlbjj",
|
||||
"icon": "fa-calendar-day",
|
||||
"description": "123",
|
||||
"category": {
|
||||
"main": "tool",
|
||||
"sub": "office"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "NFS-PV-PVC",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=szkw0erj0eukvu8uch2kznzm",
|
||||
"icon": "fa-network-wired",
|
||||
"description": "123",
|
||||
"category": {
|
||||
"main": "dev",
|
||||
"sub": "cloud"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "刑法助手",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=6ot25ul3kli322rybodpring",
|
||||
"icon": "fa-gavel",
|
||||
"description": "123",
|
||||
"category": {
|
||||
"main": "law",
|
||||
"sub": "criminal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "税法助手",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=rjazldnzqc1gr03k1giije7v",
|
||||
"icon": "fa-file-invoice-dollar",
|
||||
"description": "123",
|
||||
"category": {
|
||||
"main": "law",
|
||||
"sub": "tax"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "汉语新解",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=lif7r64uc12rmkhgv51gxslh",
|
||||
"icon": "fa-language",
|
||||
"description": "123",
|
||||
"category": {
|
||||
"main": "edu",
|
||||
"sub": "language"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "AI绘画提示词",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=example1",
|
||||
"icon": "fa-paint-brush",
|
||||
"category": {
|
||||
"main": "ai",
|
||||
"sub": "image"
|
||||
},
|
||||
"description": "",
|
||||
"private": false
|
||||
},
|
||||
{
|
||||
"title": "AI写作助手",
|
||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=example2",
|
||||
"icon": "fa-keyboard",
|
||||
"category": {
|
||||
"main": "ai",
|
||||
"sub": "writing"
|
||||
}
|
||||
}
|
||||
]
|
||||
37
data/attachments.json
Normal file
@ -0,0 +1,37 @@
|
||||
[
|
||||
{
|
||||
"filename": "d078c01de3be46deab9e85a94285d785.png",
|
||||
"type": "background",
|
||||
"upload_time": "2025-07-05 13:55:10"
|
||||
},
|
||||
{
|
||||
"filename": "5dd4f5d3cd7b48eca9967fa063ea5cd9.png",
|
||||
"type": "background",
|
||||
"upload_time": "2025-07-05 13:55:15"
|
||||
},
|
||||
{
|
||||
"filename": "b2c128cf2d4e47daa349c5e7f38c932c.png",
|
||||
"type": "logo",
|
||||
"upload_time": "2025-07-05 13:58:32"
|
||||
},
|
||||
{
|
||||
"filename": "5378dda810964da9a7515ec844628738.png",
|
||||
"type": "logo",
|
||||
"upload_time": "2025-07-05 16:46:09"
|
||||
},
|
||||
{
|
||||
"filename": "f40e2eb965b24e358a5bba9523231f8f.png",
|
||||
"type": "logo",
|
||||
"upload_time": "2025-07-05 16:46:16"
|
||||
},
|
||||
{
|
||||
"filename": "e4e762f039ce471489fc65db6cd395c7.mp4",
|
||||
"type": "video",
|
||||
"upload_time": "2025-07-05 20:54:08"
|
||||
},
|
||||
{
|
||||
"filename": "b23249a9681840329afb0c4af489fc30.mp4",
|
||||
"type": "video",
|
||||
"upload_time": "2025-07-05 21:02:17"
|
||||
}
|
||||
]
|
||||
104
data/categories.json
Normal file
@ -0,0 +1,104 @@
|
||||
{
|
||||
"dev": {
|
||||
"name": "开发工具",
|
||||
"color": "#4cc9f0",
|
||||
"sub": {
|
||||
"data": {
|
||||
"name": "数据库",
|
||||
"color": "#4895ef",
|
||||
"weight": 3
|
||||
},
|
||||
"web": {
|
||||
"name": "Web服务",
|
||||
"color": "#4361ee",
|
||||
"weight": 5
|
||||
},
|
||||
"ops": {
|
||||
"name": "运维部署",
|
||||
"color": "#3a0ca3",
|
||||
"weight": 2
|
||||
},
|
||||
"cloud": {
|
||||
"name": "云计算",
|
||||
"color": "#480ca8",
|
||||
"weight": 0
|
||||
}
|
||||
},
|
||||
"private": true,
|
||||
"sub_private": {
|
||||
"data": true,
|
||||
"web": true,
|
||||
"ops": true,
|
||||
"cloud": true
|
||||
},
|
||||
"weight": 99999
|
||||
},
|
||||
"edu": {
|
||||
"name": "教育学习",
|
||||
"color": "#f72585",
|
||||
"sub": {
|
||||
"science": {
|
||||
"name": "自然科学",
|
||||
"color": "#b5179e",
|
||||
"weight": 0
|
||||
},
|
||||
"language": {
|
||||
"name": "语言",
|
||||
"color": "#7209b7",
|
||||
"weight": 0
|
||||
}
|
||||
},
|
||||
"private": false,
|
||||
"weight": 0
|
||||
},
|
||||
"tool": {
|
||||
"name": "效率工具",
|
||||
"color": "#7209b7",
|
||||
"sub": {
|
||||
"office": {
|
||||
"name": "办公",
|
||||
"color": "#560bad",
|
||||
"weight": 0
|
||||
}
|
||||
},
|
||||
"weight": 0
|
||||
},
|
||||
"law": {
|
||||
"name": "法律相关",
|
||||
"color": "#4895ef",
|
||||
"sub": {
|
||||
"criminal": {
|
||||
"name": "刑法",
|
||||
"color": "#4361ee",
|
||||
"weight": 0
|
||||
},
|
||||
"tax": {
|
||||
"name": "税法",
|
||||
"color": "#3f37c9",
|
||||
"weight": 0
|
||||
}
|
||||
},
|
||||
"weight": 0
|
||||
},
|
||||
"ai": {
|
||||
"name": "AI工具",
|
||||
"color": "#f8961e",
|
||||
"sub": {
|
||||
"image": {
|
||||
"name": "图像生成",
|
||||
"color": "#f3722c",
|
||||
"weight": 999
|
||||
},
|
||||
"writing": {
|
||||
"name": "写作",
|
||||
"color": "#f9844a",
|
||||
"weight": 0
|
||||
}
|
||||
},
|
||||
"private": false,
|
||||
"sub_private": {
|
||||
"image": false
|
||||
},
|
||||
"weight": 29999
|
||||
}
|
||||
}
|
||||
922
data/icons.json
Normal file
@ -0,0 +1,922 @@
|
||||
{
|
||||
"fa-solid fa-house": "房屋",
|
||||
"fa-solid fa-code": "代码",
|
||||
"fa-solid fa-database": "数据库",
|
||||
"fa-solid fa-server": "服务器",
|
||||
"fa-solid fa-cloud": "云",
|
||||
"fa-solid fa-laptop-code": "编程",
|
||||
"fa-solid fa-book": "书籍",
|
||||
"fa-solid fa-graduation-cap": "教育",
|
||||
"fa-solid fa-flask": "科学",
|
||||
"fa-solid fa-language": "语言",
|
||||
"fa-solid fa-file-word": "文档",
|
||||
"fa-solid fa-file-excel": "表格",
|
||||
"fa-solid fa-file-powerpoint": "演示",
|
||||
"fa-solid fa-gavel": "法律",
|
||||
"fa-solid fa-balance-scale": "平衡",
|
||||
"fa-solid fa-robot": "AI",
|
||||
"fa-solid fa-brain": "智能",
|
||||
"fa-solid fa-image": "图像",
|
||||
"fa-solid fa-paint-brush": "绘画",
|
||||
"fa-solid fa-search": "搜索",
|
||||
"fa-solid fa-link": "链接",
|
||||
"fa-solid fa-globe": "地球",
|
||||
"fa-solid fa-envelope": "邮件",
|
||||
"fa-solid fa-calendar": "日历",
|
||||
"fa-solid fa-clock": "时钟",
|
||||
"fa-solid fa-calculator": "计算器",
|
||||
"fa-solid fa-chart-line": "图表",
|
||||
"fa-solid fa-users": "用户",
|
||||
"fa-solid fa-shield-alt": "安全",
|
||||
"fa-solid fa-lock": "锁",
|
||||
"fa-solid fa-key": "钥匙",
|
||||
"fa-solid fa-terminal": "终端",
|
||||
"fa-solid fa-network-wired": "网络",
|
||||
"fa-solid fa-desktop": "桌面",
|
||||
"fa-solid fa-mobile-alt": "手机",
|
||||
"fa-solid fa-tablet-alt": "平板",
|
||||
"fa-solid fa-print": "打印",
|
||||
"fa-solid fa-camera": "相机",
|
||||
"fa-solid fa-video": "视频",
|
||||
"fa-solid fa-microphone": "麦克风",
|
||||
"fa-solid fa-music": "音乐",
|
||||
"fa-solid fa-gamepad": "游戏",
|
||||
"fa-solid fa-puzzle-piece": "拼图",
|
||||
"fa-solid fa-map": "地图",
|
||||
"fa-solid fa-compass": "指南针",
|
||||
"fa-solid fa-flag": "旗帜",
|
||||
"fa-solid fa-heart": "心形",
|
||||
"fa-solid fa-star": "星星",
|
||||
"fa-solid fa-bell": "铃铛",
|
||||
"fa-solid fa-gift": "礼物",
|
||||
"fa-solid fa-shopping-cart": "购物车",
|
||||
"fa-solid fa-credit-card": "信用卡",
|
||||
"fa-solid fa-money-bill-wave": "货币",
|
||||
"fa-solid fa-coins": "硬币",
|
||||
"fa-solid fa-piggy-bank": "存钱罐",
|
||||
"fa-solid fa-briefcase": "公文包",
|
||||
"fa-solid fa-suitcase": "行李箱",
|
||||
"fa-solid fa-building": "建筑",
|
||||
"fa-solid fa-hospital": "医院",
|
||||
"fa-solid fa-ambulance": "救护车",
|
||||
"fa-solid fa-fire": "火",
|
||||
"fa-solid fa-leaf": "叶子",
|
||||
"fa-solid fa-tree": "树",
|
||||
"fa-solid fa-seedling": "幼苗",
|
||||
"fa-solid fa-recycle": "回收",
|
||||
"fa-solid fa-sun": "太阳",
|
||||
"fa-solid fa-moon": "月亮",
|
||||
"fa-solid fa-cloud-sun": "多云",
|
||||
"fa-solid fa-cloud-rain": "雨",
|
||||
"fa-solid fa-snowflake": "雪花",
|
||||
"fa-solid fa-wind": "风",
|
||||
"fa-solid fa-umbrella": "雨伞",
|
||||
"fa-solid fa-bolt": "闪电",
|
||||
"fa-solid fa-fire-extinguisher": "灭火器",
|
||||
"fa-solid fa-shower": "淋浴",
|
||||
"fa-solid fa-bath": "浴缸",
|
||||
"fa-solid fa-toilet": "马桶",
|
||||
"fa-solid fa-couch": "沙发",
|
||||
"fa-solid fa-bed": "床",
|
||||
"fa-solid fa-chair": "椅子",
|
||||
"fa-solid fa-utensils": "餐具",
|
||||
"fa-solid fa-mug-hot": "杯子",
|
||||
"fa-solid fa-blender": "搅拌机",
|
||||
"fa-solid fa-ice-cream": "冰淇淋",
|
||||
"fa-solid fa-hamburger": "汉堡",
|
||||
"fa-solid fa-pizza-slice": "披萨",
|
||||
"fa-solid fa-b bread-slice": "面包",
|
||||
"fa-solid fa-cheese": "奶酪",
|
||||
"fa-solid fa-egg": "鸡蛋",
|
||||
"fa-solid fa-apple-alt": "苹果",
|
||||
"fa-solid fa-lemon": "柠檬",
|
||||
"fa-solid fa-pepper-hot": "辣椒",
|
||||
"fa-solid fa-fish": "鱼",
|
||||
"fa-solid fa-drumstick-bite": "鸡腿",
|
||||
"fa-solid fa-carrot": "胡萝卜",
|
||||
"fa-solid fa-beer": "啤酒",
|
||||
"fa-solid fa-wine-glass-alt": "酒杯",
|
||||
"fa-solid fa-cocktail": "鸡尾酒",
|
||||
"fa-solid fa-coffee": "咖啡",
|
||||
"fa-solid fa-tea": "茶",
|
||||
"fa-solid fa-user": "用户",
|
||||
"fa-solid fa-users-cog": "管理员",
|
||||
"fa-solid fa-tools": "工具",
|
||||
"fa-solid fa-cog": "设置",
|
||||
"fa-solid fa-sync": "同步",
|
||||
"fa-solid fa-download": "下载",
|
||||
"fa-solid fa-upload": "上传",
|
||||
"fa-solid fa-trash": "删除",
|
||||
"fa-solid fa-edit": "编辑",
|
||||
"fa-solid fa-save": "保存",
|
||||
"fa-solid fa-folder": "文件夹",
|
||||
"fa-solid fa-folder-open": "打开文件夹",
|
||||
"fa-solid fa-file": "文件",
|
||||
"fa-solid fa-file-alt": "文本文件",
|
||||
"fa-solid fa-file-pdf": "PDF",
|
||||
"fa-solid fa-file-image": "图片文件",
|
||||
"fa-solid fa-file-audio": "音频文件",
|
||||
"fa-solid fa-file-video": "视频文件",
|
||||
"fa-solid fa-file-archive": "压缩文件",
|
||||
"fa-solid fa-file-code": "代码文件",
|
||||
"fa-solid fa-barcode": "条形码",
|
||||
"fa-solid fa-qrcode": "二维码",
|
||||
"fa-solid fa-tag": "标签",
|
||||
"fa-solid fa-tags": "多个标签",
|
||||
"fa-solid fa-comment": "评论",
|
||||
"fa-solid fa-comments": "多条评论",
|
||||
"fa-solid fa-thumbs-up": "点赞",
|
||||
"fa-solid fa-thumbs-down": "点踩",
|
||||
"fa-solid fa-share": "分享",
|
||||
"fa-solid fa-external-link-alt": "外部链接",
|
||||
"fa-solid fa-eye": "查看",
|
||||
"fa-solid fa-eye-slash": "隐藏",
|
||||
"fa-solid fa-filter": "筛选",
|
||||
"fa-solid fa-sort": "排序",
|
||||
"fa-solid fa-arrow-up": "向上箭头",
|
||||
"fa-solid fa-arrow-down": "向下箭头",
|
||||
"fa-solid fa-arrow-left": "向左箭头",
|
||||
"fa-solid fa-arrow-right": "向右箭头",
|
||||
"fa-solid fa-expand": "放大",
|
||||
"fa-solid fa-compress": "缩小",
|
||||
"fa-solid fa-plus": "加号",
|
||||
"fa-solid fa-minus": "减号",
|
||||
"fa-solid fa-times": "关闭",
|
||||
"fa-solid fa-check": "确认",
|
||||
"fa-solid fa-ban": "禁止",
|
||||
"fa-solid fa-info-circle": "信息",
|
||||
"fa-solid fa-exclamation-triangle": "警告",
|
||||
"fa-solid fa-exclamation-circle": "错误",
|
||||
"fa-solid fa-question-circle": "帮助",
|
||||
"fa-solid fa-check-circle": "成功",
|
||||
"fa-solid fa-ellipsis-h": "更多",
|
||||
"fa-solid fa-headphones": "耳机",
|
||||
"fa-solid fa-tv": "电视",
|
||||
"fa-solid fa-gamepad": "游戏手柄",
|
||||
"fa-solid fa-dumbbell": "健身",
|
||||
"fa-solid fa-bicycle": "自行车",
|
||||
"fa-solid fa-bus": "公交车",
|
||||
"fa-solid fa-car": "汽车",
|
||||
"fa-solid fa-plane": "飞机",
|
||||
"fa-solid fa-ship": "轮船",
|
||||
"fa-solid fa-train": "火车",
|
||||
"fa-solid fa-subway": "地铁",
|
||||
"fa-solid fa-motorcycle": "摩托车",
|
||||
"fa-solid fa-paper-plane": "纸飞机",
|
||||
"fa-solid fa-space-shuttle": "航天飞机",
|
||||
"fa-solid fa-satellite": "卫星",
|
||||
"fa-solid fa-microscope": "显微镜",
|
||||
"fa-solid fa-atom": "原子",
|
||||
"fa-solid fa-vial": "试管",
|
||||
"fa-solid fa-prescription-bottle": "药瓶",
|
||||
"fa-solid fa-stethoscope": "听诊器",
|
||||
"fa-solid fa-syringe": "注射器",
|
||||
"fa-solid fa-dna": "DNA",
|
||||
"fa-solid fa-wheelchair": "轮椅",
|
||||
"fa-solid fa-crutch": "拐杖",
|
||||
"fa-solid fa-procedures": "医疗程序",
|
||||
"fa-solid fa-first-aid": "急救",
|
||||
"fa-solid fa-hand-holding-heart": "手捧爱心",
|
||||
"fa-solid fa-handshake": "握手",
|
||||
"fa-solid fa-praying-hands": "祈祷",
|
||||
"fa-solid fa-church": "教堂",
|
||||
"fa-solid fa-mosque": "清真寺",
|
||||
"fa-solid fa-synagogue": "犹太教堂",
|
||||
"fa-solid fa-kaaba": "天房",
|
||||
"fa-solid fa-torah": "托拉",
|
||||
"fa-solid fa-bible": "圣经",
|
||||
"fa-solid fa-quran": "古兰经",
|
||||
"fa-solid fa-place-of-worship": "礼拜场所",
|
||||
"fa-solid fa-angry": "生气",
|
||||
"fa-solid fa-dizzy": "晕眩",
|
||||
"fa-solid fa-flushed": "脸红",
|
||||
"fa-solid fa-frown": "皱眉",
|
||||
"fa-solid fa-frown-open": "张嘴皱眉",
|
||||
"fa-solid fa-grimace": "鬼脸",
|
||||
"fa-solid fa-grin": "露齿笑",
|
||||
"fa-solid fa-grin-alt": "咧嘴笑",
|
||||
"fa-solid fa-grin-beam": "开心笑",
|
||||
"fa-solid fa-grin-beam-sweat": "尴尬笑",
|
||||
"fa-solid fa-grin-hearts": "爱心眼笑",
|
||||
"fa-solid fa-grin-squint": "眯眼笑",
|
||||
"fa-solid fa-grin-squint-tears": "笑哭",
|
||||
"fa-solid fa-grin-stars": "星星眼笑",
|
||||
"fa-solid fa-grin-tears": "笑出眼泪",
|
||||
"fa-solid fa-grin-tongue": "吐舌笑",
|
||||
"fa-solid fa-grin-tongue-squint": "调皮吐舌",
|
||||
"fa-solid fa-grin-tongue-wink": "眨眼吐舌",
|
||||
"fa-solid fa-grin-wink": "眨眼笑",
|
||||
"fa-solid fa-kiss": "亲吻",
|
||||
"fa-solid fa-kiss-beam": "开心亲吻",
|
||||
"fa-solid fa-kiss-wink-heart": "眨眼爱心吻",
|
||||
"fa-solid fa-laugh": "大笑",
|
||||
"fa-solid fa-laugh-beam": "开心大笑",
|
||||
"fa-solid fa-laugh-squint": "眯眼大笑",
|
||||
"fa-solid fa-laugh-wink": "眨眼大笑",
|
||||
"fa-solid fa-meh": "中性",
|
||||
"fa-solid fa-meh-blank": "面无表情",
|
||||
"fa-solid fa-meh-rolling-eyes": "翻白眼",
|
||||
"fa-solid fa-sad-cry": "伤心哭泣",
|
||||
"fa-solid fa-sad-tear": "流泪",
|
||||
"fa-solid fa-smile": "微笑",
|
||||
"fa-solid fa-smile-beam": "开心微笑",
|
||||
"fa-solid fa-smile-wink": "眨眼微笑",
|
||||
"fa-solid fa-surprise": "惊讶",
|
||||
"fa-solid fa-tired": "疲惫",
|
||||
"fa-solid fa-user-astronaut": "宇航员",
|
||||
"fa-solid fa-user-ninja": "忍者",
|
||||
"fa-solid fa-user-secret": "特工",
|
||||
"fa-solid fa-user-tie": "西装用户",
|
||||
"fa-solid fa-ghost": "幽灵",
|
||||
"fa-solid fa-poo": "便便",
|
||||
"fa-solid fa-robot": "机器人",
|
||||
"fa-solid fa-cat": "猫",
|
||||
"fa-solid fa-dog": "狗",
|
||||
"fa-solid fa-dove": "和平鸽",
|
||||
"fa-solid fa-dragon": "龙",
|
||||
"fa-solid fa-feather": "羽毛",
|
||||
"fa-solid fa-feather-alt": "羽毛笔",
|
||||
"fa-solid fa-frog": "青蛙",
|
||||
"fa-solid fa-hippo": "河马",
|
||||
"fa-solid fa-horse": "马",
|
||||
"fa-solid fa-horse-head": "马头",
|
||||
"fa-solid fa-kiwi-bird": "几维鸟",
|
||||
"fa-solid fa-otter": "水獭",
|
||||
"fa-solid fa-paw": "爪子",
|
||||
"fa-solid fa-spider": "蜘蛛",
|
||||
"fa-solid fa-crow": "乌鸦",
|
||||
"fa-solid fa-ankh": "安卡",
|
||||
"fa-solid fa-bahai": "巴哈伊",
|
||||
"fa-solid fa-bible": "圣经",
|
||||
"fa-solid fa-book-dead": "死灵之书",
|
||||
"fa-solid fa-book-open": "打开的书",
|
||||
"fa-solid fa-book-reader": "阅读器",
|
||||
"fa-solid fa-church": "教堂",
|
||||
"fa-solid fa-cross": "十字架",
|
||||
"fa-solid fa-dharmachakra": "法轮",
|
||||
"fa-solid fa-dice-d20": "20面骰",
|
||||
"fa-solid fa-dice-d6": "6面骰",
|
||||
"fa-solid fa-dice-five": "骰子5",
|
||||
"fa-solid fa-dice-four": "骰子4",
|
||||
"fa-solid fa-dice-one": "骰子1",
|
||||
"fa-solid fa-dice-six": "骰子6",
|
||||
"fa-solid fa-dice-three": "骰子3",
|
||||
"fa-solid fa-dice-two": "骰子2",
|
||||
"fa-solid fa-dragon": "龙",
|
||||
"fa-solid fa-eye-evil": "邪恶之眼",
|
||||
"fa-solid fa-fist-raised": "拳头",
|
||||
"fa-solid fa-flask-poison": "毒药瓶",
|
||||
"fa-solid fa-flask-potion": "药水瓶",
|
||||
"fa-solid fa-fort-awesome": "堡垒",
|
||||
"fa-solid fa-gopuram": "印度塔",
|
||||
"fa-solid fa-hamsa": "哈姆萨",
|
||||
"fa-solid fa-hand-holding-magic": "魔法手",
|
||||
"fa-solid fa-hat-wizard": "巫师帽",
|
||||
"fa-solid fa-haykal": "巴哈伊符号",
|
||||
"fa-solid fa-jedi": "绝地",
|
||||
"fa-solid fa-journal-whills": "绝地圣典",
|
||||
"fa-solid fa-kaaba": "天房",
|
||||
"fa-solid fa-khanda": "坎达剑",
|
||||
"fa-solid fa-magic": "魔法",
|
||||
"fa-solid fa-mandolin": "曼陀林",
|
||||
"fa-solid fa-mask": "面具",
|
||||
"fa-solid fa-menorah": "烛台",
|
||||
"fa-solid fa-mosque": "清真寺",
|
||||
"fa-solid fa-om": "唵",
|
||||
"fa-solid fa-pastafarianism": "飞天面条",
|
||||
"fa-solid fa-peace": "和平",
|
||||
"fa-solid fa-place-of-worship": "礼拜场所",
|
||||
"fa-solid fa-pray": "祈祷",
|
||||
"fa-solid fa-praying-hands": "祈祷之手",
|
||||
"fa-solid fa-quran": "古兰经",
|
||||
"fa-solid fa-scroll": "卷轴",
|
||||
"fa-solid fa-star-and-crescent": "星月",
|
||||
"fa-solid fa-star-of-david": "大卫之星",
|
||||
"fa-solid fa-synagogue": "犹太教堂",
|
||||
"fa-solid fa-torah": "托拉",
|
||||
"fa-solid fa-vihara": "寺院",
|
||||
"fa-solid fa-volume-mute": "静音",
|
||||
"fa-solid fa-volume-down": "音量减小",
|
||||
"fa-solid fa-volume-up": "音量增大",
|
||||
"fa-solid fa-volume-off": "音量关闭",
|
||||
"fa-solid fa-wifi": "WiFi",
|
||||
"fa-solid fa-signal": "信号",
|
||||
"fa-solid fa-battery-empty": "电池空",
|
||||
"fa-solid fa-battery-quarter": "电池25%",
|
||||
"fa-solid fa-battery-half": "电池50%",
|
||||
"fa-solid fa-battery-three-quarters": "电池75%",
|
||||
"fa-solid fa-battery-full": "电池满",
|
||||
"fa-solid fa-plug": "插头",
|
||||
"fa-solid fa-power-off": "关机",
|
||||
"fa-solid fa-lightbulb": "灯泡",
|
||||
"fa-solid fa-mobile": "手机",
|
||||
"fa-solid fa-tablet": "平板",
|
||||
"fa-solid fa-laptop": "笔记本",
|
||||
"fa-solid fa-desktop": "台式机",
|
||||
"fa-solid fa-sim-card": "SIM卡",
|
||||
"fa-solid fa-memory": "内存",
|
||||
"fa-solid fa-hdd": "硬盘",
|
||||
"fa-solid fa-ethernet": "以太网",
|
||||
"fa-solid fa-sd-card": "SD卡",
|
||||
"fa-solid fa-usb": "USB",
|
||||
"fa-solid fa-bluetooth": "蓝牙",
|
||||
"fa-solid fa-bluetooth-b": "蓝牙B",
|
||||
"fa-solid fa-nfc": "NFC",
|
||||
"fa-solid fa-network-wired": "有线网络",
|
||||
"fa-solid fa-satellite-dish": "卫星天线",
|
||||
"fa-solid fa-tv": "电视",
|
||||
"fa-solid fa-projector": "投影仪",
|
||||
"fa-solid fa-robot": "机器人",
|
||||
"fa-solid fa-microchip": "芯片",
|
||||
"fa-solid fa-server": "服务器",
|
||||
"fa-solid fa-database": "数据库",
|
||||
"fa-solid fa-hdd": "硬盘",
|
||||
"fa-solid fa-save": "保存",
|
||||
"fa-solid fa-upload": "上传",
|
||||
"fa-solid fa-download": "下载",
|
||||
"fa-solid fa-cloud-upload-alt": "云上传",
|
||||
"fa-solid fa-cloud-download-alt": "云下载",
|
||||
"fa-solid fa-cloud": "云",
|
||||
"fa-solid fa-cloud-meatball": "云肉丸",
|
||||
"fa-solid fa-cloud-moon": "云月亮",
|
||||
"fa-solid fa-cloud-moon-rain": "云月雨",
|
||||
"fa-solid fa-cloud-rain": "云雨",
|
||||
"fa-solid fa-cloud-showers-heavy": "暴雨",
|
||||
"fa-solid fa-cloud-sun": "云太阳",
|
||||
"fa-solid fa-cloud-sun-rain": "云阳雨",
|
||||
"fa-solid fa-meteor": "流星",
|
||||
"fa-solid fa-moon": "月亮",
|
||||
"fa-solid fa-poo-storm": "便便风暴",
|
||||
"fa-solid fa-rainbow": "彩虹",
|
||||
"fa-solid fa-smog": "雾霾",
|
||||
"fa-solid fa-snowflake": "雪花",
|
||||
"fa-solid fa-sun": "太阳",
|
||||
"fa-solid fa-temperature-high": "高温",
|
||||
"fa-solid fa-temperature-low": "低温",
|
||||
"fa-solid fa-tint": "水滴",
|
||||
"fa-solid fa-tint-slash": "无水",
|
||||
"fa-solid fa-umbrella": "雨伞",
|
||||
"fa-solid fa-water": "水",
|
||||
"fa-solid fa-wind": "风",
|
||||
"fa-solid fa-adjust": "调整",
|
||||
"fa-solid fa-bolt": "闪电",
|
||||
"fa-solid fa-camera": "相机",
|
||||
"fa-solid fa-camera-retro": "复古相机",
|
||||
"fa-solid fa-chalkboard": "黑板",
|
||||
"fa-solid fa-clipboard": "剪贴板",
|
||||
"fa-solid fa-comment": "评论",
|
||||
"fa-solid fa-comment-alt": "评论框",
|
||||
"fa-solid fa-comments": "多条评论",
|
||||
"fa-solid fa-copy": "复制",
|
||||
"fa-solid fa-cut": "剪切",
|
||||
"fa-solid fa-edit": "编辑",
|
||||
"fa-solid fa-eraser": "橡皮擦",
|
||||
"fa-solid fa-file": "文件",
|
||||
"fa-solid fa-file-alt": "文本文件",
|
||||
"fa-solid fa-file-archive": "压缩文件",
|
||||
"fa-solid fa-file-audio": "音频文件",
|
||||
"fa-solid fa-file-code": "代码文件",
|
||||
"fa-solid fa-file-excel": "Excel文件",
|
||||
"fa-solid fa-file-image": "图片文件",
|
||||
"fa-solid fa-file-pdf": "PDF文件",
|
||||
"fa-solid fa-file-powerpoint": "PPT文件",
|
||||
"fa-solid fa-file-video": "视频文件",
|
||||
"fa-solid fa-file-word": "Word文件",
|
||||
"fa-solid fa-folder": "文件夹",
|
||||
"fa-solid fa-folder-open": "打开文件夹",
|
||||
"fa-solid fa-font": "字体",
|
||||
"fa-solid fa-glasses": "眼镜",
|
||||
"fa-solid fa-highlighter": "荧光笔",
|
||||
"fa-solid fa-i-cursor": "I型光标",
|
||||
"fa-solid fa-keyboard": "键盘",
|
||||
"fa-solid fa-marker": "马克笔",
|
||||
"fa-solid fa-paperclip": "回形针",
|
||||
"fa-solid fa-paste": "粘贴",
|
||||
"fa-solid fa-pen": "钢笔",
|
||||
"fa-solid fa-pen-alt": "钢笔替代",
|
||||
"fa-solid fa-pen-fancy": "花式笔",
|
||||
"fa-solid fa-pen-nib": "笔尖",
|
||||
"fa-solid fa-pencil-alt": "铅笔",
|
||||
"fa-solid fa-print": "打印",
|
||||
"fa-solid fa-quote-left": "左引号",
|
||||
"fa-solid fa-quote-right": "右引号",
|
||||
"fa-solid fa-stamp": "印章",
|
||||
"fa-solid fa-sticky-note": "便签",
|
||||
"fa-solid fa-table": "表格",
|
||||
"fa-solid fa-tasks": "任务",
|
||||
"fa-solid fa-thumbtack": "图钉",
|
||||
"fa-solid fa-trash": "垃圾桶",
|
||||
"fa-solid fa-trash-alt": "垃圾桶(替代)",
|
||||
"fa-solid fa-underline": "下划线",
|
||||
"fa-solid fa-unlink": "取消链接",
|
||||
"fa-solid fa-window-maximize": "窗口最大化",
|
||||
"fa-solid fa-window-minimize": "窗口最小化",
|
||||
"fa-solid fa-window-restore": "窗口恢复",
|
||||
"fa-solid fa-align-center": "居中对齐",
|
||||
"fa-solid fa-align-justify": "两端对齐",
|
||||
"fa-solid fa-align-left": "左对齐",
|
||||
"fa-solid fa-align-right": "右对齐",
|
||||
"fa-solid fa-bold": "粗体",
|
||||
"fa-solid fa-border-all": "所有边框",
|
||||
"fa-solid fa-border-none": "无边框",
|
||||
"fa-solid fa-border-style": "边框样式",
|
||||
"fa-solid fa-clipboard-check": "剪贴板检查",
|
||||
"fa-solid fa-clipboard-list": "剪贴板列表",
|
||||
"fa-solid fa-columns": "列",
|
||||
"fa-solid fa-copyright": "版权",
|
||||
"fa-solid fa-ellipsis-h": "水平省略号",
|
||||
"fa-solid fa-ellipsis-v": "垂直省略号",
|
||||
"fa-solid fa-file-signature": "文件签名",
|
||||
"fa-solid fa-font-awesome-logo-full": "Font Awesome标志",
|
||||
"fa-solid fa-heading": "标题",
|
||||
"fa-solid fa-horizontal-rule": "水平线",
|
||||
"fa-solid fa-indent": "缩进",
|
||||
"fa-solid fa-italic": "斜体",
|
||||
"fa-solid fa-link": "链接",
|
||||
"fa-solid fa-list": "列表",
|
||||
"fa-solid fa-list-alt": "列表(替代)",
|
||||
"fa-solid fa-list-ol": "有序列表",
|
||||
"fa-solid fa-list-ul": "无序列表",
|
||||
"fa-solid fa-outdent": "减少缩进",
|
||||
"fa-solid fa-paragraph": "段落",
|
||||
"fa-solid fa-poll": "投票",
|
||||
"fa-solid fa-poll-h": "水平投票",
|
||||
"fa-solid fa-redo": "重做",
|
||||
"fa-solid fa-redo-alt": "重做(替代)",
|
||||
"fa-solid fa-reply": "回复",
|
||||
"fa-solid fa-reply-all": "回复全部",
|
||||
"fa-solid fa-share-square": "分享方块",
|
||||
"fa-solid fa-spell-check": "拼写检查",
|
||||
"fa-solid fa-strikethrough": "删除线",
|
||||
"fa-solid fa-subscript": "下标",
|
||||
"fa-solid fa-superscript": "上标",
|
||||
"fa-solid fa-sync": "同步",
|
||||
"fa-solid fa-sync-alt": "同步(替代)",
|
||||
"fa-solid fa-table": "表格",
|
||||
"fa-solid fa-text-height": "文本高度",
|
||||
"fa-solid fa-text-width": "文本宽度",
|
||||
"fa-solid fa-th": "表格标题",
|
||||
"fa-solid fa-th-large": "大表格",
|
||||
"fa-solid fa-th-list": "表格列表",
|
||||
"fa-solid fa-trash-restore": "恢复删除",
|
||||
"fa-solid fa-trash-restore-alt": "恢复删除(替代)",
|
||||
"fa-solid fa-undo": "撤销",
|
||||
"fa-solid fa-undo-alt": "撤销(替代)",
|
||||
"fa-solid fa-unlink": "取消链接",
|
||||
"fa-solid fa-wrench": "扳手",
|
||||
"fa-brands fa-500px": "500px",
|
||||
"fa-brands fa-accessible-icon": "无障碍图标",
|
||||
"fa-brands fa-accusoft": "Accusoft",
|
||||
"fa-brands fa-acquisitions-incorporated": "Acquisitions Inc",
|
||||
"fa-brands fa-adn": "ADN",
|
||||
"fa-brands fa-adobe": "Adobe",
|
||||
"fa-brands fa-adversal": "Adversal",
|
||||
"fa-brands fa-affiliatetheme": "AffiliateTheme",
|
||||
"fa-brands fa-airbnb": "Airbnb",
|
||||
"fa-brands fa-algolia": "Algolia",
|
||||
"fa-brands fa-alipay": "支付宝",
|
||||
"fa-brands fa-amazon": "亚马逊",
|
||||
"fa-brands fa-amazon-pay": "Amazon Pay",
|
||||
"fa-brands fa-amilia": "Amilia",
|
||||
"fa-brands fa-android": "安卓",
|
||||
"fa-brands fa-angellist": "AngelList",
|
||||
"fa-brands fa-angrycreative": "Angry Creative",
|
||||
"fa-brands fa-angular": "Angular",
|
||||
"fa-brands fa-app-store": "App Store",
|
||||
"fa-brands fa-app-store-ios": "App Store iOS",
|
||||
"fa-brands fa-apper": "Apper",
|
||||
"fa-brands fa-apple": "苹果",
|
||||
"fa-brands fa-apple-pay": "Apple Pay",
|
||||
"fa-brands fa-artstation": "ArtStation",
|
||||
"fa-brands fa-asymmetrik": "Asymmetrik",
|
||||
"fa-brands fa-atlassian": "Atlassian",
|
||||
"fa-brands fa-audible": "Audible",
|
||||
"fa-brands fa-autoprefixer": "Autoprefixer",
|
||||
"fa-brands fa-avianex": "Avianex",
|
||||
"fa-brands fa-aviato": "Aviato",
|
||||
"fa-brands fa-aws": "AWS",
|
||||
"fa-brands fa-bandcamp": "Bandcamp",
|
||||
"fa-brands fa-battle-net": "暴雪战网",
|
||||
"fa-brands fa-behance": "Behance",
|
||||
"fa-brands fa-behance-square": "Behance方块",
|
||||
"fa-brands fa-bimobject": "BIMobject",
|
||||
"fa-brands fa-bitbucket": "Bitbucket",
|
||||
"fa-brands fa-bitcoin": "比特币",
|
||||
"fa-brands fa-bity": "Bity",
|
||||
"fa-brands fa-black-tie": "Black Tie",
|
||||
"fa-brands fa-blackberry": "黑莓",
|
||||
"fa-brands fa-blogger": "Blogger",
|
||||
"fa-brands fa-blogger-b": "Blogger B",
|
||||
"fa-brands fa-bluetooth": "蓝牙",
|
||||
"fa-brands fa-bluetooth-b": "蓝牙B",
|
||||
"fa-brands fa-bootstrap": "Bootstrap",
|
||||
"fa-brands fa-btc": "BTC",
|
||||
"fa-brands fa-buffer": "Buffer",
|
||||
"fa-brands fa-buromobelexperte": "Büromöbel Experte",
|
||||
"fa-brands fa-buy-n-large": "Buy n Large",
|
||||
"fa-brands fa-buysellads": "BuySellAds",
|
||||
"fa-brands fa-canadian-maple-leaf": "加拿大枫叶",
|
||||
"fa-brands fa-cc-amazon-pay": "CC Amazon Pay",
|
||||
"fa-brands fa-cc-amex": "美国运通",
|
||||
"fa-brands fa-cc-apple-pay": "CC Apple Pay",
|
||||
"fa-brands fa-cc-diners-club": "大来卡",
|
||||
"fa-brands fa-cc-discover": "Discover卡",
|
||||
"fa-brands fa-cc-jcb": "JCB卡",
|
||||
"fa-brands fa-cc-mastercard": "万事达卡",
|
||||
"fa-brands fa-cc-paypal": "PayPal卡",
|
||||
"fa-brands fa-cc-stripe": "Stripe卡",
|
||||
"fa-brands fa-cc-visa": "Visa卡",
|
||||
"fa-brands fa-centercode": "Centercode",
|
||||
"fa-brands fa-centos": "CentOS",
|
||||
"fa-brands fa-chrome": "Chrome",
|
||||
"fa-brands fa-chromecast": "Chromecast",
|
||||
"fa-brands fa-cloudflare": "Cloudflare",
|
||||
"fa-brands fa-cloudscale": "Cloudscale",
|
||||
"fa-brands fa-cloudsmith": "Cloudsmith",
|
||||
"fa-brands fa-cloudversify": "Cloudversify",
|
||||
"fa-brands fa-codepen": "CodePen",
|
||||
"fa-brands fa-codiepie": "CodiePie",
|
||||
"fa-brands fa-confluence": "Confluence",
|
||||
"fa-brands fa-connectdevelop": "Connect Develop",
|
||||
"fa-brands fa-contao": "Contao",
|
||||
"fa-brands fa-cotton-bureau": "Cotton Bureau",
|
||||
"fa-brands fa-cpanel": "cPanel",
|
||||
"fa-brands fa-creative-commons": "Creative Commons",
|
||||
"fa-brands fa-creative-commons-by": "CC BY",
|
||||
"fa-brands fa-creative-commons-nc": "CC NC",
|
||||
"fa-brands fa-creative-commons-nc-eu": "CC NC EU",
|
||||
"fa-brands fa-creative-commons-nc-jp": "CC NC JP",
|
||||
"fa-brands fa-creative-commons-nd": "CC ND",
|
||||
"fa-brands fa-creative-commons-pd": "CC PD",
|
||||
"fa-brands fa-creative-commons-pd-alt": "CC PD Alt",
|
||||
"fa-brands fa-creative-commons-remix": "CC Remix",
|
||||
"fa-brands fa-creative-commons-sa": "CC SA",
|
||||
"fa-brands fa-creative-commons-sampling": "CC Sampling",
|
||||
"fa-brands fa-creative-commons-sampling-plus": "CC Sampling Plus",
|
||||
"fa-brands fa-creative-commons-share": "CC Share",
|
||||
"fa-brands fa-creative-commons-zero": "CC Zero",
|
||||
"fa-brands fa-critical-role": "Critical Role",
|
||||
"fa-brands fa-css3": "CSS3",
|
||||
"fa-brands fa-css3-alt": "CSS3 Alt",
|
||||
"fa-brands fa-cuttlefish": "Cuttlefish",
|
||||
"fa-brands fa-d-and-d": "D&D",
|
||||
"fa-brands fa-d-and-d-beyond": "D&D Beyond",
|
||||
"fa-brands fa-dailymotion": "Dailymotion",
|
||||
"fa-brands fa-dashcube": "Dashcube",
|
||||
"fa-brands fa-deezer": "Deezer",
|
||||
"fa-brands fa-delicious": "Delicious",
|
||||
"fa-brands fa-deploydog": "Deploydog",
|
||||
"fa-brands fa-deskpro": "Deskpro",
|
||||
"fa-brands fa-dev": "DEV",
|
||||
"fa-brands fa-deviantart": "DeviantArt",
|
||||
"fa-brands fa-dhl": "DHL",
|
||||
"fa-brands fa-diaspora": "Diaspora",
|
||||
"fa-brands fa-digg": "Digg",
|
||||
"fa-brands fa-digital-ocean": "DigitalOcean",
|
||||
"fa-brands fa-discord": "Discord",
|
||||
"fa-brands fa-discourse": "Discourse",
|
||||
"fa-brands fa-dochub": "DocHub",
|
||||
"fa-brands fa-docker": "Docker",
|
||||
"fa-brands fa-draft2digital": "Draft2Digital",
|
||||
"fa-brands fa-dribbble": "Dribbble",
|
||||
"fa-brands fa-dribbble-square": "Dribbble方块",
|
||||
"fa-brands fa-dropbox": "Dropbox",
|
||||
"fa-brands fa-drupal": "Drupal",
|
||||
"fa-brands fa-dyalog": "Dyalog",
|
||||
"fa-brands fa-earlybirds": "Earlybirds",
|
||||
"fa-brands fa-ebay": "eBay",
|
||||
"fa-brands fa-edge": "Edge",
|
||||
"fa-brands fa-edge-legacy": "Edge Legacy",
|
||||
"fa-brands fa-elementor": "Elementor",
|
||||
"fa-brands fa-ello": "Ello",
|
||||
"fa-brands fa-ember": "Ember",
|
||||
"fa-brands fa-empire": "Empire",
|
||||
"fa-brands fa-envira": "Envira",
|
||||
"fa-brands fa-erlang": "Erlang",
|
||||
"fa-brands fa-ethereum": "以太坊",
|
||||
"fa-brands fa-etsy": "Etsy",
|
||||
"fa-brands fa-evernote": "Evernote",
|
||||
"fa-brands fa-expeditedssl": "ExpeditedSSL",
|
||||
"fa-brands fa-facebook": "Facebook",
|
||||
"fa-brands fa-facebook-f": "Facebook F",
|
||||
"fa-brands fa-facebook-messenger": "Facebook Messenger",
|
||||
"fa-brands fa-facebook-square": "Facebook方块",
|
||||
"fa-brands fa-fantasy-flight-games": "Fantasy Flight Games",
|
||||
"fa-brands fa-fedex": "FedEx",
|
||||
"fa-brands fa-fedora": "Fedora",
|
||||
"fa-brands fa-figma": "Figma",
|
||||
"fa-brands fa-firefox": "Firefox",
|
||||
"fa-brands fa-firefox-browser": "Firefox浏览器",
|
||||
"fa-brands fa-first-order": "First Order",
|
||||
"fa-brands fa-first-order-alt": "First Order Alt",
|
||||
"fa-brands fa-firstdraft": "Firstdraft",
|
||||
"fa-brands fa-flickr": "Flickr",
|
||||
"fa-brands fa-flipboard": "Flipboard",
|
||||
"fa-brands fa-fly": "Fly",
|
||||
"fa-brands fa-font-awesome": "Font Awesome",
|
||||
"fa-brands fa-font-awesome-alt": "Font Awesome Alt",
|
||||
"fa-brands fa-font-awesome-flag": "Font Awesome标志",
|
||||
"fa-brands fa-fonticons": "Fonticons",
|
||||
"fa-brands fa-fonticons-fi": "Fonticons Fi",
|
||||
"fa-brands fa-fort-awesome": "Fort Awesome",
|
||||
"fa-brands fa-fort-awesome-alt": "Fort Awesome Alt",
|
||||
"fa-brands fa-forumbee": "Forumbee",
|
||||
"fa-brands fa-foursquare": "Foursquare",
|
||||
"fa-brands fa-free-code-camp": "Free Code Camp",
|
||||
"fa-brands fa-freebsd": "FreeBSD",
|
||||
"fa-brands fa-fulcrum": "Fulcrum",
|
||||
"fa-brands fa-galactic-republic": "银河共和国",
|
||||
"fa-brands fa-galactic-senate": "银河参议院",
|
||||
"fa-brands fa-get-pocket": "Pocket",
|
||||
"fa-brands fa-gg": "GG",
|
||||
"fa-brands fa-gg-circle": "GG圆圈",
|
||||
"fa-brands fa-git": "Git",
|
||||
"fa-brands fa-git-alt": "Git Alt",
|
||||
"fa-brands fa-git-square": "Git方块",
|
||||
"fa-brands fa-github": "GitHub",
|
||||
"fa-brands fa-github-alt": "GitHub Alt",
|
||||
"fa-brands fa-github-square": "GitHub方块",
|
||||
"fa-brands fa-gitkraken": "GitKraken",
|
||||
"fa-brands fa-gitlab": "GitLab",
|
||||
"fa-brands fa-gitter": "Gitter",
|
||||
"fa-brands fa-glide": "Glide",
|
||||
"fa-brands fa-glide-g": "Glide G",
|
||||
"fa-brands fa-gofore": "Gofore",
|
||||
"fa-brands fa-goodreads": "Goodreads",
|
||||
"fa-brands fa-goodreads-g": "Goodreads G",
|
||||
"fa-brands fa-google": "Google",
|
||||
"fa-brands fa-google-drive": "Google Drive",
|
||||
"fa-brands fa-google-pay": "Google Pay",
|
||||
"fa-brands fa-google-play": "Google Play",
|
||||
"fa-brands fa-google-plus": "Google+",
|
||||
"fa-brands fa-google-plus-g": "Google+ G",
|
||||
"fa-brands fa-google-plus-square": "Google+方块",
|
||||
"fa-brands fa-google-wallet": "Google Wallet",
|
||||
"fa-brands fa-gratipay": "Gratipay",
|
||||
"fa-brands fa-grav": "Grav",
|
||||
"fa-brands fa-gripfire": "Gripfire",
|
||||
"fa-brands fa-grunt": "Grunt",
|
||||
"fa-brands fa-guilded": "Guilded",
|
||||
"fa-brands fa-gulp": "Gulp",
|
||||
"fa-brands fa-hacker-news": "Hacker News",
|
||||
"fa-brands fa-hacker-news-square": "Hacker News方块",
|
||||
"fa-brands fa-hackerrank": "HackerRank",
|
||||
"fa-brands fa-hips": "HIPS",
|
||||
"fa-brands fa-hire-a-helper": "HireAHelper",
|
||||
"fa-brands fa-hive": "Hive",
|
||||
"fa-brands fa-hooli": "Hooli",
|
||||
"fa-brands fa-hornbill": "Hornbill",
|
||||
"fa-brands fa-hotjar": "Hotjar",
|
||||
"fa-brands fa-houzz": "Houzz",
|
||||
"fa-brands fa-html5": "HTML5",
|
||||
"fa-brands fa-hubspot": "HubSpot",
|
||||
"fa-brands fa-ideal": "iDEAL",
|
||||
"fa-brands fa-imdb": "IMDb",
|
||||
"fa-brands fa-instagram": "Instagram",
|
||||
"fa-brands fa-instagram-square": "Instagram方块",
|
||||
"fa-brands fa-intercom": "Intercom",
|
||||
"fa-brands fa-internet-explorer": "IE",
|
||||
"fa-brands fa-invision": "InVision",
|
||||
"fa-brands fa-ioxhost": "ioxhost",
|
||||
"fa-brands fa-itch-io": "itch.io",
|
||||
"fa-brands fa-itunes": "iTunes",
|
||||
"fa-brands fa-itunes-note": "iTunes Note",
|
||||
"fa-brands fa-java": "Java",
|
||||
"fa-brands fa-jedi-order": "绝地武士团",
|
||||
"fa-brands fa-jenkins": "Jenkins",
|
||||
"fa-brands fa-jira": "Jira",
|
||||
"fa-brands fa-joget": "Joget",
|
||||
"fa-brands fa-joomla": "Joomla",
|
||||
"fa-brands fa-js": "JS",
|
||||
"fa-brands fa-js-square": "JS方块",
|
||||
"fa-brands fa-jsfiddle": "JSFiddle",
|
||||
"fa-brands fa-kaggle": "Kaggle",
|
||||
"fa-brands fa-keybase": "Keybase",
|
||||
"fa-brands fa-keycdn": "KeyCDN",
|
||||
"fa-brands fa-kickstarter": "Kickstarter",
|
||||
"fa-brands fa-kickstarter-k": "Kickstarter K",
|
||||
"fa-brands fa-korvue": "Korvue",
|
||||
"fa-brands fa-laravel": "Laravel",
|
||||
"fa-brands fa-lastfm": "Last.fm",
|
||||
"fa-brands fa-lastfm-square": "Last.fm方块",
|
||||
"fa-brands fa-leanpub": "Leanpub",
|
||||
"fa-brands fa-less": "Less",
|
||||
"fa-brands fa-line": "Line",
|
||||
"fa-brands fa-linkedin": "LinkedIn",
|
||||
"fa-brands fa-linkedin-in": "LinkedIn In",
|
||||
"fa-brands fa-linode": "Linode",
|
||||
"fa-brands fa-linux": "Linux",
|
||||
"fa-brands fa-lyft": "Lyft",
|
||||
"fa-brands fa-magento": "Magento",
|
||||
"fa-brands fa-mailchimp": "Mailchimp",
|
||||
"fa-brands fa-mandalorian": "曼达洛人",
|
||||
"fa-brands fa-markdown": "Markdown",
|
||||
"fa-brands fa-mastodon": "Mastodon",
|
||||
"fa-brands fa-maxcdn": "MaxCDN",
|
||||
"fa-brands fa-mdb": "MDB",
|
||||
"fa-brands fa-medapps": "MedApps",
|
||||
"fa-brands fa-medium": "Medium",
|
||||
"fa-brands fa-medium-m": "Medium M",
|
||||
"fa-brands fa-medrt": "Medrt",
|
||||
"fa-brands fa-meetup": "Meetup",
|
||||
"fa-brands fa-megaport": "Megaport",
|
||||
"fa-brands fa-mendeley": "Mendeley",
|
||||
"fa-brands fa-microblog": "Micro.blog",
|
||||
"fa-brands fa-microsoft": "微软",
|
||||
"fa-brands fa-mix": "Mix",
|
||||
"fa-brands fa-mixcloud": "Mixcloud",
|
||||
"fa-brands fa-mixer": "Mixer",
|
||||
"fa-brands fa-mizuni": "Mizuni",
|
||||
"fa-brands fa-modx": "MODX",
|
||||
"fa-brands fa-monero": "门罗币",
|
||||
"fa-brands fa-napster": "Napster",
|
||||
"fa-brands fa-neos": "Neos",
|
||||
"fa-brands fa-nimblr": "Nimblr",
|
||||
"fa-brands fa-node": "Node.js",
|
||||
"fa-brands fa-node-js": "Node.js",
|
||||
"fa-brands fa-npm": "npm",
|
||||
"fa-brands fa-ns8": "NS8",
|
||||
"fa-brands fa-nutritionix": "Nutritionix",
|
||||
"fa-brands fa-octopus-deploy": "Octopus Deploy",
|
||||
"fa-brands fa-odnoklassniki": "Odnoklassniki",
|
||||
"fa-brands fa-odnoklassniki-square": "Odnoklassniki方块",
|
||||
"fa-brands fa-old-republic": "旧共和国",
|
||||
"fa-brands fa-opencart": "OpenCart",
|
||||
"fa-brands fa-openid": "OpenID",
|
||||
"fa-brands fa-opera": "Opera",
|
||||
"fa-brands fa-optin-monster": "Optin Monster",
|
||||
"fa-brands fa-orcid": "ORCID",
|
||||
"fa-brands fa-osi": "OSI",
|
||||
"fa-brands fa-page4": "Page4",
|
||||
"fa-brands fa-pagelines": "Pagelines",
|
||||
"fa-brands fa-palfed": "Palfed",
|
||||
"fa-brands fa-patreon": "Patreon",
|
||||
"fa-brands fa-paypal": "PayPal",
|
||||
"fa-brands fa-penny-arcade": "Penny Arcade",
|
||||
"fa-brands fa-periscope": "Periscope",
|
||||
"fa-brands fa-phabricator": "Phabricator",
|
||||
"fa-brands fa-phoenix-framework": "Phoenix Framework",
|
||||
"fa-brands fa-phoenix-squadron": "凤凰中队",
|
||||
"fa-brands fa-php": "PHP",
|
||||
"fa-brands fa-pied-piper": "Pied Piper",
|
||||
"fa-brands fa-pied-piper-alt": "Pied Piper Alt",
|
||||
"fa-brands fa-pied-piper-hat": "Pied Piper Hat",
|
||||
"fa-brands fa-pied-piper-pp": "Pied Piper PP",
|
||||
"fa-brands fa-pied-piper-square": "Pied Piper方块",
|
||||
"fa-brands fa-pinterest": "Pinterest",
|
||||
"fa-brands fa-pinterest-p": "Pinterest P",
|
||||
"fa-brands fa-pinterest-square": "Pinterest方块",
|
||||
"fa-brands fa-playstation": "PlayStation",
|
||||
"fa-brands fa-product-hunt": "Product Hunt",
|
||||
"fa-brands fa-pushed": "Pushed",
|
||||
"fa-brands fa-python": "Python",
|
||||
"fa-brands fa-qq": "QQ",
|
||||
"fa-brands fa-quinscape": "QuinScape",
|
||||
"fa-brands fa-quora": "Quora",
|
||||
"fa-brands fa-r-project": "R Project",
|
||||
"fa-brands fa-raspberry-pi": "树莓派",
|
||||
"fa-brands fa-ravelry": "Ravelry",
|
||||
"fa-brands fa-react": "React",
|
||||
"fa-brands fa-reacteurope": "ReactEurope",
|
||||
"fa-brands fa-readme": "Readme",
|
||||
"fa-brands fa-rebel": "Rebel",
|
||||
"fa-brands fa-red-river": "Red River",
|
||||
"fa-brands fa-reddit": "Reddit",
|
||||
"fa-brands fa-reddit-alien": "Reddit Alien",
|
||||
"fa-brands fa-reddit-square": "Reddit方块",
|
||||
"fa-brands fa-redhat": "Red Hat",
|
||||
"fa-brands fa-renren": "人人网",
|
||||
"fa-brands fa-replyd": "Replyd",
|
||||
"fa-brands fa-researchgate": "ResearchGate",
|
||||
"fa-brands fa-resolving": "Resolving",
|
||||
"fa-brands fa-rev": "Rev",
|
||||
"fa-brands fa-rocketchat": "Rocket.Chat",
|
||||
"fa-brands fa-rockrms": "Rockrms",
|
||||
"fa-brands fa-rust": "Rust",
|
||||
"fa-brands fa-safari": "Safari",
|
||||
"fa-brands fa-salesforce": "Salesforce",
|
||||
"fa-brands fa-sass": "Sass",
|
||||
"fa-brands fa-schlix": "Schlix",
|
||||
"fa-brands fa-scribd": "Scribd",
|
||||
"fa-brands fa-searchengin": "搜索引擎",
|
||||
"fa-brands fa-sellcast": "Sellcast",
|
||||
"fa-brands fa-sellsy": "Sellsy",
|
||||
"fa-brands fa-servicestack": "ServiceStack",
|
||||
"fa-brands fa-shirtsinbulk": "Shirts In Bulk",
|
||||
"fa-brands fa-shopify": "Shopify",
|
||||
"fa-brands fa-shopware": "Shopware",
|
||||
"fa-brands fa-simplybuilt": "SimplyBuilt",
|
||||
"fa-brands fa-sistrix": "Sistrix",
|
||||
"fa-brands fa-sith": "西斯",
|
||||
"fa-brands fa-sketch": "Sketch",
|
||||
"fa-brands fa-skyatlas": "Skyatlas",
|
||||
"fa-brands fa-skype": "Skype",
|
||||
"fa-brands fa-slack": "Slack",
|
||||
"fa-brands fa-slack-hash": "Slack Hash",
|
||||
"fa-brands fa-slideshare": "SlideShare",
|
||||
"fa-brands fa-snapchat": "Snapchat",
|
||||
"fa-brands fa-snapchat-ghost": "Snapchat Ghost",
|
||||
"fa-brands fa-snapchat-square": "Snapchat方块",
|
||||
"fa-brands fa-soundcloud": "SoundCloud",
|
||||
"fa-brands fa-sourcetree": "SourceTree",
|
||||
"fa-brands fa-speakap": "Speakap",
|
||||
"fa-brands fa-speaker-deck": "Speaker Deck",
|
||||
"fa-brands fa-spotify": "Spotify",
|
||||
"fa-brands fa-squarespace": "Squarespace",
|
||||
"fa-brands fa-stack-exchange": "Stack Exchange",
|
||||
"fa-brands fa-stack-overflow": "Stack Overflow",
|
||||
"fa-brands fa-stackpath": "Stackpath",
|
||||
"fa-brands fa-staylinked": "StayLinked",
|
||||
"fa-brands fa-steam": "Steam",
|
||||
"fa-brands fa-steam-square": "Steam方块",
|
||||
"fa-brands fa-steam-symbol": "Steam符号",
|
||||
"fa-brands fa-sticker-mule": "Sticker Mule",
|
||||
"fa-brands fa-strava": "Strava",
|
||||
"fa-brands fa-stripe": "Stripe",
|
||||
"fa-brands fa-stripe-s": "Stripe S",
|
||||
"fa-brands fa-studiovinari": "StudioVinari",
|
||||
"fa-brands fa-stumbleupon": "StumbleUpon",
|
||||
"fa-brands fa-stumbleupon-circle": "StumbleUpon圆圈",
|
||||
"fa-brands fa-superpowers": "Superpowers",
|
||||
"fa-brands fa-supple": "Supple",
|
||||
"fa-brands fa-suse": "SUSE",
|
||||
"fa-brands fa-swift": "Swift",
|
||||
"fa-brands fa-symfony": "Symfony",
|
||||
"fa-brands fa-teamspeak": "TeamSpeak",
|
||||
"fa-brands fa-telegram": "Telegram",
|
||||
"fa-brands fa-telegram-plane": "Telegram Plane",
|
||||
"fa-brands fa-tencent-weibo": "腾讯微博",
|
||||
"fa-brands fa-the-red-yeti": "The Red Yeti",
|
||||
"fa-brands fa-themeco": "Themeco",
|
||||
"fa-brands fa-themeisle": "ThemeIsle",
|
||||
"fa-brands fa-think-peaks": "Think Peaks",
|
||||
"fa-brands fa-tiktok": "TikTok",
|
||||
"fa-brands fa-trade-federation": "贸易联盟",
|
||||
"fa-brands fa-trello": "Trello",
|
||||
"fa-brands fa-tripadvisor": "TripAdvisor",
|
||||
"fa-brands fa-tumblr": "Tumblr",
|
||||
"fa-brands fa-tumblr-square": "Tumblr方块",
|
||||
"fa-brands fa-twitch": "Twitch",
|
||||
"fa-brands fa-twitter": "Twitter",
|
||||
"fa-brands fa-twitter-square": "Twitter方块",
|
||||
"fa-brands fa-typo3": "TYPO3",
|
||||
"fa-brands fa-uber": "Uber",
|
||||
"fa-brands fa-ubuntu": "Ubuntu",
|
||||
"fa-brands fa-uikit": "UIkit",
|
||||
"fa-brands fa-umbraco": "Umbraco",
|
||||
"fa-brands fa-uncharted": "Uncharted",
|
||||
"fa-brands fa-uniregistry": "Uniregistry",
|
||||
"fa-brands fa-unity": "Unity",
|
||||
"fa-brands fa-unsplash": "Unsplash",
|
||||
"fa-brands fa-untappd": "Untappd",
|
||||
"fa-brands fa-ups": "UPS",
|
||||
"fa-brands fa-usb": "USB",
|
||||
"fa-brands fa-usps": "USPS",
|
||||
"fa-brands fa-ussunnah": "US Sunnah",
|
||||
"fa-brands fa-vaadin": "Vaadin",
|
||||
"fa-brands fa-viacoin": "Viacoin",
|
||||
"fa-brands fa-viadeo": "Viadeo",
|
||||
"fa-brands fa-viadeo-square": "Viadeo方块",
|
||||
"fa-brands fa-viber": "Viber",
|
||||
"fa-brands fa-vimeo": "Vimeo",
|
||||
"fa-brands fa-vimeo-square": "Vimeo方块",
|
||||
"fa-brands fa-vimeo-v": "Vimeo V",
|
||||
"fa-brands fa-vine": "Vine",
|
||||
"fa-brands fa-vk": "VK",
|
||||
"fa-brands fa-vnv": "VNV",
|
||||
"fa-brands fa-vuejs": "Vue.js",
|
||||
"fa-brands fa-watchman-monitoring": "Watchman Monitoring",
|
||||
"fa-brands fa-waze": "Waze",
|
||||
"fa-brands fa-weebly": "Weebly",
|
||||
"fa-brands fa-weibo": "微博",
|
||||
"fa-brands fa-weixin": "微信",
|
||||
"fa-brands fa-whatsapp": "WhatsApp",
|
||||
"fa-brands fa-whatsapp-square": "WhatsApp方块",
|
||||
"fa-brands fa-whmcs": "WHMCS",
|
||||
"fa-brands fa-wikipedia-w": "Wikipedia W",
|
||||
"fa-brands fa-windows": "Windows",
|
||||
"fa-brands fa-wix": "Wix",
|
||||
"fa-brands fa-wizards-of-the-coast": "海岸巫师",
|
||||
"fa-brands fa-wodu": "WODU",
|
||||
"fa-brands fa-wolf-pack-battalion": "Wolf Pack Battalion",
|
||||
"fa-brands fa-wordpress": "WordPress",
|
||||
"fa-brands fa-wordpress-simple": "WordPress Simple",
|
||||
"fa-brands fa-wpbeginner": "WPBeginner",
|
||||
"fa-brands fa-wpexplorer": "WPExplorer",
|
||||
"fa-brands fa-wpforms": "WPForms",
|
||||
"fa-brands fa-wpressr": "WPpressr",
|
||||
"fa-brands fa-xbox": "Xbox",
|
||||
"fa-brands fa-xing": "Xing",
|
||||
"fa-brands fa-xing-square": "Xing方块",
|
||||
"fa-brands fa-y-combinator": "Y Combinator",
|
||||
"fa-brands fa-yahoo": "Yahoo",
|
||||
"fa-brands fa-yammer": "Yammer",
|
||||
"fa-brands fa-yandex": "Yandex",
|
||||
"fa-brands fa-yandex-international": "Yandex International",
|
||||
"fa-brands fa-yarn": "Yarn",
|
||||
"fa-brands fa-yelp": "Yelp",
|
||||
"fa-brands fa-yoast": "Yoast",
|
||||
"fa-brands fa-youtube": "YouTube",
|
||||
"fa-brands fa-youtube-square": "YouTube方块",
|
||||
"fa-brands fa-zhihu": "知乎"
|
||||
}
|
||||
14
data/settings.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"card_style": "compact",
|
||||
"search_history": [],
|
||||
"theme": "light",
|
||||
"bg_image": "/upload/background/5dd4f5d3cd7b48eca9967fa063ea5cd9.png",
|
||||
"dark_bg_image": "/static/background_dark.jpg",
|
||||
"site_title": "应用导航",
|
||||
"show_logo": true,
|
||||
"logo_type": "image",
|
||||
"logo_icon": "fa-solid fa-th-list",
|
||||
"logo_image": "/upload/logo/f40e2eb965b24e358a5bba9523231f8f.png",
|
||||
"dark_bg_rotate": false,
|
||||
"admin_password_hash": "scrypt:32768:8:1$mPFCfRRzOrcjE6z3$e72ef50a2d3f7292f64bcfc5e21f32c95ea8665414ea8d5f6b216735d68f151166c99fae21132c7949bd92ea32041f969cd4a471adb110a99328089541f7dccb"
|
||||
}
|
||||
6
data/system_config.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"site_title": "导航系统",
|
||||
"default_theme": "dark",
|
||||
"allow_registration": true,
|
||||
"enable_captcha": true
|
||||
}
|
||||
5
data/system_settings.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"site_title": "前往导航",
|
||||
"logo": "/static/logo.png",
|
||||
"favicon": "/static/favicon.ico"
|
||||
}
|
||||
9
data/users.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"admin": {
|
||||
"password": "112233",
|
||||
"theme": "light",
|
||||
"card_style": "compact",
|
||||
"bg_image": "/upload/background/admin_bg.png",
|
||||
"dark_bg_image": "/upload/background/admin_dark_bg.png"
|
||||
}
|
||||
}
|
||||
BIN
static/background_dark.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
static/background_light.jpg
Normal file
|
After Width: | Height: | Size: 256 KiB |
6
static/css/all.min.css
vendored
Normal file
7
static/css/bootstrap.min.css
vendored
Normal file
4
static/css/font-awesome.min.css
vendored
Normal file
BIN
static/favicon.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
7
static/js/bootstrap.bundle.min.js
vendored
Normal file
BIN
static/webfonts/fa-solid-900.woff2
Normal file
216
templates/add_app.html
Normal file
@ -0,0 +1,216 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}添加应用{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4>添加应用</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">标题</label>
|
||||
<input type="text" name="title" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">URL</label>
|
||||
<input type="url" name="url" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">图标</label>
|
||||
<input type="hidden" name="icon" id="selectedIcon" value="" required>
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="icon-preview me-3">
|
||||
<i id="iconPreview" class="fas fa-question-circle fa-2x"></i>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#iconModal">
|
||||
<i class="fas fa-icons me-2"></i>选择图标
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">主分类</label>
|
||||
<select name="main_category" id="main_category" class="form-select" required>
|
||||
<option value="">请选择</option>
|
||||
{% for main_id, cat in categories.items() %}
|
||||
<option value="{{ main_id }}">{{ cat.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">子分类</label>
|
||||
<select name="sub_category" id="sub_category" class="form-select" required>
|
||||
<option value="">请选择</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">描述</label>
|
||||
<textarea name="description" class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" name="private" class="form-check-input" id="privateCheck">
|
||||
<label class="form-check-label" for="privateCheck">私有应用(仅登录后可见)</label>
|
||||
</div>
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" class="btn btn-primary me-md-2">
|
||||
<i class="fas fa-save me-2"></i>添加
|
||||
</button>
|
||||
<a href="{{ url_for('index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>返回
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图标选择模态框 -->
|
||||
<div class="modal fade" id="iconModal" tabindex="-1" aria-labelledby="iconModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="iconModalLabel">选择图标</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||
<input type="text" id="iconSearch" class="form-control" placeholder="搜索图标...">
|
||||
</div>
|
||||
<div class="row row-cols-3 row-cols-sm-4 row-cols-md-5 row-cols-lg-6 row-cols-xl-8 g-3" id="iconGrid">
|
||||
{% for icon, name in icons.items() %}
|
||||
<div class="col">
|
||||
<div class="icon-item p-3 text-center rounded" data-icon="{{ icon }}" title="{{ name }}">
|
||||
<i class="{{ icon }} fa-2x mb-2"></i>
|
||||
<div class="small text-truncate">{{ name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="me-auto">
|
||||
<span id="selectedIconName">未选择图标</span>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" id="confirmIcon">确认选择</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let selectedIcon = '';
|
||||
let selectedIconName = '';
|
||||
|
||||
// 图标搜索功能
|
||||
document.getElementById('iconSearch').addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
const iconItems = document.querySelectorAll('.icon-item');
|
||||
|
||||
iconItems.forEach(item => {
|
||||
const iconName = item.getAttribute('title').toLowerCase();
|
||||
const iconCode = item.dataset.icon.toLowerCase();
|
||||
if (iconName.includes(searchTerm) || iconCode.includes(searchTerm)) {
|
||||
item.closest('.col').style.display = 'block';
|
||||
} else {
|
||||
item.closest('.col').style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 图标点击选择
|
||||
document.querySelectorAll('.icon-item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
// 移除所有选中状态
|
||||
document.querySelectorAll('.icon-item').forEach(i => {
|
||||
i.classList.remove('selected');
|
||||
});
|
||||
|
||||
// 添加选中状态
|
||||
this.classList.add('selected');
|
||||
selectedIcon = this.dataset.icon;
|
||||
selectedIconName = this.getAttribute('title');
|
||||
|
||||
// 更新底部显示
|
||||
document.getElementById('selectedIconName').textContent = selectedIconName;
|
||||
});
|
||||
});
|
||||
|
||||
// 确认选择
|
||||
document.getElementById('confirmIcon').addEventListener('click', function() {
|
||||
if (selectedIcon) {
|
||||
document.getElementById('selectedIcon').value = selectedIcon;
|
||||
document.getElementById('iconPreview').className = selectedIcon + ' fa-2x';
|
||||
bootstrap.Modal.getInstance(document.getElementById('iconModal')).hide();
|
||||
} else {
|
||||
alert('请先选择一个图标');
|
||||
}
|
||||
});
|
||||
|
||||
// 主分类变化时加载子分类
|
||||
document.getElementById('main_category').addEventListener('change', function() {
|
||||
const mainId = this.value;
|
||||
if (!mainId) return;
|
||||
|
||||
fetch('/get_subcategories/' + mainId)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const subSelect = document.getElementById('sub_category');
|
||||
subSelect.innerHTML = '<option value="">请选择</option>';
|
||||
|
||||
for (const [id, name] of Object.entries(data)) {
|
||||
const option = document.createElement('option');
|
||||
option.value = id;
|
||||
option.textContent = name;
|
||||
subSelect.appendChild(option);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.icon-preview {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.icon-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.icon-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
|
||||
.icon-item.selected {
|
||||
background-color: #e7f1ff;
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
#iconGrid {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#selectedIconName {
|
||||
font-weight: bold;
|
||||
color: #0d6efd;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
98
templates/attachments.html
Normal file
@ -0,0 +1,98 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}附件管理{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4><i class="fas fa-paperclip me-2"></i>附件管理</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-4">
|
||||
<h5 class="border-bottom pb-2 mb-3">上传新附件</h5>
|
||||
<form method="POST" action="{{ url_for('upload_attachment') }}" enctype="multipart/form-data">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="type" class="form-label">附件类型</label>
|
||||
<select name="type" id="type" class="form-select" required>
|
||||
<option value="logo">Logo</option>
|
||||
<option value="background">背景图片</option>
|
||||
<option value="video">背景视频</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="file" class="form-label">选择文件</label>
|
||||
<input type="file" class="form-control" id="file" name="file" accept="image/*,video/mp4" required>
|
||||
</div>
|
||||
<div class="col-md-2 mb-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-upload me-2"></i>上传
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5 class="border-bottom pb-2 mb-3">附件列表</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>预览</th>
|
||||
<th>文件名</th>
|
||||
<th>类型</th>
|
||||
<th>上传时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for attachment in attachments %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if attachment.type == 'logo' %}
|
||||
<img src="{{ url_for('uploaded_logo', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
|
||||
{% elif attachment.type == 'background' %}
|
||||
<img src="{{ url_for('uploaded_background', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
|
||||
{% else %}
|
||||
<video width="80" height="45" muted style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
|
||||
<source src="{{ url_for('uploaded_video', filename=attachment.filename) }}" type="video/mp4">
|
||||
</video>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ attachment.filename }}</td>
|
||||
<td>
|
||||
{% if attachment.type == 'logo' %}
|
||||
Logo
|
||||
{% elif attachment.type == 'background' %}
|
||||
背景图片
|
||||
{% else %}
|
||||
背景视频
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ attachment.upload_time }}</td>
|
||||
<td>
|
||||
<form method="POST" action="{{ url_for('delete_attachment_route', filename=attachment.filename) }}" style="display: inline;">
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('确定要删除这个附件吗?')">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">暂无附件</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<a href="{{ url_for('index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> 返回首页
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
128
templates/base.html
Normal file
@ -0,0 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}前往导航{% endblock %}</title>
|
||||
{% if settings and settings.logo_type == 'image' and settings.logo_image %}
|
||||
<link rel="icon" href="{{ settings.logo_image }}">
|
||||
{% else %}
|
||||
<link rel="icon" href="/static/favicon.png">
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/css/all.min.css">
|
||||
<style>
|
||||
body {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
.navbar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #eee;
|
||||
text-align: center;
|
||||
color: #777;
|
||||
}
|
||||
/* 添加选中状态的样式 */
|
||||
.nav-item .nav-link.active {
|
||||
font-weight: bold;
|
||||
color: #0d6efd !important;
|
||||
border-bottom: 2px solid #0d6efd;
|
||||
}
|
||||
.badge .badge {
|
||||
padding: 0.2em 0.4em;
|
||||
font-size: 0.75em;
|
||||
line-height: 1;
|
||||
}
|
||||
.bg-primary {
|
||||
--bs-bg-opacity: 1;
|
||||
background-color: rgb(106 113 124) !important;
|
||||
}
|
||||
|
||||
.bg-light {
|
||||
--bs-bg-opacity: 1;
|
||||
background-color: rgb(220 224 227) !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light rounded">
|
||||
<div class="container-fluid">
|
||||
{% if settings and settings.show_logo and settings.logo_type == 'image' and settings.logo_image %}
|
||||
<a class="navbar-brand" href="{{ url_for('navigation') }}">
|
||||
<img src="{{ settings.logo_image }}" height="30" class="d-inline-block align-top me-2" alt="Logo">
|
||||
{{ settings.site_title if settings.site_title else '前往导航' }}
|
||||
</a>
|
||||
{% elif settings and settings.show_logo and settings.logo_type == 'icon' and settings.logo_icon %}
|
||||
<a class="navbar-brand" href="{{ url_for('navigation') }}">
|
||||
<i class="{{ settings.logo_icon }} me-2"></i>
|
||||
{{ settings.site_title if settings.site_title else '前往导航' }}
|
||||
</a>
|
||||
{% else %}
|
||||
<a class="navbar-brand" href="{{ url_for('navigation') }}">前往导航</a>
|
||||
{% endif %}
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
{% if 'username' in session %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'index' %}active{% endif %}" href="{{ url_for('index') }}">应用管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'manage_categories' %}active{% endif %}" href="{{ url_for('manage_categories') }}">分类管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'manage_attachments' %}active{% endif %}" href="{{ url_for('manage_attachments') }}">附件管理</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
{% if 'username' in session %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-user"></i> {{ session['username'] }}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item {% if request.endpoint == 'system_settings' %}active{% endif %}" href="{{ url_for('system_settings') }}">系统设置</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('logout') }}">退出登录</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'login' %}active{% endif %}" href="{{ url_for('login') }}">登录</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<footer class="footer">
|
||||
<p>© 2025 导航管理系统</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/bootstrap.bundle.min.js"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
112
templates/categories.html
Normal file
@ -0,0 +1,112 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}分类管理{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4><i class="fas fa-folder me-2"></i>分类管理</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-4">
|
||||
<h5>添加主分类</h5>
|
||||
<form method="post" action="{{ url_for('add_main_category') }}" class="d-flex align-items-center gap-2">
|
||||
<input type="text" name="id" class="form-control" placeholder="主分类 ID" required style="width: 120px;">
|
||||
<input type="text" name="name" class="form-control" placeholder="主分类名称" required style="width: 150px;">
|
||||
<input type="number" name="weight" class="form-control" placeholder="权重" value="0" style="width: 80px;">
|
||||
<input type="color" name="color" class="form-control form-control-color" value="#4361ee" title="选择颜色" style="width: 50px;">
|
||||
<div class="form-check form-check-inline">
|
||||
<input type="checkbox" class="form-check-input" id="main_private" name="private">
|
||||
<label class="form-check-label" for="main_private">私有</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> 添加
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5>添加子分类</h5>
|
||||
<form method="post" action="{{ url_for('add_sub_category') }}" class="d-flex align-items-center gap-2">
|
||||
<select name="main_id" class="form-select" required style="width: 150px;">
|
||||
<option value="">选择主分类</option>
|
||||
{% for main_id, cat in categories.items() %}
|
||||
<option value="{{ main_id }}">{{ cat.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="text" name="sub_id" class="form-control" placeholder="子分类 ID" required style="width: 120px;">
|
||||
<input type="text" name="sub_name" class="form-control" placeholder="子分类名称" required style="width: 150px;">
|
||||
<input type="number" name="weight" class="form-control" placeholder="权重" value="0" style="width: 80px;">
|
||||
<input type="color" name="color" class="form-control form-control-color" value="#4895ef" title="选择颜色" style="width: 50px;">
|
||||
<div class="form-check form-check-inline">
|
||||
<input type="checkbox" class="form-check-input" id="sub_private" name="private">
|
||||
<label class="form-check-label" for="sub_private">私有</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fas fa-plus"></i> 添加
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5>已存在的分类</h5>
|
||||
<div class="list-group">
|
||||
{% for main_id, cat in categories.items() %}
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>{{ cat.name }}</strong>(ID: {{ main_id }})
|
||||
<span class="badge" style="background-color: {{ cat.color }}; color: white;">{{ cat.color }}</span>
|
||||
{% if cat.get('private', False) %}
|
||||
<span class="badge bg-warning text-dark ms-2">私有</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ url_for('edit_main_category', main_id=main_id) }}"
|
||||
class="btn btn-sm btn-primary me-2">
|
||||
<i class="fas fa-edit"></i> 编辑
|
||||
</a>
|
||||
<a href="{{ url_for('delete_main_category', main_id=main_id) }}"
|
||||
class="btn btn-sm btn-danger"
|
||||
onclick="return confirm('确认删除整个主分类?')">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="mt-2 list-group list-group-flush">
|
||||
{% for sub_id, subData in cat.sub.items() %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
{{ subData.name }}(ID: {{ sub_id }})
|
||||
<span class="badge" style="background-color: {{ subData.color }}; color: white;">{{ subData.color }}</span>
|
||||
{% if cat.get('sub_private', {}).get(sub_id, False) %}
|
||||
<span class="badge bg-warning text-dark ms-2">私有</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
<div>
|
||||
<a href="{{ url_for('edit_sub_category', main_id=main_id, sub_id=sub_id) }}"
|
||||
class="btn btn-sm btn-primary me-2">
|
||||
<i class="fas fa-edit"></i> 编辑
|
||||
</a>
|
||||
<a href="{{ url_for('delete_sub_category', main_id=main_id, sub_id=sub_id) }}"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
onclick="return confirm('确认删除该子分类?')">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<a href="{{ url_for('index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> 返回首页
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
242
templates/edit_app.html
Normal file
@ -0,0 +1,242 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}编辑应用{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4>编辑应用</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">标题</label>
|
||||
<input type="text" name="title" class="form-control" value="{{ app.title }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">URL</label>
|
||||
<input type="url" name="url" class="form-control" value="{{ app.url }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">图标</label>
|
||||
<input type="hidden" name="icon" id="selectedIcon" value="{{ app.icon if app.icon else '' }}" required>
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="icon-preview me-3">
|
||||
<i id="iconPreview" class="{% if app.icon %}{{ app.icon }}{% else %}fas fa-question-circle{% endif %} fa-2x"></i>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#iconModal">
|
||||
<i class="fas fa-icons me-2"></i>选择图标
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">主分类</label>
|
||||
<select name="main_category" id="main_category" class="form-select" required>
|
||||
<option value="">请选择</option>
|
||||
{% for main_id, cat in categories.items() %}
|
||||
<option value="{{ main_id }}" {% if app.category.main == main_id %}selected{% endif %}>{{ cat.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">子分类</label>
|
||||
<select name="sub_category" id="sub_category" class="form-select" required>
|
||||
{% if app.category.main in categories %}
|
||||
{% for sub_id, subData in categories[app.category.main].sub.items() %}
|
||||
<option value="{{ sub_id }}" {% if app.category.sub == sub_id %}selected{% endif %}>{{ subData.name }}</option>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option value="">请先选择主分类</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">描述</label>
|
||||
<textarea name="description" class="form-control" rows="3">{{ app.description if app.description else '' }}</textarea>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" name="private" class="form-check-input" id="privateCheck" {% if app.private %}checked{% endif %}>
|
||||
<label class="form-check-label" for="privateCheck">私有应用(仅登录后可见)</label>
|
||||
</div>
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" class="btn btn-success me-md-2">
|
||||
<i class="fas fa-save me-2"></i>保存修改
|
||||
</button>
|
||||
<a href="{{ url_for('index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>返回
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图标选择模态框 -->
|
||||
<div class="modal fade" id="iconModal" tabindex="-1" aria-labelledby="iconModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="iconModalLabel">选择图标</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||
<input type="text" id="iconSearch" class="form-control" placeholder="搜索图标...">
|
||||
</div>
|
||||
<div class="row row-cols-3 row-cols-sm-4 row-cols-md-5 row-cols-lg-6 row-cols-xl-8 g-3" id="iconGrid">
|
||||
{% for icon, name in icons.items() %}
|
||||
<div class="col">
|
||||
<div class="icon-item p-3 text-center rounded {% if app.icon == icon %}selected{% endif %}"
|
||||
data-icon="{{ icon }}" title="{{ name }}">
|
||||
<i class="{{ icon }} fa-2x mb-2"></i>
|
||||
<div class="small text-truncate">{{ name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="me-auto">
|
||||
<span id="selectedIconName">
|
||||
{% if app.icon %}
|
||||
{{ icons[app.icon] if app.icon in icons else '自定义图标' }}
|
||||
{% else %}
|
||||
未选择图标
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" id="confirmIcon">确认选择</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let selectedIcon = '{{ app.icon if app.icon else "" }}';
|
||||
let selectedIconName = '{% if app.icon %}{{ icons[app.icon] if app.icon in icons else "自定义图标" }}{% else %}未选择图标{% endif %}';
|
||||
|
||||
// 图标搜索功能
|
||||
document.getElementById('iconSearch').addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
const iconItems = document.querySelectorAll('.icon-item');
|
||||
|
||||
iconItems.forEach(item => {
|
||||
const iconName = item.getAttribute('title').toLowerCase();
|
||||
const iconCode = item.dataset.icon.toLowerCase();
|
||||
if (iconName.includes(searchTerm) || iconCode.includes(searchTerm)) {
|
||||
item.closest('.col').style.display = 'block';
|
||||
} else {
|
||||
item.closest('.col').style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 图标点击选择
|
||||
document.querySelectorAll('.icon-item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
// 移除所有选中状态
|
||||
document.querySelectorAll('.icon-item').forEach(i => {
|
||||
i.classList.remove('selected');
|
||||
});
|
||||
|
||||
// 添加选中状态
|
||||
this.classList.add('selected');
|
||||
selectedIcon = this.dataset.icon;
|
||||
selectedIconName = this.getAttribute('title');
|
||||
|
||||
// 更新底部显示
|
||||
document.getElementById('selectedIconName').textContent = selectedIconName;
|
||||
});
|
||||
});
|
||||
|
||||
// 确认选择
|
||||
document.getElementById('confirmIcon').addEventListener('click', function() {
|
||||
if (selectedIcon) {
|
||||
document.getElementById('selectedIcon').value = selectedIcon;
|
||||
document.getElementById('iconPreview').className = selectedIcon + ' fa-2x';
|
||||
bootstrap.Modal.getInstance(document.getElementById('iconModal')).hide();
|
||||
} else {
|
||||
alert('请先选择一个图标');
|
||||
}
|
||||
});
|
||||
|
||||
// 主分类变化时加载子分类
|
||||
document.getElementById('main_category').addEventListener('change', function() {
|
||||
const mainId = this.value;
|
||||
if (!mainId) return;
|
||||
|
||||
fetch('/get_subcategories/' + mainId)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const subSelect = document.getElementById('sub_category');
|
||||
subSelect.innerHTML = '<option value="">请选择</option>';
|
||||
|
||||
for (const [id, subData] of Object.entries(data)) {
|
||||
const option = document.createElement('option');
|
||||
option.value = id;
|
||||
option.textContent = subData.name;
|
||||
subSelect.appendChild(option);
|
||||
}
|
||||
|
||||
// 尝试保留原来的子分类选择
|
||||
if ('{{ app.category.sub }}' && data['{{ app.category.sub }}']) {
|
||||
subSelect.value = '{{ app.category.sub }}';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 初始化时高亮已选图标
|
||||
if (selectedIcon) {
|
||||
const selectedItem = document.querySelector(`.icon-item[data-icon="${selectedIcon}"]`);
|
||||
if (selectedItem) {
|
||||
selectedItem.classList.add('selected');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.icon-preview {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.icon-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.icon-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
|
||||
.icon-item.selected {
|
||||
background-color: #e7f1ff;
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
#iconGrid {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#selectedIconName {
|
||||
font-weight: bold;
|
||||
color: #0d6efd;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
40
templates/edit_main_category.html
Normal file
@ -0,0 +1,40 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}编辑主分类{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4>编辑主分类</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label for="main_id" class="form-label">分类ID</label>
|
||||
<input type="text" class="form-control" id="main_id" value="{{ main_id }}" disabled>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">分类名称</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="{{ category.name }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="color" class="form-label">颜色</label>
|
||||
<input type="color" name="color" id="color" class="form-control form-control-color"
|
||||
value="{{ category.color }}" title="选择颜色">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="weight" class="form-label">权重</label>
|
||||
<input type="number" class="form-control" id="weight" name="weight" value="{{ category.get('weight', 0) }}">
|
||||
<small class="text-muted">权重越大,排序越靠前</small>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="private" name="private"
|
||||
{% if category.get('private', False) %}checked{% endif %}>
|
||||
<label class="form-check-label" for="private">私有分类</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{{ url_for('manage_categories') }}" class="btn btn-secondary">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
40
templates/edit_sub_category.html
Normal file
@ -0,0 +1,40 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}编辑子分类{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4>编辑子分类</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label for="sub_id" class="form-label">子分类ID</label>
|
||||
<input type="text" class="form-control" id="sub_id" name="sub_id" value="{{ sub_id }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="sub_name" class="form-label">子分类名称</label>
|
||||
<input type="text" class="form-control" id="sub_name" name="sub_name" value="{{ sub_name }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="color" class="form-label">颜色</label>
|
||||
<input type="color" name="color" id="color" class="form-control form-control-color"
|
||||
value="{{ color }}" title="选择颜色">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="weight" class="form-label">权重</label>
|
||||
<input type="number" class="form-control" id="weight" name="weight" value="{{ weight }}">
|
||||
<small class="text-muted">权重越大,排序越靠前</small>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="private" name="private"
|
||||
{% if is_private %}checked{% endif %}>
|
||||
<label class="form-check-label" for="private">私有分类</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{{ url_for('manage_categories') }}" class="btn btn-secondary">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
1401
templates/index.html
Normal file
68
templates/login.html
Normal file
@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录</title>
|
||||
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
.login-container {
|
||||
background-color: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,0.1);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
.login-title {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.captcha-img {
|
||||
cursor: pointer;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<h2 class="login-title">应用导航系统</h2>
|
||||
|
||||
<!-- 添加这部分代码来显示flash消息 -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">用户名</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">密码</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="captcha" class="form-label">验证码</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="captcha" name="captcha" required>
|
||||
<img src="{{ url_for('captcha') }}" class="captcha-img" onclick="this.src='{{ url_for('captcha') }}?'+Math.random()">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">登录</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
93
templates/manage.html
Normal file
@ -0,0 +1,93 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}应用管理{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||
<h4><i class="fas fa-cube me-2"></i>应用管理</h4>
|
||||
<div>
|
||||
<a href="{{ url_for('add_app') }}" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-plus"></i> 添加应用
|
||||
</a>
|
||||
<a href="{{ url_for('manage_categories') }}" class="btn btn-secondary btn-sm">
|
||||
<i class="fas fa-tags"></i> 管理分类
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<form class="row g-3">
|
||||
<div class="col-auto">
|
||||
<select name="category" class="form-select">
|
||||
<option value="">所有分类</option>
|
||||
{% for cat_id, cat_data in categories.items() %}
|
||||
<option value="{{ cat_id }}" {% if category_filter == cat_id %}selected{% endif %}>
|
||||
{{ cat_data.name }} (主分类)
|
||||
</option>
|
||||
{% for sub_id, sub_data in cat_data.sub.items() %}
|
||||
<option value="{{ sub_id }}" {% if category_filter == sub_id %}selected{% endif %}>
|
||||
{{ sub_data.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-primary">筛选</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>图标</th>
|
||||
<th>标题</th>
|
||||
<th>分类/权重</th>
|
||||
<th>URL</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for app in apps %}
|
||||
<tr>
|
||||
<td><i class="fas {{ app.icon }} fa-lg"></i></td>
|
||||
<td>
|
||||
{{ app.title }}
|
||||
{% if app.get('private', False) %}
|
||||
<span class="badge bg-warning text-dark ms-2">私有</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<span class="badge d-flex align-items-center" style="background-color: {{ categories[app.category.main].color }}; color: white;">
|
||||
{{ categories[app.category.main].name }}
|
||||
<span class="badge bg-light text-dark ms-1">{{ categories[app.category.main].weight }}</span>
|
||||
</span>
|
||||
{% if app.category.sub %}
|
||||
<span class="badge d-flex align-items-center" style="background-color: {{ categories[app.category.main].sub[app.category.sub].color }}; color: white;">
|
||||
{{ categories[app.category.main].sub[app.category.sub].name }}
|
||||
<span class="badge bg-light text-dark ms-1">{{ categories[app.category.main].sub[app.category.sub].weight }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td><a href="{{ app.url }}" target="_blank">{{ app.url[:30] }}...</a></td>
|
||||
<td>
|
||||
<a href="{{ url_for('edit_app', index=loop.index0) }}" class="btn btn-sm btn-warning">
|
||||
<i class="fas fa-edit"></i> 编辑
|
||||
</a>
|
||||
<a href="{{ url_for('delete_app', index=loop.index0) }}" class="btn btn-sm btn-danger" onclick="return confirm('确定删除吗?')">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
649
templates/settings.html
Normal file
@ -0,0 +1,649 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}系统设置{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4><i class="fas fa-cog me-2"></i>系统设置</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" enctype="multipart/form-data" class="needs-validation" novalidate>
|
||||
<!-- 主题设置 -->
|
||||
<div class="card mb-4 border-light shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0"><i class="fas fa-palette me-2"></i>显示设置</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="theme" class="form-label">主题设置</label>
|
||||
<select name="theme" id="theme" class="form-select" required>
|
||||
<option value="auto" {% if settings.theme == 'auto' %}selected{% endif %}>自动(跟随系统)</option>
|
||||
<option value="light" {% if settings.theme == 'light' %}selected{% endif %}>明亮模式</option>
|
||||
<option value="dark" {% if settings.theme == 'dark' %}selected{% endif %}>暗黑模式</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="card_style" class="form-label">卡片样式</label>
|
||||
<select name="card_style" id="card_style" class="form-select" required>
|
||||
<option value="normal" {% if settings.card_style == 'normal' %}selected{% endif %}>正常大小</option>
|
||||
<option value="compact" {% if settings.card_style == 'compact' %}selected{% endif %}>紧凑模式</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网站标题与Logo -->
|
||||
<div class="card mb-4 border-light shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0"><i class="fas fa-heading me-2"></i>网站标题与Logo</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="site_title" class="form-label">网站标题</label>
|
||||
<input type="text" class="form-control" id="site_title" name="site_title"
|
||||
value="{{ settings.site_title }}" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="show_logo" name="show_logo"
|
||||
{% if settings.show_logo %}checked{% endif %}>
|
||||
<label class="form-check-label" for="show_logo">在标题左侧显示Logo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Logo类型</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="logo_type" id="logo_type_icon"
|
||||
value="icon" {% if settings.logo_type == 'icon' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="logo_type_icon">使用图标</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="logo_type" id="logo_type_image"
|
||||
value="image" {% if settings.logo_type == 'image' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="logo_type_image">使用图片</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图标选择 -->
|
||||
<div id="logoIconSection" class="mb-3" style="{% if settings.logo_type != 'icon' %}display:none;{% endif %}">
|
||||
<label class="form-label">选择图标</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fas {{ settings.logo_icon }}"></i></span>
|
||||
<select class="form-select" name="logo_icon" id="logo_icon">
|
||||
{% for icon, name in icons.items() %}
|
||||
<option value="{{ icon }}" {% if icon.endswith(settings.logo_icon) %}selected{% endif %}>
|
||||
{{ name }} ({{ icon }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片选择 -->
|
||||
<div id="logoImageSection" style="{% if settings.logo_type != 'image' %}display:none;{% endif %}">
|
||||
<div class="mb-3">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="logo_image_type" id="logo_image_none" value="none"
|
||||
{% if not settings.logo_image %}checked{% endif %}>
|
||||
<label class="form-check-label" for="logo_image_none">使用默认图片</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="logo_image_type" id="logo_image_existing" value="existing"
|
||||
{% if settings.logo_image %}checked{% endif %}>
|
||||
<label class="form-check-label" for="logo_image_existing">选择已上传的Logo</label>
|
||||
<input type="hidden" name="selected_logo" id="selected_logo" value="{{ settings.logo_image.split('/')[-1] if settings.logo_image else '' }}">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary mt-2" data-bs-toggle="modal" data-bs-target="#logoModal">
|
||||
<i class="fas fa-image me-1"></i>选择Logo
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if settings.logo_image and settings.logo_type == 'image' %}
|
||||
<div class="current-logo mt-3">
|
||||
<p class="mb-1"><strong>当前Logo预览:</strong></p>
|
||||
<img src="{{ settings.logo_image }}" style="max-width: 100px; max-height: 100px;" class="img-thumbnail">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 明亮模式背景设置 -->
|
||||
<div class="card mb-4 border-light shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0"><i class="fas fa-sun me-2"></i>明亮模式背景</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="bg_type" id="bg_none" value="none"
|
||||
{% if settings.bg_image == 'none' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="bg_none">不使用背景</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="bg_type" id="bg_default" value="default"
|
||||
{% if settings.bg_image == '/static/background_light.jpg' or (not settings.bg_image or settings.bg_image == '') %}checked{% endif %}>
|
||||
<label class="form-check-label" for="bg_default">使用默认明亮背景</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="bg_type" id="bg_existing" value="existing"
|
||||
{% if settings.bg_image and settings.bg_image != '/static/background_light.jpg' and settings.bg_image != 'none' and not settings.bg_image.endswith('.mp4') %}checked{% endif %}>
|
||||
<label class="form-check-label" for="bg_existing">选择已上传的背景图片</label>
|
||||
<input type="hidden" name="selected_bg" id="selected_bg" value="{{ settings.bg_image.split('/')[-1] if settings.bg_image and settings.bg_image != '/static/background_light.jpg' and settings.bg_image != 'none' and not settings.bg_image.endswith('.mp4') else '' }}">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary mt-2" data-bs-toggle="modal" data-bs-target="#bgModal">
|
||||
<i class="fas fa-image me-1"></i>选择背景图片
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="bg_type" id="bg_video" value="video"
|
||||
{% if settings.bg_image and settings.bg_image.endswith('.mp4') %}checked{% endif %}>
|
||||
<label class="form-check-label" for="bg_video">选择已上传的背景视频</label>
|
||||
<input type="hidden" name="selected_video" id="selected_video" value="{{ settings.bg_image.split('/')[-1] if settings.bg_image and settings.bg_image.endswith('.mp4') else '' }}">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary mt-2" data-bs-toggle="modal" data-bs-target="#videoModal">
|
||||
<i class="fas fa-video me-1"></i>选择背景视频
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if settings.bg_image and settings.bg_image != '/static/background_light.jpg' and settings.bg_image != 'none' %}
|
||||
<div class="current-bg mt-3">
|
||||
<p class="mb-1"><strong>当前背景预览:</strong></p>
|
||||
{% if settings.bg_image.endswith('.mp4') %}
|
||||
<video width="300" height="150" controls class="img-thumbnail">
|
||||
<source src="{{ settings.bg_image }}" type="video/mp4">
|
||||
您的浏览器不支持视频预览
|
||||
</video>
|
||||
{% else %}
|
||||
<img src="{{ settings.bg_image }}" style="max-width: 300px; max-height: 150px;" class="img-thumbnail">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 暗黑模式背景设置 -->
|
||||
<div class="card mb-4 border-light shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0"><i class="fas fa-moon me-2"></i>暗黑模式背景</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="dark_bg_type" id="dark_bg_none" value="none"
|
||||
{% if settings.dark_bg_image == 'none' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="dark_bg_none">不使用背景</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="dark_bg_type" id="dark_bg_default" value="default"
|
||||
{% if settings.dark_bg_image == '/static/background_dark.jpg' or (not settings.dark_bg_image or settings.dark_bg_image == '') %}checked{% endif %}>
|
||||
<label class="form-check-label" for="dark_bg_default">使用默认暗黑背景</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="dark_bg_type" id="dark_bg_existing" value="existing"
|
||||
{% if settings.dark_bg_image and settings.dark_bg_image != '/static/background_dark.jpg' and settings.dark_bg_image != 'none' and not settings.dark_bg_image.endswith('.mp4') %}checked{% endif %}>
|
||||
<label class="form-check-label" for="dark_bg_existing">选择已上传的背景图片</label>
|
||||
<input type="hidden" name="selected_dark_bg" id="selected_dark_bg" value="{{ settings.dark_bg_image.split('/')[-1] if settings.dark_bg_image and settings.dark_bg_image != '/static/background_dark.jpg' and settings.dark_bg_image != 'none' and not settings.dark_bg_image.endswith('.mp4') else '' }}">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary mt-2" data-bs-toggle="modal" data-bs-target="#darkBgModal">
|
||||
<i class="fas fa-image me-1"></i>选择背景图片
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="dark_bg_type" id="dark_bg_video" value="video"
|
||||
{% if settings.dark_bg_image and settings.dark_bg_image.endswith('.mp4') %}checked{% endif %}>
|
||||
<label class="form-check-label" for="dark_bg_video">选择已上传的背景视频</label>
|
||||
<input type="hidden" name="selected_dark_video" id="selected_dark_video" value="{{ settings.dark_bg_image.split('/')[-1] if settings.dark_bg_image and settings.dark_bg_image.endswith('.mp4') else '' }}">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary mt-2" data-bs-toggle="modal" data-bs-target="#darkVideoModal">
|
||||
<i class="fas fa-video me-1"></i>选择背景视频
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if settings.dark_bg_image and settings.dark_bg_image != '/static/background_dark.jpg' and settings.dark_bg_image != 'none' %}
|
||||
<div class="current-bg mt-3">
|
||||
<p class="mb-1"><strong>当前背景预览:</strong></p>
|
||||
{% if settings.dark_bg_image.endswith('.mp4') %}
|
||||
<video width="300" height="150" controls class="img-thumbnail">
|
||||
<source src="{{ settings.dark_bg_image }}" type="video/mp4">
|
||||
您的浏览器不支持视频预览
|
||||
</video>
|
||||
{% else %}
|
||||
<img src="{{ settings.dark_bg_image }}" style="max-width: 300px; max-height: 150px;" class="img-thumbnail">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改密码 -->
|
||||
<div class="card mb-4 border-light shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0"><i class="fas fa-lock me-2"></i>安全设置</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card border-warning">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<i class="fas fa-key me-2"></i>修改管理员密码
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="old_password" class="form-label">当前密码</label>
|
||||
<input type="password" class="form-control" id="old_password" name="old_password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="new_password" class="form-label">新密码</label>
|
||||
<input type="password" class="form-control" id="new_password" name="new_password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirm_password" class="form-label">确认新密码</label>
|
||||
<input type="password" class="form-control" id="confirm_password" name="confirm_password">
|
||||
</div>
|
||||
<div class="alert alert-info mb-0">
|
||||
<i class="fas fa-info-circle me-2"></i>如果不修改密码,请留空这些字段
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a href="{{ url_for('index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>返回首页
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save me-2"></i>保存设置
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logo选择模态框 -->
|
||||
<div class="modal fade" id="logoModal" tabindex="-1" aria-labelledby="logoModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="logoModalLabel">选择Logo</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
{% for attachment in attachments if attachment.type == 'logo' %}
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card h-100 cursor-pointer" onclick="selectLogo('{{ attachment.filename }}')">
|
||||
<img src="{{ url_for('uploaded_logo', filename=attachment.filename) }}"
|
||||
class="card-img-top img-thumbnail"
|
||||
style="height: 100px; object-fit: contain;">
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 明亮模式背景图片选择模态框 -->
|
||||
<div class="modal fade" id="bgModal" tabindex="-1" aria-labelledby="bgModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="bgModalLabel">选择明亮模式背景图片</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
{% for attachment in attachments if attachment.type == 'background' %}
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card h-100 cursor-pointer" onclick="selectBg('{{ attachment.filename }}')">
|
||||
<img src="{{ url_for('uploaded_background', filename=attachment.filename) }}"
|
||||
class="card-img-top img-thumbnail"
|
||||
style="height: 150px; object-fit: cover;">
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 明亮模式背景视频选择模态框 -->
|
||||
<div class="modal fade" id="videoModal" tabindex="-1" aria-labelledby="videoModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="videoModalLabel">选择明亮模式背景视频</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
{% for attachment in attachments if attachment.type == 'video' %}
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card h-100 cursor-pointer" onclick="selectVideo('{{ attachment.filename }}')">
|
||||
<video class="card-img-top img-thumbnail" style="height: 150px; object-fit: cover;" muted>
|
||||
<source src="{{ url_for('uploaded_video', filename=attachment.filename) }}" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 暗黑模式背景图片选择模态框 -->
|
||||
<div class="modal fade" id="darkBgModal" tabindex="-1" aria-labelledby="darkBgModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="darkBgModalLabel">选择暗黑模式背景图片</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
{% for attachment in attachments if attachment.type == 'background' %}
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card h-100 cursor-pointer" onclick="selectDarkBg('{{ attachment.filename }}')">
|
||||
<img src="{{ url_for('uploaded_background', filename=attachment.filename) }}"
|
||||
class="card-img-top img-thumbnail"
|
||||
style="height: 150px; object-fit: cover;">
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 暗黑模式背景视频选择模态框 -->
|
||||
<div class="modal fade" id="darkVideoModal" tabindex="-1" aria-labelledby="darkVideoModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="darkVideoModalLabel">选择暗黑模式背景视频</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
{% for attachment in attachments if attachment.type == 'video' %}
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card h-100 cursor-pointer" onclick="selectDarkVideo('{{ attachment.filename }}')">
|
||||
<video class="card-img-top img-thumbnail" style="height: 150px; object-fit: cover;" muted>
|
||||
<source src="{{ url_for('uploaded_video', filename=attachment.filename) }}" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 全局选择函数
|
||||
function selectLogo(filename) {
|
||||
document.getElementById('selected_logo').value = filename;
|
||||
document.getElementById('logo_image_existing').checked = true;
|
||||
document.getElementById('logo_image_none').checked = false;
|
||||
|
||||
// 更新预览
|
||||
const previewContainer = document.querySelector('.current-logo');
|
||||
if (!previewContainer) {
|
||||
// 如果预览容器不存在,创建一个
|
||||
const logoImageSection = document.getElementById('logoImageSection');
|
||||
const previewDiv = document.createElement('div');
|
||||
previewDiv.className = 'current-logo mt-3';
|
||||
previewDiv.innerHTML = `
|
||||
<p class="mb-1"><strong>当前Logo预览:</strong></p>
|
||||
<img src="/upload/logo/${filename}" style="max-width: 100px; max-height: 100px;" class="img-thumbnail">
|
||||
`;
|
||||
logoImageSection.appendChild(previewDiv);
|
||||
} else {
|
||||
// 更新现有预览
|
||||
previewContainer.querySelector('img').src = `/upload/logo/${filename}`;
|
||||
}
|
||||
|
||||
// 使用原生 JavaScript 关闭模态框
|
||||
const logoModal = bootstrap.Modal.getInstance(document.getElementById('logoModal'));
|
||||
logoModal.hide();
|
||||
}
|
||||
|
||||
function selectBg(filename) {
|
||||
document.getElementById('selected_bg').value = filename;
|
||||
document.getElementById('bg_existing').checked = true;
|
||||
document.getElementById('bg_none').checked = false;
|
||||
document.getElementById('bg_default').checked = false;
|
||||
document.getElementById('bg_video').checked = false;
|
||||
|
||||
// 更新预览
|
||||
const previewContainer = document.querySelector('.current-bg');
|
||||
if (!previewContainer) {
|
||||
const bgSection = document.querySelector('input[name="bg_type"]').closest('.card');
|
||||
const previewDiv = document.createElement('div');
|
||||
previewDiv.className = 'current-bg mt-3';
|
||||
previewDiv.innerHTML = `
|
||||
<p class="mb-1"><strong>当前背景预览:</strong></p>
|
||||
<img src="/upload/background/${filename}" style="max-width: 300px; max-height: 150px;" class="img-thumbnail">
|
||||
`;
|
||||
bgSection.querySelector('.card-body').appendChild(previewDiv);
|
||||
} else {
|
||||
previewContainer.innerHTML = `
|
||||
<p class="mb-1"><strong>当前背景预览:</strong></p>
|
||||
<img src="/upload/background/${filename}" style="max-width: 300px; max-height: 150px;" class="img-thumbnail">
|
||||
`;
|
||||
}
|
||||
|
||||
const bgModal = bootstrap.Modal.getInstance(document.getElementById('bgModal'));
|
||||
bgModal.hide();
|
||||
}
|
||||
|
||||
function selectVideo(filename) {
|
||||
document.getElementById('selected_video').value = filename;
|
||||
document.getElementById('bg_video').checked = true;
|
||||
document.getElementById('bg_none').checked = false;
|
||||
document.getElementById('bg_default').checked = false;
|
||||
document.getElementById('bg_existing').checked = false;
|
||||
|
||||
// 更新预览
|
||||
const previewContainer = document.querySelector('.current-bg');
|
||||
if (!previewContainer) {
|
||||
const bgSection = document.querySelector('input[name="bg_type"]').closest('.card');
|
||||
const previewDiv = document.createElement('div');
|
||||
previewDiv.className = 'current-bg mt-3';
|
||||
previewDiv.innerHTML = `
|
||||
<p class="mb-1"><strong>当前背景预览:</strong></p>
|
||||
<video width="300" height="150" controls class="img-thumbnail">
|
||||
<source src="/upload/video/${filename}" type="video/mp4">
|
||||
您的浏览器不支持视频预览
|
||||
</video>
|
||||
`;
|
||||
bgSection.querySelector('.card-body').appendChild(previewDiv);
|
||||
} else {
|
||||
previewContainer.innerHTML = `
|
||||
<p class="mb-1"><strong>当前背景预览:</strong></p>
|
||||
<video width="300" height="150" controls class="img-thumbnail">
|
||||
<source src="/upload/video/${filename}" type="video/mp4">
|
||||
您的浏览器不支持视频预览
|
||||
</video>
|
||||
`;
|
||||
}
|
||||
|
||||
const videoModal = bootstrap.Modal.getInstance(document.getElementById('videoModal'));
|
||||
videoModal.hide();
|
||||
}
|
||||
|
||||
function selectDarkBg(filename) {
|
||||
document.getElementById('selected_dark_bg').value = filename;
|
||||
document.getElementById('dark_bg_existing').checked = true;
|
||||
document.getElementById('dark_bg_none').checked = false;
|
||||
document.getElementById('dark_bg_default').checked = false;
|
||||
document.getElementById('dark_bg_video').checked = false;
|
||||
|
||||
const previewContainer = document.querySelectorAll('.current-bg')[1];
|
||||
if (!previewContainer) {
|
||||
const darkBgSection = document.querySelector('input[name="dark_bg_type"]').closest('.card');
|
||||
const previewDiv = document.createElement('div');
|
||||
previewDiv.className = 'current-bg mt-3';
|
||||
previewDiv.innerHTML = `
|
||||
<p class="mb-1"><strong>当前背景预览:</strong></p>
|
||||
<img src="/upload/background/${filename}" style="max-width: 300px; max-height: 150px;" class="img-thumbnail">
|
||||
`;
|
||||
darkBgSection.querySelector('.card-body').appendChild(previewDiv);
|
||||
} else {
|
||||
previewContainer.innerHTML = `
|
||||
<p class="mb-1"><strong>当前背景预览:</strong></p>
|
||||
<img src="/upload/background/${filename}" style="max-width: 300px; max-height: 150px;" class="img-thumbnail">
|
||||
`;
|
||||
}
|
||||
|
||||
const darkBgModal = bootstrap.Modal.getInstance(document.getElementById('darkBgModal'));
|
||||
darkBgModal.hide();
|
||||
}
|
||||
|
||||
function selectDarkVideo(filename) {
|
||||
document.getElementById('selected_dark_video').value = filename;
|
||||
document.getElementById('dark_bg_video').checked = true;
|
||||
document.getElementById('dark_bg_none').checked = false;
|
||||
document.getElementById('dark_bg_default').checked = false;
|
||||
document.getElementById('dark_bg_existing').checked = false;
|
||||
|
||||
const previewContainer = document.querySelectorAll('.current-bg')[1];
|
||||
if (!previewContainer) {
|
||||
const darkBgSection = document.querySelector('input[name="dark_bg_type"]').closest('.card');
|
||||
const previewDiv = document.createElement('div');
|
||||
previewDiv.className = 'current-bg mt-3';
|
||||
previewDiv.innerHTML = `
|
||||
<p class="mb-1"><strong>当前背景预览:</strong></p>
|
||||
<video width="300" height="150" controls class="img-thumbnail">
|
||||
<source src="/upload/video/${filename}" type="video/mp4">
|
||||
您的浏览器不支持视频预览
|
||||
</video>
|
||||
`;
|
||||
darkBgSection.querySelector('.card-body').appendChild(previewDiv);
|
||||
} else {
|
||||
previewContainer.innerHTML = `
|
||||
<p class="mb-1"><strong>当前背景预览:</strong></p>
|
||||
<video width="300" height="150" controls class="img-thumbnail">
|
||||
<source src="/upload/video/${filename}" type="video/mp4">
|
||||
您的浏览器不支持视频预览
|
||||
</video>
|
||||
`;
|
||||
}
|
||||
|
||||
const darkVideoModal = bootstrap.Modal.getInstance(document.getElementById('darkVideoModal'));
|
||||
darkVideoModal.hide();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 明亮模式背景
|
||||
const bgRadios = document.querySelectorAll('input[name="bg_type"]');
|
||||
bgRadios.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
document.getElementById('selected_bg').disabled = this.value !== 'existing';
|
||||
document.getElementById('selected_video').disabled = this.value !== 'video';
|
||||
});
|
||||
});
|
||||
|
||||
// 暗黑模式背景
|
||||
const darkBgRadios = document.querySelectorAll('input[name="dark_bg_type"]');
|
||||
darkBgRadios.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
document.getElementById('selected_dark_bg').disabled = this.value !== 'existing';
|
||||
document.getElementById('selected_dark_video').disabled = this.value !== 'video';
|
||||
});
|
||||
});
|
||||
|
||||
// Logo图片类型
|
||||
const logoImageRadios = document.querySelectorAll('input[name="logo_image_type"]');
|
||||
logoImageRadios.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
document.getElementById('selected_logo').disabled = this.value !== 'existing';
|
||||
});
|
||||
});
|
||||
|
||||
// 动态显示/隐藏logo设置部分
|
||||
document.querySelectorAll('input[name="logo_type"]').forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
document.getElementById('logoIconSection').style.display = this.value === 'icon' ? 'block' : 'none';
|
||||
document.getElementById('logoImageSection').style.display = this.value === 'image' ? 'block' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// 表单验证
|
||||
const forms = document.querySelectorAll('.needs-validation');
|
||||
forms.forEach(form => {
|
||||
form.addEventListener('submit', function(event) {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
}, false);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card-header {
|
||||
font-weight: 600;
|
||||
}
|
||||
.form-check-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
.current-bg img, .current-logo img, .current-bg video {
|
||||
border: 2px solid #dee2e6;
|
||||
}
|
||||
.img-thumbnail {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.cursor-pointer:hover {
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2px);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
.card .card-header {
|
||||
border-bottom: 1px solid rgba(0,0,0,.125);
|
||||
}
|
||||
.card .card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.card .card-header.bg-light {
|
||||
background-color: #abcbeb !important;
|
||||
}
|
||||
.card .card-header h5 {
|
||||
font-size: 1.1rem;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.card.mb-4 {
|
||||
margin-bottom: 1.5rem!important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
BIN
upload/background/5dd4f5d3cd7b48eca9967fa063ea5cd9.png
Normal file
|
After Width: | Height: | Size: 720 KiB |
BIN
upload/background/d078c01de3be46deab9e85a94285d785.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
upload/logo/5378dda810964da9a7515ec844628738.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
upload/logo/b2c128cf2d4e47daa349c5e7f38c932c.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
upload/logo/f40e2eb965b24e358a5bba9523231f8f.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |