变迁左右滑动、自定义页脚等功能,应用编辑图标BUG修复
This commit is contained in:
parent
ee114dda50
commit
e9a755fbe1
23
README.md
23
README.md
@ -14,29 +14,28 @@
|
||||
### 界面优化
|
||||
1. 每行卡片数量支持自定义(4个、5个、6个、8个)
|
||||
2. 应用的图标支持上传图片自定义 - 已完成
|
||||
3. 首页增加页脚
|
||||
4. 一级分类和二级分类固定宽度,超出宽度可左右滑动查看
|
||||
3. 自定义页脚 - 已完成
|
||||
4. 一级分类和二级分类固定宽度,超出宽度可左右滑动查看 - 已完成
|
||||
5. 气泡的小箭头靠左对齐 - 已完成
|
||||
6. logo图标设置区分明亮和暗黑模式
|
||||
7. 私有应用在首页添加标识
|
||||
8. 首页卡片右键菜单
|
||||
9. 应用支持配置多个URL,左键打开默认URL,右键可选择URL进行复制地址或者打开或者编辑应用
|
||||
10. 新增应用界面便捷增加分类
|
||||
11. 新增应用界面便捷增加图标图片
|
||||
|
||||
### 功能增强
|
||||
8. 应用管理页支持分页和按一级分类筛选
|
||||
9. 应用分页和附件分页功能
|
||||
10. 网站图标自动获取功能
|
||||
11. 书签收藏工具
|
||||
1. 应用管理页支持分页和按一级分类筛选 - 已完成
|
||||
2. 应用分页和附件分页功能 - 已完成
|
||||
3. 网站图标自动获取功能
|
||||
4. 书签收藏工具
|
||||
|
||||
### BUG修复
|
||||
12. 应用编辑页面没有回显带入图片
|
||||
1. 应用编辑页面没有回显带入图片 - 已解决
|
||||
|
||||
### 批量操作
|
||||
13. 应用批量选择功能:
|
||||
1. 应用批量选择功能:
|
||||
- 批量删除
|
||||
- 批量设置私有化/公有化
|
||||
14. 附件批量选择功能:
|
||||
2. 附件批量选择功能:
|
||||
- 批量删除
|
||||
|
||||
15. 新增应用界面便捷增加分类
|
||||
16. 新增应用界面便捷增加图标图片
|
||||
87
app.py
87
app.py
@ -11,6 +11,7 @@ from functools import wraps
|
||||
from werkzeug.utils import secure_filename
|
||||
import requests
|
||||
from urllib.parse import urlparse
|
||||
from math import ceil
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'your_secret_key_here' # 请更改为安全的密钥
|
||||
@ -44,6 +45,7 @@ SETTINGS_FILE = os.path.join(DATA_DIR, 'settings.json')
|
||||
|
||||
GUEST_SETTINGS_FILE = os.path.join(DATA_DIR, 'guest_settings.json')
|
||||
|
||||
|
||||
def migrate_settings(settings):
|
||||
"""迁移旧版设置到新版格式"""
|
||||
if 'admin_password' in settings:
|
||||
@ -59,6 +61,12 @@ def migrate_settings(settings):
|
||||
'123456',
|
||||
method='pbkdf2:sha256'
|
||||
)
|
||||
|
||||
# 确保有页脚设置
|
||||
if 'footer_html' not in settings:
|
||||
settings[
|
||||
'footer_html'] = '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
@ -78,7 +86,8 @@ def init_settings():
|
||||
"logo_icon": "fa-th-list",
|
||||
"logo_image": "",
|
||||
"uploaded_backgrounds": [],
|
||||
"uploaded_logos": []
|
||||
"uploaded_logos": [],
|
||||
"footer_html": '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
|
||||
}
|
||||
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_settings, f, ensure_ascii=False, indent=2)
|
||||
@ -90,6 +99,14 @@ def init_settings():
|
||||
if 'admin_password_hash' not in settings:
|
||||
# 需要迁移
|
||||
settings = migrate_settings(settings)
|
||||
# 添加新字段的默认值
|
||||
if 'footer_html' not in settings:
|
||||
settings['footer_html'] = '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
|
||||
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(settings, f, ensure_ascii=False, indent=2)
|
||||
elif 'footer_html' not in settings:
|
||||
# 已有密码哈希但没有页脚设置的情况
|
||||
settings['footer_html'] = '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
|
||||
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(settings, f, ensure_ascii=False, indent=2)
|
||||
|
||||
@ -356,6 +373,7 @@ def system_settings():
|
||||
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')
|
||||
settings['footer_html'] = request.form.get('footer_html', '')
|
||||
|
||||
# 处理logo设置
|
||||
if settings['logo_type'] == 'icon':
|
||||
@ -467,9 +485,38 @@ def system_settings():
|
||||
@app.route('/attachments')
|
||||
@login_required
|
||||
def manage_attachments():
|
||||
type_filter = request.args.get('type', 'all')
|
||||
search_query = request.args.get('search', '').lower()
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = 10 # 每页显示10条数据
|
||||
|
||||
settings = load_settings()
|
||||
attachments = load_attachments()
|
||||
return render_template('attachments.html', settings=settings, attachments=attachments)
|
||||
|
||||
# 应用筛选
|
||||
filtered_attachments = []
|
||||
for attachment in attachments:
|
||||
# 类型筛选
|
||||
if type_filter != 'all' and attachment['type'] != type_filter:
|
||||
continue
|
||||
|
||||
# 搜索筛选
|
||||
if search_query and search_query not in attachment['filename'].lower():
|
||||
continue
|
||||
|
||||
filtered_attachments.append(attachment)
|
||||
|
||||
# 分页处理
|
||||
total_pages = ceil(len(filtered_attachments) / per_page)
|
||||
paginated_attachments = filtered_attachments[(page - 1) * per_page: page * per_page]
|
||||
|
||||
return render_template('attachments.html',
|
||||
settings=settings,
|
||||
attachments=paginated_attachments,
|
||||
current_page=page,
|
||||
total_pages=total_pages,
|
||||
search_query=search_query,
|
||||
type_filter=type_filter)
|
||||
|
||||
|
||||
@app.route('/upload_attachment', methods=['POST'])
|
||||
@ -573,21 +620,45 @@ def handle_settings():
|
||||
@login_required
|
||||
def index():
|
||||
category_filter = request.args.get('category')
|
||||
search_query = request.args.get('search', '').lower()
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = 10 # 每页显示10条数据
|
||||
|
||||
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
|
||||
# 分类筛选
|
||||
if category_filter and not (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
|
||||
continue
|
||||
|
||||
return render_template('manage.html', apps=apps, settings=settings, categories=categories)
|
||||
# 搜索筛选
|
||||
if search_query and not (search_query in app['title'].lower() or
|
||||
search_query in app['url'].lower() or
|
||||
search_query in app.get('description', '').lower() or
|
||||
search_query in app['category']['main'].lower() or
|
||||
search_query in app['category']['sub'].lower()):
|
||||
continue
|
||||
|
||||
filtered_apps.append(app)
|
||||
|
||||
# 分页处理
|
||||
total_pages = ceil(len(filtered_apps) / per_page)
|
||||
paginated_apps = filtered_apps[(page - 1) * per_page: page * per_page]
|
||||
|
||||
return render_template('manage.html',
|
||||
apps=paginated_apps,
|
||||
settings=settings,
|
||||
categories=categories,
|
||||
current_page=page,
|
||||
total_pages=total_pages,
|
||||
search_query=search_query,
|
||||
category_filter=category_filter)
|
||||
|
||||
@app.route('/')
|
||||
def navigation():
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
"show_logo": true,
|
||||
"logo_type": "image",
|
||||
"logo_icon": "fa-solid fa-th-list",
|
||||
"logo_image": "/upload/logo/5378dda810964da9a7515ec844628738.png",
|
||||
"logo_image": "/upload/logo/b2c128cf2d4e47daa349c5e7f38c932c.png",
|
||||
"dark_bg_rotate": false,
|
||||
"admin_password_hash": "scrypt:32768:8:1$mPFCfRRzOrcjE6z3$e72ef50a2d3f7292f64bcfc5e21f32c95ea8665414ea8d5f6b216735d68f151166c99fae21132c7949bd92ea32041f969cd4a471adb110a99328089541f7dccb"
|
||||
"admin_password_hash": "scrypt:32768:8:1$mPFCfRRzOrcjE6z3$e72ef50a2d3f7292f64bcfc5e21f32c95ea8665414ea8d5f6b216735d68f151166c99fae21132c7949bd92ea32041f969cd4a471adb110a99328089541f7dccb",
|
||||
"footer_html": "<div class=\"flex flex-col items-center text-slate-400 text-sm py-4\" style=\"margin-top:100px\">\r\n <div class=\"flex items-center\">\r\n <span>© 2023 AIDaohang. All rights reserved.</span>\r\n <span class=\"mx-2\">|</span>\r\n <span>Powered by <a href=\"https://github.com\" target=\"_blank\" class=\"hover:text-slate-200 ml-1\">AIDaohang</a></span>\r\n </div>\r\n</div>"
|
||||
}
|
||||
@ -32,7 +32,25 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5 class="border-bottom pb-2 mb-3">附件列表</h5>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h5 class="border-bottom pb-2 mb-0">附件列表</h5>
|
||||
<form class="d-flex" method="GET">
|
||||
<select name="type" class="form-select me-2" style="width: 120px;">
|
||||
<option value="all" {% if type_filter == 'all' %}selected{% endif %}>所有类型</option>
|
||||
<option value="logo" {% if type_filter == 'logo' %}selected{% endif %}>Logo</option>
|
||||
<option value="background" {% if type_filter == 'background' %}selected{% endif %}>背景图片</option>
|
||||
<option value="video" {% if type_filter == 'video' %}selected{% endif %}>背景视频</option>
|
||||
<option value="icon" {% if type_filter == 'icon' %}selected{% endif %}>图标图片</option>
|
||||
</select>
|
||||
<input type="text" name="search" class="form-control me-2" placeholder="搜索文件名..." value="{{ search_query }}">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
{% if type_filter != 'all' or search_query %}
|
||||
<a href="{{ url_for('manage_attachments') }}" class="btn btn-secondary ms-2">重置</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
@ -83,12 +101,37 @@
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">暂无附件</td>
|
||||
<td colspan="5" class="text-center">没有找到匹配的附件</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页导航 -->
|
||||
{% if total_pages > 1 %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item {% if current_page == 1 %}disabled{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('manage_attachments', page=current_page-1, type=type_filter, search=search_query) }}" aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% for page_num in range(1, total_pages + 1) %}
|
||||
<li class="page-item {% if page_num == current_page %}active{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('manage_attachments', page=page_num, type=type_filter, search=search_query) }}">{{ page_num }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
<li class="page-item {% if current_page == total_pages %}disabled{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('manage_attachments', page=current_page+1, type=type_filter, search=search_query) }}" aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<a href="{{ url_for('index') }}" class="btn btn-secondary">
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<link rel="stylesheet" href="/static/css/all.min.css">
|
||||
<style>
|
||||
body {
|
||||
padding-top: 20px; /* 修改了这里 */
|
||||
padding-top: 0px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
.navbar {
|
||||
@ -120,9 +120,9 @@
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<footer class="footer">
|
||||
<p>© 2025 导航管理系统</p>
|
||||
</footer>
|
||||
<footer class="footer">
|
||||
{% include 'footer.html' %}
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
@ -17,14 +17,19 @@
|
||||
<label class="form-label">URL</label>
|
||||
<input type="url" name="url" class="form-control" value="{{ app.url }}" required>
|
||||
</div>
|
||||
<!-- 在图标选择部分修改 -->
|
||||
<div class="mb-3">
|
||||
<!-- 修改后的图标选择部分 -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">图标</label>
|
||||
<input type="hidden" name="icon" id="selectedIcon" value="" required>
|
||||
<input type="hidden" name="icon" id="selectedIcon" value="{{ app.icon }}" 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>
|
||||
{% if app.icon.startswith('/upload/icon/') %}
|
||||
<img id="iconImagePreview" src="{{ app.icon }}" style="max-width: 32px; max-height: 32px;">
|
||||
<i id="iconPreview" class="fas fa-question-circle fa-2x" style="display: none;"></i>
|
||||
{% else %}
|
||||
<i id="iconPreview" class="{{ app.icon }} fa-2x"></i>
|
||||
<img id="iconImagePreview" src="" style="display: none; max-width: 32px; max-height: 32px;">
|
||||
{% endif %}
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary me-2" data-bs-toggle="modal" data-bs-target="#iconModal">
|
||||
<i class="fas fa-icons me-2"></i>选择图标
|
||||
@ -33,10 +38,10 @@
|
||||
<i class="fas fa-image me-2"></i>选择图片
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加图标图片选择模态框 -->
|
||||
<div class="modal fade" id="iconImageModal" tabindex="-1" aria-labelledby="iconImageModalLabel" aria-hidden="true">
|
||||
<!-- 图标图片选择模态框 -->
|
||||
<div class="modal fade" id="iconImageModal" tabindex="-1" aria-labelledby="iconImageModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@ -74,7 +79,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">主分类</label>
|
||||
<select name="main_category" id="main_category" class="form-select" required>
|
||||
@ -205,7 +210,10 @@
|
||||
document.getElementById('confirmIcon').addEventListener('click', function() {
|
||||
if (selectedIcon) {
|
||||
document.getElementById('selectedIcon').value = selectedIcon;
|
||||
document.getElementById('iconPreview').className = selectedIcon + ' fa-2x';
|
||||
const iconPreview = document.getElementById('iconPreview');
|
||||
iconPreview.className = selectedIcon + ' fa-2x';
|
||||
iconPreview.style.display = 'inline';
|
||||
document.getElementById('iconImagePreview').style.display = 'none';
|
||||
bootstrap.Modal.getInstance(document.getElementById('iconModal')).hide();
|
||||
} else {
|
||||
alert('请先选择一个图标');
|
||||
@ -238,19 +246,15 @@
|
||||
});
|
||||
|
||||
// 初始化时高亮已选图标
|
||||
if (selectedIcon) {
|
||||
if (selectedIcon && !selectedIcon.startsWith('/upload/icon/')) {
|
||||
const selectedItem = document.querySelector(`.icon-item[data-icon="${selectedIcon}"]`);
|
||||
if (selectedItem) {
|
||||
selectedItem.classList.add('selected');
|
||||
}
|
||||
}
|
||||
});
|
||||
// 图标图片选择功能
|
||||
let selectedIconImage = '';
|
||||
let selectedIconImageName = '';
|
||||
|
||||
// 图标图片搜索功能
|
||||
document.getElementById('iconImageSearch').addEventListener('input', function() {
|
||||
// 图标图片搜索功能
|
||||
document.getElementById('iconImageSearch').addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
const iconImageItems = document.querySelectorAll('.icon-image-item');
|
||||
|
||||
@ -262,10 +266,10 @@ document.getElementById('iconImageSearch').addEventListener('input', function()
|
||||
item.closest('.col').style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 图标图片点击选择
|
||||
document.querySelectorAll('.icon-image-item').forEach(item => {
|
||||
// 图标图片点击选择
|
||||
document.querySelectorAll('.icon-image-item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
// 移除所有选中状态
|
||||
document.querySelectorAll('.icon-image-item').forEach(i => {
|
||||
@ -280,21 +284,23 @@ document.querySelectorAll('.icon-image-item').forEach(item => {
|
||||
// 更新底部显示
|
||||
document.getElementById('selectedIconImageName').textContent = selectedIconImageName;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 确认选择图标图片
|
||||
document.getElementById('confirmIconImage').addEventListener('click', function() {
|
||||
// 确认选择图标图片
|
||||
document.getElementById('confirmIconImage').addEventListener('click', function() {
|
||||
if (selectedIconImage) {
|
||||
document.getElementById('selectedIcon').value = '/upload/icon/' + selectedIconImage;
|
||||
document.getElementById('iconPreview').style.display = 'none';
|
||||
const iconPath = '/upload/icon/' + selectedIconImage;
|
||||
document.getElementById('selectedIcon').value = iconPath;
|
||||
const iconImagePreview = document.getElementById('iconImagePreview');
|
||||
iconImagePreview.src = '/upload/icon/' + selectedIconImage;
|
||||
iconImagePreview.src = iconPath;
|
||||
iconImagePreview.style.display = 'inline';
|
||||
document.getElementById('iconPreview').style.display = 'none';
|
||||
bootstrap.Modal.getInstance(document.getElementById('iconImageModal')).hide();
|
||||
} else {
|
||||
alert('请先选择一张图片');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -325,12 +331,29 @@ document.getElementById('confirmIconImage').addEventListener('click', function()
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
#iconGrid {
|
||||
.icon-image-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.icon-image-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
|
||||
.icon-image-item.selected {
|
||||
background-color: #e7f1ff;
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
#iconGrid, #iconImageGrid {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#selectedIconName {
|
||||
#selectedIconName, #selectedIconImageName {
|
||||
font-weight: bold;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
6
templates/footer.html
Normal file
6
templates/footer.html
Normal file
@ -0,0 +1,6 @@
|
||||
<!-- templates/footer.html -->
|
||||
{% if settings and settings.footer_html %}
|
||||
{{ settings.footer_html|safe }}
|
||||
{% else %}
|
||||
<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>
|
||||
{% endif %}
|
||||
@ -22,8 +22,8 @@
|
||||
--ai-color: #f8961e;
|
||||
--tooltip-bg: rgb(149 236 105);
|
||||
--tooltip-text: black;
|
||||
--bg-image: none; /* 修改为none */
|
||||
--dark-bg-image: none; /* 修改为none */
|
||||
--bg-image: none;
|
||||
--dark-bg-image: none;
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
@ -162,7 +162,7 @@
|
||||
}
|
||||
|
||||
/* 右下角按钮组 */
|
||||
.floating-buttons {
|
||||
.floating-buttons {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
@ -170,8 +170,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.floating-btn {
|
||||
}
|
||||
.floating-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
@ -186,14 +186,14 @@
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
transition: all 0.3s;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.floating-btn:hover {
|
||||
.floating-btn:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 按钮提示文字 */
|
||||
.floating-btn::after {
|
||||
/* 按钮提示文字 */
|
||||
.floating-btn::after {
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
@ -206,109 +206,109 @@
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.floating-btn:hover::after {
|
||||
}
|
||||
.floating-btn:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 暗黑模式下的按钮样式 */
|
||||
body.dark-theme .floating-btn {
|
||||
/* 暗黑模式下的按钮样式 */
|
||||
body.dark-theme .floating-btn {
|
||||
background: #444;
|
||||
color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
/* 简洁模式卡片样式 */
|
||||
.app-item.compact {
|
||||
/* 简洁模式卡片样式 */
|
||||
.app-item.compact {
|
||||
padding: 10px 15px;
|
||||
height: 50px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.app-item.compact .app-icon {
|
||||
.app-item.compact .app-icon {
|
||||
margin-right: 15px;
|
||||
margin-bottom: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.app-item.compact .app-info {
|
||||
width: 100%;
|
||||
}
|
||||
.app-item.compact .app-title {
|
||||
.app-item.compact .app-title {
|
||||
font-size: 15px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
.app-item.compact .app-url,
|
||||
.app-item.compact .app-tags {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 暗黑主题 */
|
||||
body.dark-theme {
|
||||
/* 暗黑主题 */
|
||||
body.dark-theme {
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
--tooltip-bg: rgba(30, 30, 30, 0.95);
|
||||
--tooltip-text: #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 新增暗黑模式下标题颜色设置 */
|
||||
body.dark-theme h1 {
|
||||
/* 新增暗黑模式下标题颜色设置 */
|
||||
body.dark-theme h1 {
|
||||
color: white !important;
|
||||
}
|
||||
body.dark-theme .app-item {
|
||||
}
|
||||
body.dark-theme .app-item {
|
||||
background-color: #1e1e1edb;
|
||||
border-color: #333333a1;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
body.dark-theme .category-title {
|
||||
}
|
||||
body.dark-theme .category-title {
|
||||
border-bottom-color: #333;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
body.dark-theme .filter-container {
|
||||
}
|
||||
body.dark-theme .filter-container {
|
||||
background-color: #121212d4 !important;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
body.dark-theme .secondary-filters-container {
|
||||
}
|
||||
body.dark-theme .secondary-filters-container {
|
||||
background-color: #12121200 !important;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
body.dark-theme .filter-btn {
|
||||
}
|
||||
body.dark-theme .filter-btn {
|
||||
background-color: #2d2d2d !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
body.dark-theme .search-input {
|
||||
}
|
||||
body.dark-theme .search-input {
|
||||
background-color: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
border-color: #333;
|
||||
}
|
||||
body.dark-theme .search-results {
|
||||
}
|
||||
body.dark-theme .search-results {
|
||||
background-color: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
body.dark-theme .search-result-item {
|
||||
}
|
||||
body.dark-theme .search-result-item {
|
||||
border-bottom-color: #333;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
body.dark-theme .search-result-item:hover {
|
||||
}
|
||||
body.dark-theme .search-result-item:hover {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
body.dark-theme .floating-btn {
|
||||
}
|
||||
body.dark-theme .floating-btn {
|
||||
background-color: #444;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
body.dark-theme .floating-btn:hover {
|
||||
}
|
||||
body.dark-theme .floating-btn:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
body.dark-theme .app-url {
|
||||
}
|
||||
body.dark-theme .app-url {
|
||||
color: #aaa !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.filter-container {
|
||||
/* 修改后的筛选容器样式 - 支持横向滚动 */
|
||||
.filter-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
@ -323,13 +323,37 @@ body.dark-theme .app-url {
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 修改后的筛选行样式 - 支持横向滚动 */
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 10px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 10px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--primary-color) transparent;
|
||||
justify-content: center; /* 默认居中 */
|
||||
padding: 0 20px; /* 添加内边距 */
|
||||
}
|
||||
|
||||
/* 当内容超出时靠左 */
|
||||
.filter-row.scrollable {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
/* 自定义滚动条样式 */
|
||||
.filter-row::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
.filter-row::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.filter-row::-webkit-scrollbar-thumb {
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
@ -340,6 +364,8 @@ body.dark-theme .app-url {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap; /* 禁止按钮内文字换行 */
|
||||
flex-shrink: 0; /* 禁止按钮缩小 */
|
||||
}
|
||||
.filter-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
@ -348,17 +374,17 @@ body.dark-theme .app-url {
|
||||
.filter-btn.active {
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
}
|
||||
/* 一级标签样式 */
|
||||
.filter-btn[data-level="1"] {
|
||||
}
|
||||
/* 一级标签样式 */
|
||||
.filter-btn[data-level="1"] {
|
||||
font-size: 15px;
|
||||
padding: 4px 18px;
|
||||
}
|
||||
/* 二级标签样式 */
|
||||
.filter-btn[data-level="2"] {
|
||||
}
|
||||
/* 二级标签样式 */
|
||||
.filter-btn[data-level="2"] {
|
||||
font-size: 13px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
}
|
||||
/* 分类选择器颜色 */
|
||||
.filter-btn.dev {
|
||||
background-color: var(--dev-color, #4cc9f0) !important;
|
||||
@ -376,35 +402,58 @@ body.dark-theme .app-url {
|
||||
background-color: var(--ai-color, #f8961e) !important;
|
||||
}
|
||||
|
||||
.caret {
|
||||
.caret {
|
||||
margin-right: 8px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.caret.down {
|
||||
}
|
||||
.caret.down {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 修改后的二级筛选容器样式 - 支持横向滚动 */
|
||||
.secondary-filters-container {
|
||||
width: 100%;
|
||||
display: none;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
transition: background-color 0.3s ease;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 10px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--primary-color) transparent;
|
||||
justify-content: center; /* 默认居中 */
|
||||
padding: 0 20px; /* 添加内边距 */
|
||||
}
|
||||
.secondary-filters-container.show {
|
||||
|
||||
/* 当内容超出时靠左 */
|
||||
.secondary-filters-container.scrollable {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* 自定义二级筛选容器的滚动条样式 */
|
||||
.secondary-filters-container::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
.secondary-filters-container::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.secondary-filters-container::-webkit-scrollbar-thumb {
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.secondary-filters-container.show {
|
||||
display: flex;
|
||||
}
|
||||
.secondary-filters-wrapper {
|
||||
}
|
||||
.secondary-filters-wrapper {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
min-height: 50px;
|
||||
}
|
||||
.app-list-container {
|
||||
min-height: 30px;
|
||||
}
|
||||
.app-list-container {
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
}
|
||||
.category-title {
|
||||
}
|
||||
.category-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin: 30px 0 15px 0;
|
||||
@ -413,39 +462,39 @@ body.dark-theme .app-url {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
.category-title i {
|
||||
}
|
||||
.category-title i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.app-group {
|
||||
}
|
||||
.app-group {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.app-list {
|
||||
}
|
||||
.app-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.app-list {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
@media (max-width: 992px) {
|
||||
}
|
||||
@media (max-width: 992px) {
|
||||
.app-list {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.app-list {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
@media (max-width: 576px) {
|
||||
}
|
||||
@media (max-width: 576px) {
|
||||
.app-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.app-item {
|
||||
}
|
||||
.app-item {
|
||||
background-color: #ffffff69;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
@ -458,13 +507,13 @@ body.dark-theme .app-url {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
position: relative;
|
||||
}
|
||||
.app-item:hover {
|
||||
}
|
||||
.app-item:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 15px rgba(0,0,0,0.1);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
.app-icon {
|
||||
}
|
||||
.app-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 15px;
|
||||
@ -477,46 +526,46 @@ body.dark-theme .app-url {
|
||||
font-size: 18px;
|
||||
color: var(--primary-color);
|
||||
transition: background-color 0.3s ease;
|
||||
overflow: hidden; /* 添加这行确保内容不会溢出 */
|
||||
}
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-icon img {
|
||||
.app-icon img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
margin: 0 !important; /* 修改margin为0 */
|
||||
}
|
||||
.app-info {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.app-info {
|
||||
flex: 1;
|
||||
}
|
||||
.app-title {
|
||||
}
|
||||
.app-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
.app-url {
|
||||
}
|
||||
.app-url {
|
||||
font-size: 13px;
|
||||
color: #6c757d;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
.app-tags {
|
||||
}
|
||||
.app-tags {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin-top: 5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.app-tag {
|
||||
}
|
||||
.app-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
font-size: 12px;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
/* 分类标签颜色 */
|
||||
.tag-dev {
|
||||
background-color: var(--dev-color, #4cc9f0) !important;
|
||||
@ -534,16 +583,16 @@ body.dark-theme .app-url {
|
||||
background-color: var(--ai-color, #f8961e) !important;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
.no-results {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: 6c757d;
|
||||
}
|
||||
.app-group.hidden {
|
||||
}
|
||||
.app-group.hidden {
|
||||
display: none;
|
||||
}
|
||||
.loading-spinner {
|
||||
}
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@ -552,17 +601,17 @@ body.dark-theme .app-url {
|
||||
border-top-color: var(--primary-color);
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin-right: 10px;
|
||||
}
|
||||
@keyframes spin {
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
}
|
||||
|
||||
/* 修改后的聊天气泡提示框样式 */
|
||||
.app-description {
|
||||
/* 修改后的聊天气泡提示框样式 */
|
||||
.app-description {
|
||||
position: absolute;
|
||||
bottom: calc(100% + 10px);
|
||||
left: 20px; /* 调整左侧位置 */
|
||||
transform: none; /* 移除水平居中转换 */
|
||||
left: 20px;
|
||||
transform: none;
|
||||
background-color: var(--tooltip-bg);
|
||||
color: var(--tooltip-text);
|
||||
padding: 10px 15px;
|
||||
@ -576,22 +625,29 @@ body.dark-theme .app-url {
|
||||
z-index: 100;
|
||||
text-align: left;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* 修改后的聊天气泡小三角 - 移到左侧 */
|
||||
.app-description::after {
|
||||
/* 修改后的聊天气泡小三角 - 移到左侧 */
|
||||
.app-description::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 20px; /* 调整与气泡对齐 */
|
||||
transform: none; /* 移除水平居中转换 */
|
||||
left: 20px;
|
||||
transform: none;
|
||||
border-width: 8px;
|
||||
border-style: solid;
|
||||
border-color: var(--tooltip-bg) transparent transparent transparent;
|
||||
}
|
||||
.app-item:hover .app-description {
|
||||
}
|
||||
.app-item:hover .app-description {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #eee;
|
||||
text-align: center;
|
||||
color: #777;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -612,12 +668,12 @@ body.dark-theme .app-url {
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="search-container">
|
||||
<div class="search-container">
|
||||
<input type="text" class="search-input" placeholder="搜索应用、网址或分类..." id="searchInput">
|
||||
<div class="search-results" id="searchResults"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-container">
|
||||
<div class="filter-container">
|
||||
<div class="filter-row" id="primaryFilters">
|
||||
<button class="filter-btn active" data-level="1" data-filter="all">
|
||||
<span id="loadingSpinner" class="loading-spinner"></span>
|
||||
@ -627,14 +683,14 @@ body.dark-theme .app-url {
|
||||
<div class="secondary-filters-wrapper" id="secondaryFiltersWrapper">
|
||||
<!-- 二级标签容器将通过JS动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app-list-container" id="appListContainer">
|
||||
<div class="app-list-container" id="appListContainer">
|
||||
<div class="text-center py-5">
|
||||
<div class="loading-spinner" style="width: 40px; height: 40px; margin: 0 auto 15px;"></div>
|
||||
<p>正在加载应用数据...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="floating-buttons">
|
||||
<a href="/login?next=/" class="floating-btn" id="loginBtn" title="登录/退出"><i class="fas fa-sign-in-alt"></i></a>
|
||||
@ -643,7 +699,10 @@ body.dark-theme .app-url {
|
||||
<button class="floating-btn" id="compactToggle" title="简洁模式">📱</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<footer class="footer">
|
||||
{% include 'footer.html' %}
|
||||
</footer>
|
||||
<script>
|
||||
// 全局变量存储应用和分类数据
|
||||
let apps = [];
|
||||
let categories = {};
|
||||
@ -761,10 +820,10 @@ body.dark-theme .app-url {
|
||||
const weightB = b[1].weight || 0;
|
||||
return weightB - weightA; // 降序排列
|
||||
})
|
||||
);
|
||||
);
|
||||
|
||||
// 对每个主分类下的子分类也按权重排序
|
||||
Object.values(categories).forEach(cat => {
|
||||
// 对每个主分类下的子分类也按权重排序
|
||||
Object.values(categories).forEach(cat => {
|
||||
if (cat.sub) {
|
||||
cat.sub = Object.fromEntries(
|
||||
Object.entries(cat.sub).sort((a, b) => {
|
||||
@ -774,26 +833,25 @@ Object.values(categories).forEach(cat => {
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 渲染界面
|
||||
renderFilters();
|
||||
renderApps();
|
||||
loadingSpinner.style.display = 'none';
|
||||
primaryFilters.querySelector('[data-filter="all"]').textContent = '全部';
|
||||
} catch (error) {
|
||||
// 渲染界面
|
||||
renderFilters();
|
||||
renderApps();
|
||||
loadingSpinner.style.display = 'none';
|
||||
primaryFilters.querySelector('[data-filter="all"]').textContent = '全部';
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
appListContainer.innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
加载数据失败,请刷新页面重试
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 更新登录按钮状态
|
||||
function updateLoginButton() {
|
||||
// 更新登录按钮状态
|
||||
function updateLoginButton() {
|
||||
if (isLoggedIn) {
|
||||
loginBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i>';
|
||||
loginBtn.href = '/logout';
|
||||
@ -805,15 +863,15 @@ function updateLoginButton() {
|
||||
loginBtn.title = '登录';
|
||||
adminBtn.href = '/login?next=/manage'; // 未登录时跳转到登录页面
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据分类ID获取对应的颜色类
|
||||
function getColorForCategory(categoryId) {
|
||||
// 根据分类ID获取对应的颜色类
|
||||
function getColorForCategory(categoryId) {
|
||||
return categoryId;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成筛选按钮
|
||||
function renderFilters() {
|
||||
// 生成筛选按钮
|
||||
function renderFilters() {
|
||||
// 生成一级标签
|
||||
primaryFilters.innerHTML = '';
|
||||
const allPrimaryBtn = document.createElement('button');
|
||||
@ -859,10 +917,10 @@ function renderFilters() {
|
||||
|
||||
// 初始加载时显示所有二级标签
|
||||
showAllSecondaryFilters();
|
||||
}
|
||||
}
|
||||
|
||||
// 设置所有一级标签箭头的展开/收起状态
|
||||
function setAllPrimaryCaretDown(shouldDown) {
|
||||
// 设置所有一级标签箭头的展开/收起状态
|
||||
function setAllPrimaryCaretDown(shouldDown) {
|
||||
const primaryBtns = primaryFilters.querySelectorAll('[data-level="1"]');
|
||||
primaryBtns.forEach(btn => {
|
||||
if (btn.dataset.filter !== 'all') {
|
||||
@ -876,10 +934,10 @@ function setAllPrimaryCaretDown(shouldDown) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 设置特定一级标签箭头的展开/收起状态
|
||||
function setPrimaryCaretDown(primaryId, shouldDown) {
|
||||
// 设置特定一级标签箭头的展开/收起状态
|
||||
function setPrimaryCaretDown(primaryId, shouldDown) {
|
||||
const btn = primaryFilters.querySelector(`[data-filter="${primaryId}"]`);
|
||||
if (btn) {
|
||||
const caret = btn.querySelector('.caret');
|
||||
@ -891,10 +949,10 @@ function setPrimaryCaretDown(primaryId, shouldDown) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 显示所有二级标签
|
||||
function showAllSecondaryFilters() {
|
||||
// 显示所有二级标签
|
||||
function showAllSecondaryFilters() {
|
||||
collapseAllSecondary();
|
||||
|
||||
const container = document.createElement('div');
|
||||
@ -931,10 +989,10 @@ function showAllSecondaryFilters() {
|
||||
secondaryFiltersWrapper.innerHTML = '';
|
||||
secondaryFiltersWrapper.appendChild(container);
|
||||
currentExpandedPrimary = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 展开二级标签
|
||||
function expandSecondary(primaryFilterId) {
|
||||
// 展开二级标签
|
||||
function expandSecondary(primaryFilterId) {
|
||||
collapseAllSecondary();
|
||||
|
||||
const primaryBtn = primaryFilters.querySelector(`[data-filter="${primaryFilterId}"]`);
|
||||
@ -971,16 +1029,16 @@ function expandSecondary(primaryFilterId) {
|
||||
secondaryFiltersWrapper.appendChild(container);
|
||||
currentExpandedPrimary = primaryFilterId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 收起所有二级标签
|
||||
function collapseAllSecondary() {
|
||||
// 收起所有二级标签
|
||||
function collapseAllSecondary() {
|
||||
secondaryFiltersWrapper.innerHTML = '';
|
||||
currentExpandedPrimary = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置当前筛选条件
|
||||
function setFilter(primaryFilter, secondaryFilter) {
|
||||
// 设置当前筛选条件
|
||||
function setFilter(primaryFilter, secondaryFilter) {
|
||||
currentPrimaryFilter = primaryFilter;
|
||||
currentSecondaryFilter = secondaryFilter;
|
||||
|
||||
@ -1011,10 +1069,10 @@ function setFilter(primaryFilter, secondaryFilter) {
|
||||
|
||||
// 重新渲染应用列表
|
||||
renderApps();
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染应用列表
|
||||
function renderApps() {
|
||||
// 渲染应用列表
|
||||
function renderApps() {
|
||||
appListContainer.innerHTML = '';
|
||||
|
||||
// 按一级标签分组
|
||||
@ -1067,7 +1125,7 @@ function renderApps() {
|
||||
appList.className = 'app-list';
|
||||
|
||||
// 添加应用卡片
|
||||
filteredGroupApps.forEach(app => {
|
||||
filteredGroupApps.forEach(app => {
|
||||
const subCatName = categories[app.category.main]?.sub[app.category.sub]?.name || app.category.sub;
|
||||
const subCatColor = categories[app.category.main]?.sub[app.category.sub]?.color || mainCatData.color;
|
||||
|
||||
@ -1101,7 +1159,7 @@ filteredGroupApps.forEach(app => {
|
||||
|
||||
appItem.prepend(descriptionDiv);
|
||||
appList.appendChild(appItem);
|
||||
});
|
||||
});
|
||||
|
||||
groupDiv.appendChild(appList);
|
||||
appListContainer.appendChild(groupDiv);
|
||||
@ -1114,10 +1172,10 @@ filteredGroupApps.forEach(app => {
|
||||
noResults.textContent = '没有找到匹配的应用';
|
||||
appListContainer.appendChild(noResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:计算对比色
|
||||
function getContrastColor(hexColor) {
|
||||
// 辅助函数:计算对比色
|
||||
function getContrastColor(hexColor) {
|
||||
if (!hexColor) return '#ffffff';
|
||||
|
||||
// 转换hex颜色为RGB
|
||||
@ -1130,17 +1188,17 @@ function getContrastColor(hexColor) {
|
||||
|
||||
// 根据亮度返回黑色或白色
|
||||
return brightness > 128 ? '#000000' : '#ffffff';
|
||||
}
|
||||
}
|
||||
|
||||
// 全局设置
|
||||
let settings = {
|
||||
// 全局设置
|
||||
let settings = {
|
||||
theme: 'auto',
|
||||
card_style: 'normal',
|
||||
search_history: []
|
||||
};
|
||||
};
|
||||
|
||||
// 加载设置
|
||||
async function loadSettings() {
|
||||
// 加载设置
|
||||
async function loadSettings() {
|
||||
try {
|
||||
// 检查是否登录
|
||||
isLoggedIn = await checkLoginStatus();
|
||||
@ -1189,10 +1247,10 @@ async function loadSettings() {
|
||||
} catch (error) {
|
||||
console.error('加载设置失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置背景图片
|
||||
function setBackgroundImages(lightImage, darkImage) {
|
||||
// 设置背景图片
|
||||
function setBackgroundImages(lightImage, darkImage) {
|
||||
// 移除现有的视频背景
|
||||
document.querySelectorAll('.video-bg-container').forEach(el => el.remove());
|
||||
|
||||
@ -1240,10 +1298,10 @@ function setBackgroundImages(lightImage, darkImage) {
|
||||
setTimeout(() => {
|
||||
document.body.classList.add('bg-loaded');
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查登录状态
|
||||
async function checkLoginStatus() {
|
||||
// 检查登录状态
|
||||
async function checkLoginStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/check_login');
|
||||
const data = await response.json();
|
||||
@ -1252,14 +1310,14 @@ async function checkLoginStatus() {
|
||||
console.error('检查登录状态失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 应用设置
|
||||
function applySettings() {
|
||||
// 应用设置
|
||||
function applySettings() {
|
||||
// 应用主题
|
||||
document.body.classList.remove('dark-theme', 'light-theme');
|
||||
if (settings.theme === 'dark' ||
|
||||
(settings.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
(settings.theme === 'auto' && window.matchMedia('(prefers-color-sscheme: dark)').matches)) {
|
||||
document.body.classList.add('dark-theme');
|
||||
|
||||
// 显示/隐藏视频背景
|
||||
@ -1280,10 +1338,10 @@ function applySettings() {
|
||||
|
||||
// 更新主题按钮状态
|
||||
updateThemeButtonIcon();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新主题按钮图标
|
||||
function updateThemeButtonIcon() {
|
||||
// 更新主题按钮图标
|
||||
function updateThemeButtonIcon() {
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
if (document.body.classList.contains('dark-theme')) {
|
||||
themeToggle.textContent = '☀️';
|
||||
@ -1292,10 +1350,10 @@ function updateThemeButtonIcon() {
|
||||
themeToggle.textContent = '🌙';
|
||||
themeToggle.title = '切换至暗黑模式';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
function saveSettings() {
|
||||
// 保存设置
|
||||
function saveSettings() {
|
||||
if (isLoggedIn) {
|
||||
fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
@ -1313,10 +1371,10 @@ function saveSettings() {
|
||||
};
|
||||
localStorage.setItem('navSettings', JSON.stringify(settingsToSave));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索功能
|
||||
function setupSearch() {
|
||||
// 搜索功能
|
||||
function setupSearch() {
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const searchResults = document.getElementById('searchResults');
|
||||
let searchTimeout;
|
||||
@ -1354,7 +1412,7 @@ function setupSearch() {
|
||||
searchResults.innerHTML = '<div class="search-result-item">无搜索结果</div>';
|
||||
searchResults.style.display = 'block';
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
searchResults.style.display = 'none';
|
||||
}
|
||||
@ -1366,10 +1424,10 @@ function setupSearch() {
|
||||
searchResults.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 主题切换功能
|
||||
function setupThemeSwitcher() {
|
||||
// 主题切换功能
|
||||
function setupThemeSwitcher() {
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
|
||||
themeToggle.addEventListener('click', () => {
|
||||
@ -1393,20 +1451,20 @@ function setupThemeSwitcher() {
|
||||
updateThemeButtonIcon();
|
||||
saveSettings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 简洁模式切换
|
||||
function setupCompactToggle() {
|
||||
// 简洁模式切换
|
||||
function setupCompactToggle() {
|
||||
const compactToggle = document.getElementById('compactToggle');
|
||||
compactToggle.addEventListener('click', () => {
|
||||
settings.card_style = settings.card_style === 'compact' ? 'normal' : 'compact';
|
||||
applySettings();
|
||||
saveSettings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 后台管理按钮点击事件
|
||||
function setupAdminButton() {
|
||||
// 后台管理按钮点击事件
|
||||
function setupAdminButton() {
|
||||
adminBtn.addEventListener('click', async (e) => {
|
||||
if (!isLoggedIn) {
|
||||
e.preventDefault();
|
||||
@ -1420,16 +1478,38 @@ function setupAdminButton() {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadData();
|
||||
loadSettings();
|
||||
setupSearch();
|
||||
setupThemeSwitcher();
|
||||
setupCompactToggle();
|
||||
setupAdminButton();
|
||||
});
|
||||
function checkScrollable() {
|
||||
document.querySelectorAll('.filter-row, .secondary-filters-container').forEach(container => {
|
||||
// 检查内容宽度是否大于容器宽度
|
||||
const isScrollable = container.scrollWidth > container.clientWidth;
|
||||
|
||||
if(isScrollable) {
|
||||
container.classList.add('scrollable');
|
||||
} else {
|
||||
container.classList.remove('scrollable');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化时检查
|
||||
window.addEventListener('load', checkScrollable);
|
||||
// 窗口大小改变时检查
|
||||
window.addEventListener('resize', checkScrollable);
|
||||
// 内容变化时检查(如筛选器更新后)
|
||||
new MutationObserver(checkScrollable).observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@ -10,6 +10,15 @@
|
||||
<i class="fas fa-plus"></i> 添加应用
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<form class="d-flex" method="GET">
|
||||
<input type="hidden" name="category" value="{{ category_filter }}">
|
||||
<input type="text" name="search" class="form-control form-control-sm me-2" placeholder="搜索应用..." value="{{ search_query }}">
|
||||
<button type="submit" class="btn btn-sm btn-light">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
@ -31,6 +40,9 @@
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-primary">筛选</button>
|
||||
{% if category_filter or search_query %}
|
||||
<a href="{{ url_for('index') }}" class="btn btn-secondary ms-2">重置</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -86,10 +98,39 @@
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">没有找到匹配的应用</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页导航 -->
|
||||
{% if total_pages > 1 %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item {% if current_page == 1 %}disabled{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('index', page=current_page-1, category=category_filter, search=search_query) }}" aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% for page_num in range(1, total_pages + 1) %}
|
||||
<li class="page-item {% if page_num == current_page %}active{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('index', page=page_num, category=category_filter, search=search_query) }}">{{ page_num }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
<li class="page-item {% if current_page == total_pages %}disabled{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('index', page=current_page+1, category=category_filter, search=search_query) }}" aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -280,6 +280,27 @@
|
||||
</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-code me-2"></i>页脚设置</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="footer_html" class="form-label">自定义页脚HTML代码</label>
|
||||
<textarea class="form-control" id="footer_html" name="footer_html" rows="4">{{ settings.footer_html if settings.footer_html else '' }}</textarea>
|
||||
<div class="form-text">支持HTML代码,可用于添加版权信息、统计代码等</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-area mt-3 p-3 bg-light rounded">
|
||||
<h6>预览效果:</h6>
|
||||
<div id="footer_preview">
|
||||
{{ settings.footer_html|safe if settings.footer_html else '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改密码 -->
|
||||
<div class="card mb-4 border-light shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
@ -464,6 +485,10 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 页脚预览
|
||||
document.getElementById('footer_html').addEventListener('input', function() {
|
||||
document.getElementById('footer_preview').innerHTML = this.value;
|
||||
});
|
||||
// 全局选择函数
|
||||
function selectLogo(filename) {
|
||||
document.getElementById('selected_logo').value = filename;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user