优化移动端样式,优化后台逻辑
This commit is contained in:
parent
ef39536ed9
commit
75c691c183
@ -25,7 +25,7 @@
|
|||||||
## 待开发功能 ⏳
|
## 待开发功能 ⏳
|
||||||
### 界面优化
|
### 界面优化
|
||||||
1. logo图标设置区分明亮和暗黑模式
|
1. logo图标设置区分明亮和暗黑模式
|
||||||
2. 私有应用在首页添加标识
|
2. 私有应用在首页添加标识 - 已完成
|
||||||
3. 首页卡片右键菜单
|
3. 首页卡片右键菜单
|
||||||
4. 应用支持配置多个URL,左键打开默认URL,右键可选择URL进行复制地址或者打开或者编辑应用
|
4. 应用支持配置多个URL,左键打开默认URL,右键可选择URL进行复制地址或者打开或者编辑应用
|
||||||
5. 新增应用界面便捷增加分类
|
5. 新增应用界面便捷增加分类
|
||||||
@ -37,10 +37,10 @@
|
|||||||
2. 书签收藏工具
|
2. 书签收藏工具
|
||||||
|
|
||||||
### BUG修复
|
### BUG修复
|
||||||
1. 游客通过接口能查看私有应用;
|
1. 游客通过接口能查看私有应用 - 已修复
|
||||||
|
|
||||||
### 移动端适配
|
### 移动端适配
|
||||||
1. 后台页面适配
|
1. 后台页面适配 - 已完成
|
||||||
|
|
||||||
### 批量操作
|
### 批量操作
|
||||||
1. 应用批量选择功能:
|
1. 应用批量选择功能:
|
||||||
|
|||||||
42
app.py
42
app.py
@ -1033,6 +1033,48 @@ def change_password(username, new_password):
|
|||||||
else:
|
else:
|
||||||
print("错误:只能修改管理员 (admin) 的密码")
|
print("错误:只能修改管理员 (admin) 的密码")
|
||||||
|
|
||||||
|
@app.route('/app/add_from_category', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def add_app_from_category():
|
||||||
|
categories = load_categories()
|
||||||
|
icons = load_icons()
|
||||||
|
settings = load_settings()
|
||||||
|
attachments = load_attachments()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
flash('应用添加成功', 'success')
|
||||||
|
return redirect(url_for('manage_categories'))
|
||||||
|
|
||||||
|
return render_template('add_app.html',
|
||||||
|
categories=categories,
|
||||||
|
settings=settings,
|
||||||
|
icons=load_icons(),
|
||||||
|
attachments=attachments)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser(description='导航系统管理工具')
|
parser = argparse.ArgumentParser(description='导航系统管理工具')
|
||||||
|
|||||||
@ -63,5 +63,25 @@
|
|||||||
"filename": "c77f6bbbb335.png",
|
"filename": "c77f6bbbb335.png",
|
||||||
"type": "icon",
|
"type": "icon",
|
||||||
"upload_time": "2025-07-06 20:22:28"
|
"upload_time": "2025-07-06 20:22:28"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hpCMDHrCJVq3.png",
|
||||||
|
"type": "logo",
|
||||||
|
"upload_time": "2025-07-08 21:54:48"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "ksDMQ8aEwEph.png",
|
||||||
|
"type": "logo",
|
||||||
|
"upload_time": "2025-07-08 21:55:09"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "gfpWMYOvTTuI.png",
|
||||||
|
"type": "logo",
|
||||||
|
"upload_time": "2025-07-08 21:55:20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "G31hd5Kzu6t8.png",
|
||||||
|
"type": "logo",
|
||||||
|
"upload_time": "2025-07-08 21:55:29"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -155,6 +155,11 @@
|
|||||||
"name": "营养",
|
"name": "营养",
|
||||||
"color": "#4d908e",
|
"color": "#4d908e",
|
||||||
"weight": 2
|
"weight": 2
|
||||||
|
},
|
||||||
|
"swim": {
|
||||||
|
"name": "游泳",
|
||||||
|
"color": "#4895ef",
|
||||||
|
"weight": 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"weight": 4,
|
"weight": 4,
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"card_style": "compact",
|
"card_style": "compact",
|
||||||
"search_history": [],
|
"search_history": [],
|
||||||
"theme": "light",
|
"theme": "dark",
|
||||||
"bg_image": "/upload/background/5dd4f5d3cd7b.png",
|
"bg_image": "/upload/background/5dd4f5d3cd7b.png",
|
||||||
"dark_bg_image": "/upload/background/d078c01de3be.png",
|
"dark_bg_image": "/upload/background/d078c01de3be.png",
|
||||||
"site_title": "应用导航中心",
|
"site_title": "应用导航中心",
|
||||||
"show_logo": true,
|
"show_logo": true,
|
||||||
"logo_type": "image",
|
"logo_type": "image",
|
||||||
"logo_icon": "fa-solid fa-th-list",
|
"logo_icon": "fa-solid fa-th-list",
|
||||||
"logo_image": "/upload/logo/f40e2eb965b2.png",
|
"logo_image": "/upload/logo/hpCMDHrCJVq3.png",
|
||||||
"dark_bg_rotate": false,
|
"dark_bg_rotate": false,
|
||||||
"admin_password_hash": "pbkdf2:sha256:260000$ig0vNC2QdipLHgAy$6d734e5112bbdf94bc5cb49a38ca4764376ebb51c877a416d1a8f27d5fb5c415",
|
"admin_password_hash": "pbkdf2:sha256:260000$ig0vNC2QdipLHgAy$6d734e5112bbdf94bc5cb49a38ca4764376ebb51c877a416d1a8f27d5fb5c415",
|
||||||
"footer_html": "<div class=\"flex flex-col items-center text-slate-400 text-sm py-4\" style=\"margin-top:10px\">\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>"
|
"footer_html": "<div class=\"flex flex-col items-center text-slate-400 text-sm py-4\" style=\"margin-top:40px\">\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>"
|
||||||
}
|
}
|
||||||
@ -34,6 +34,8 @@
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h5 class="border-bottom pb-2 mb-0">附件列表</h5>
|
<h5 class="border-bottom pb-2 mb-0">附件列表</h5>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<form class="d-flex" method="GET">
|
<form class="d-flex" method="GET">
|
||||||
<select name="type" class="form-select me-2" style="width: 120px;">
|
<select name="type" class="form-select me-2" style="width: 120px;">
|
||||||
<option value="all" {% if type_filter == 'all' %}selected{% endif %}>所有类型</option>
|
<option value="all" {% if type_filter == 'all' %}selected{% endif %}>所有类型</option>
|
||||||
@ -51,6 +53,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 桌面端显示表格 -->
|
||||||
|
<div class="d-none d-md-block">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
@ -107,6 +112,60 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 移动端显示卡片 -->
|
||||||
|
<div class="d-md-none">
|
||||||
|
<div class="row">
|
||||||
|
{% for attachment in attachments %}
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-4">
|
||||||
|
{% if attachment.type == 'logo' %}
|
||||||
|
<img src="{{ url_for('uploaded_logo', filename=attachment.filename) }}" class="img-fluid rounded">
|
||||||
|
{% elif attachment.type == 'background' %}
|
||||||
|
<img src="{{ url_for('uploaded_background', filename=attachment.filename) }}" class="img-fluid rounded">
|
||||||
|
{% elif attachment.type == 'video' %}
|
||||||
|
<video width="100%" muted class="rounded">
|
||||||
|
<source src="{{ url_for('uploaded_video', filename=attachment.filename) }}" type="video/mp4">
|
||||||
|
</video>
|
||||||
|
{% else %}
|
||||||
|
<img src="{{ url_for('uploaded_icon', filename=attachment.filename) }}" class="img-fluid rounded">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<h6 class="mb-1">{{ attachment.filename }}</h6>
|
||||||
|
<p class="mb-1 text-muted small">
|
||||||
|
{% if attachment.type == 'logo' %}
|
||||||
|
Logo
|
||||||
|
{% elif attachment.type == 'background' %}
|
||||||
|
背景图片
|
||||||
|
{% elif attachment.type == 'video' %}
|
||||||
|
背景视频
|
||||||
|
{% else %}
|
||||||
|
图标图片
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<p class="mb-2 text-muted small">{{ attachment.upload_time }}</p>
|
||||||
|
<form method="POST" action="{{ url_for('delete_attachment_route', filename=attachment.filename) }}" class="mt-2">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('确定要删除这个附件吗?')">
|
||||||
|
<i class="fas fa-trash"></i> 删除
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-info text-center">没有找到匹配的附件</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 分页导航 -->
|
<!-- 分页导航 -->
|
||||||
{% if total_pages > 1 %}
|
{% if total_pages > 1 %}
|
||||||
|
|||||||
@ -3,62 +3,187 @@
|
|||||||
{% block title %}分类管理{% endblock %}
|
{% block title %}分类管理{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<!-- 添加子分类模态框 -->
|
||||||
|
<div class="modal fade" id="addSubCategoryModal" tabindex="-1" aria-labelledby="addSubCategoryModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="addSubCategoryModalLabel">添加子分类</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form method="post" action="{{ url_for('add_sub_category') }}" id="subCategoryForm">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="main_id" id="modalMainId">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sub_id" class="form-label">子分类 ID</label>
|
||||||
|
<input type="text" name="sub_id" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sub_name" class="form-label">子分类名称</label>
|
||||||
|
<input type="text" name="sub_name" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="weight" class="form-label">权重</label>
|
||||||
|
<input type="number" name="weight" class="form-control" value="0">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="color" class="form-label">颜色</label>
|
||||||
|
<input type="color" name="color" class="form-control form-control-color" value="#4895ef">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="sub_private_modal" name="private">
|
||||||
|
<label class="form-check-label" for="sub_private_modal">私有</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<button type="submit" class="btn btn-primary">添加</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加应用模态框 -->
|
||||||
|
<div class="modal fade" id="addAppModal" tabindex="-1" aria-labelledby="addAppModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="addAppModalLabel">添加应用</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form method="post" action="{{ url_for('add_app_from_category') }}">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="main_category" id="modalAppMainCategory">
|
||||||
|
<input type="hidden" name="sub_category" id="modalAppSubCategory">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="title" class="form-label">应用名称</label>
|
||||||
|
<input type="text" name="title" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="url" class="form-label">URL</label>
|
||||||
|
<input type="text" name="url" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="icon" class="form-label">图标</label>
|
||||||
|
<select name="icon" class="form-select" required>
|
||||||
|
{% for icon in icons %}
|
||||||
|
<option value="{{ icon }}">{{ icon }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">描述</label>
|
||||||
|
<textarea name="description" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="private" name="private">
|
||||||
|
<label class="form-check-label" for="private">私有</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<button type="submit" class="btn btn-primary">添加</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h5>添加主分类</h5>
|
<h5>添加主分类</h5>
|
||||||
<form method="post" action="{{ url_for('add_main_category') }}" class="d-flex align-items-center gap-2">
|
<form method="post" action="{{ url_for('add_main_category') }}">
|
||||||
<input type="text" name="id" class="form-control" placeholder="主分类 ID" required style="width: 120px;">
|
<div class="row g-3">
|
||||||
<input type="text" name="name" class="form-control" placeholder="主分类名称" required style="width: 150px;">
|
<div class="col-12 col-md-2">
|
||||||
<input type="number" name="weight" class="form-control" placeholder="权重" value="0" style="width: 80px;">
|
<input type="text" name="id" class="form-control" placeholder="主分类 ID" required>
|
||||||
<input type="color" name="color" class="form-control form-control-color" value="#4361ee" title="选择颜色" style="width: 50px;">
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="col-12 col-md-3">
|
||||||
|
<input type="text" name="name" class="form-control" placeholder="主分类名称" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-1">
|
||||||
|
<input type="number" name="weight" class="form-control" placeholder="权重" value="0">
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-1">
|
||||||
|
<input type="color" name="color" class="form-control form-control-color" value="#4361ee" title="选择颜色">
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-2 d-flex align-items-center">
|
||||||
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" id="main_private" name="private">
|
<input type="checkbox" class="form-check-input" id="main_private" name="private">
|
||||||
<label class="form-check-label" for="main_private">私有</label>
|
<label class="form-check-label" for="main_private">私有</label>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">
|
</div>
|
||||||
|
<div class="col-12 col-md-1">
|
||||||
|
<button type="submit" class="btn btn-primary w-100">
|
||||||
<i class="fas fa-plus"></i> 添加
|
<i class="fas fa-plus"></i> 添加
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h5>添加子分类</h5>
|
<h5>添加子分类</h5>
|
||||||
<form method="post" action="{{ url_for('add_sub_category') }}" class="d-flex align-items-center gap-2">
|
<form method="post" action="{{ url_for('add_sub_category') }}">
|
||||||
<select name="main_id" class="form-select" required style="width: 150px;">
|
<div class="row g-3">
|
||||||
|
<div class="col-12 col-md-2">
|
||||||
|
<select name="main_id" class="form-select" required>
|
||||||
<option value="">选择主分类</option>
|
<option value="">选择主分类</option>
|
||||||
{% for main_id, cat in categories.items() %}
|
{% for main_id, cat in categories.items()|sort(attribute='1.weight', reverse=True) %}
|
||||||
<option value="{{ main_id }}">{{ cat.name }}</option>
|
<option value="{{ main_id }}">{{ cat.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<input type="text" name="sub_id" class="form-control" placeholder="子分类 ID" required style="width: 120px;">
|
</div>
|
||||||
<input type="text" name="sub_name" class="form-control" placeholder="子分类名称" required style="width: 150px;">
|
<div class="col-12 col-md-2">
|
||||||
<input type="number" name="weight" class="form-control" placeholder="权重" value="0" style="width: 80px;">
|
<input type="text" name="sub_id" class="form-control" placeholder="子分类 ID" required>
|
||||||
<input type="color" name="color" class="form-control form-control-color" value="#4895ef" title="选择颜色" style="width: 50px;">
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="col-12 col-md-3">
|
||||||
|
<input type="text" name="sub_name" class="form-control" placeholder="子分类名称" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-1">
|
||||||
|
<input type="number" name="weight" class="form-control" placeholder="权重" value="0">
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-1">
|
||||||
|
<input type="color" name="color" class="form-control form-control-color" value="#4895ef" title="选择颜色">
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-2 d-flex align-items-center">
|
||||||
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" id="sub_private" name="private">
|
<input type="checkbox" class="form-check-input" id="sub_private" name="private">
|
||||||
<label class="form-check-label" for="sub_private">私有</label>
|
<label class="form-check-label" for="sub_private">私有</label>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-success">
|
</div>
|
||||||
|
<div class="col-12 col-md-1">
|
||||||
|
<button type="submit" class="btn btn-success w-100">
|
||||||
<i class="fas fa-plus"></i> 添加
|
<i class="fas fa-plus"></i> 添加
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h5>已存在的分类</h5>
|
<h5>已存在的分类</h5>
|
||||||
|
<!-- 桌面端显示 -->
|
||||||
|
<div class="d-none d-md-block">
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for main_id, cat in categories.items() %}
|
{% for main_id, cat in categories.items()|sort(attribute='1.weight', reverse=True) %}
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center flex-wrap">
|
||||||
<div>
|
<div class="mb-2 mb-md-0">
|
||||||
<strong>{{ cat.name }}</strong>(ID: {{ main_id }})
|
<strong>{{ cat.name }}</strong>(ID: {{ main_id }})
|
||||||
<span class="badge" style="background-color: {{ cat.color }}; color: white;">{{ cat.color }}</span>
|
<span class="badge" style="background-color: {{ cat.color }}; color: white;">{{ cat.get('weight', 0) }}</span>
|
||||||
{% if cat.get('private', False) %}
|
{% if cat.get('private', False) %}
|
||||||
<span class="badge bg-warning text-dark ms-2">私有</span>
|
<span class="badge bg-warning text-dark ms-2">私有</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<button class="btn btn-sm btn-success me-2"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addSubCategoryModal"
|
||||||
|
onclick="setMainId('{{ main_id }}')">
|
||||||
|
<i class="fas fa-plus"></i> 添加子分类
|
||||||
|
</button>
|
||||||
<a href="{{ url_for('edit_main_category', main_id=main_id) }}"
|
<a href="{{ url_for('edit_main_category', main_id=main_id) }}"
|
||||||
class="btn btn-sm btn-primary me-2">
|
class="btn btn-sm btn-primary me-2">
|
||||||
<i class="fas fa-edit"></i> 编辑
|
<i class="fas fa-edit"></i> 编辑
|
||||||
@ -71,16 +196,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="mt-2 list-group list-group-flush">
|
<ul class="mt-2 list-group list-group-flush">
|
||||||
{% for sub_id, subData in cat.sub.items() %}
|
{% for sub_id, subData in cat.sub.items()|sort(attribute='1.weight', reverse=True) %}
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
<li class="list-group-item d-flex justify-content-between align-items-center flex-wrap">
|
||||||
<span>
|
<span class="mb-2 mb-md-0">
|
||||||
{{ subData.name }}(ID: {{ sub_id }})
|
{{ subData.name }}(ID: {{ sub_id }})
|
||||||
<span class="badge" style="background-color: {{ subData.color }}; color: white;">{{ subData.color }}</span>
|
<span class="badge" style="background-color: {{ subData.color }}; color: white;">{{ subData.get('weight', 0) }}</span>
|
||||||
{% if cat.get('sub_private', {}).get(sub_id, False) %}
|
{% if cat.get('sub_private', {}).get(sub_id, False) %}
|
||||||
<span class="badge bg-warning text-dark ms-2">私有</span>
|
<span class="badge bg-warning text-dark ms-2">私有</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
|
<button class="btn btn-sm btn-success me-2"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addAppModal"
|
||||||
|
onclick="setCategory('{{ main_id }}', '{{ sub_id }}')">
|
||||||
|
<i class="fas fa-plus"></i> 添加应用
|
||||||
|
</button>
|
||||||
<a href="{{ url_for('edit_sub_category', main_id=main_id, sub_id=sub_id) }}"
|
<a href="{{ url_for('edit_sub_category', main_id=main_id, sub_id=sub_id) }}"
|
||||||
class="btn btn-sm btn-primary me-2">
|
class="btn btn-sm btn-primary me-2">
|
||||||
<i class="fas fa-edit"></i> 编辑
|
<i class="fas fa-edit"></i> 编辑
|
||||||
@ -99,6 +230,86 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 移动端显示 -->
|
||||||
|
<div class="d-md-none">
|
||||||
|
<div class="accordion" id="categoryAccordion">
|
||||||
|
{% for main_id, cat in categories.items()|sort(attribute='1.weight', reverse=True) %}
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center" style="background-color: {{ cat.color }}; color: white;">
|
||||||
|
<div>
|
||||||
|
<strong>{{ cat.name }}</strong> (ID: {{ main_id }})
|
||||||
|
<span class="badge bg-light text-dark ms-2">{{ cat.get('weight', 0) }}</span>
|
||||||
|
{% if cat.get('private', False) %}
|
||||||
|
<span class="badge bg-warning text-dark ms-2">私有</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-sm btn-light me-2" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{ loop.index }}" aria-expanded="true" aria-controls="collapse{{ loop.index }}">
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="collapse{{ loop.index }}" class="collapse show" data-bs-parent="#categoryAccordion">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between mb-3">
|
||||||
|
<button class="btn btn-sm btn-success flex-grow-1 me-2"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addSubCategoryModal"
|
||||||
|
onclick="setMainId('{{ main_id }}')">
|
||||||
|
<i class="fas fa-plus"></i> 添加子分类
|
||||||
|
</button>
|
||||||
|
<a href="{{ url_for('edit_main_category', main_id=main_id) }}"
|
||||||
|
class="btn btn-sm btn-primary flex-grow-1 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 flex-grow-1"
|
||||||
|
onclick="return confirm('确认删除整个主分类?')">
|
||||||
|
<i class="fas fa-trash"></i> 删除
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6 class="mb-3">子分类列表</h6>
|
||||||
|
{% for sub_id, subData in cat.sub.items()|sort(attribute='1.weight', reverse=True) %}
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<div>
|
||||||
|
<strong>{{ subData.name }}</strong> (ID: {{ sub_id }})
|
||||||
|
<span class="badge" style="background-color: {{ subData.color }}; color: white;">{{ subData.get('weight', 0) }}</span>
|
||||||
|
{% if cat.get('sub_private', {}).get(sub_id, False) %}
|
||||||
|
<span class="badge bg-warning text-dark ms-2">私有</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<button class="btn btn-sm btn-success flex-grow-1 me-2"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addAppModal"
|
||||||
|
onclick="setCategory('{{ main_id }}', '{{ sub_id }}')">
|
||||||
|
<i class="fas fa-plus"></i> 添加应用
|
||||||
|
</button>
|
||||||
|
<a href="{{ url_for('edit_sub_category', main_id=main_id, sub_id=sub_id) }}"
|
||||||
|
class="btn btn-sm btn-primary flex-grow-1 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 flex-grow-1"
|
||||||
|
onclick="return confirm('确认删除该子分类?')">
|
||||||
|
<i class="fas fa-trash"></i> 删除
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<a href="{{ url_for('index') }}" class="btn btn-secondary">
|
<a href="{{ url_for('index') }}" class="btn btn-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> 返回首页
|
<i class="fas fa-arrow-left"></i> 返回首页
|
||||||
@ -106,4 +317,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function setMainId(mainId) {
|
||||||
|
document.getElementById('modalMainId').value = mainId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCategory(mainId, subId) {
|
||||||
|
document.getElementById('modalAppMainCategory').value = mainId;
|
||||||
|
document.getElementById('modalAppSubCategory').value = subId;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -2,7 +2,7 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<title>应用导航</title>
|
<title>应用导航</title>
|
||||||
{% if settings and settings.logo_type == 'image' and settings.logo_image %}
|
{% if settings and settings.logo_type == 'image' and settings.logo_image %}
|
||||||
<link rel="icon" href="{{ settings.logo_image }}">
|
<link rel="icon" href="{{ settings.logo_image }}">
|
||||||
@ -25,6 +25,9 @@
|
|||||||
--bg-image: none;
|
--bg-image: none;
|
||||||
--dark-bg-image: none;
|
--dark-bg-image: none;
|
||||||
}
|
}
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
@ -33,6 +36,8 @@
|
|||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
color: #333;
|
color: #333;
|
||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
overflow-x: hidden;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 新增私有卡片标记样式 */
|
/* 新增私有卡片标记样式 */
|
||||||
@ -45,7 +50,7 @@
|
|||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0 30px 30px 0;
|
border-width: 0 30px 30px 0;
|
||||||
border-color: transparent #f8961e transparent transparent;
|
border-color: transparent #f8961e transparent transparent;
|
||||||
border-radius: 0 12px 0 0; /* 只圆角右上角 */
|
border-radius: 0 12px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.private-badge::after {
|
.private-badge::after {
|
||||||
@ -152,6 +157,8 @@
|
|||||||
margin: 20px auto;
|
margin: 20px auto;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 15px;
|
||||||
}
|
}
|
||||||
.search-input {
|
.search-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -164,7 +171,7 @@
|
|||||||
}
|
}
|
||||||
.search-results {
|
.search-results {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: calc(100% - 30px);
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 0 0 10px 10px;
|
border-radius: 0 0 10px 10px;
|
||||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||||
@ -237,9 +244,24 @@
|
|||||||
/* 暗黑模式下的按钮样式 */
|
/* 暗黑模式下的按钮样式 */
|
||||||
body.dark-theme .floating-btn {
|
body.dark-theme .floating-btn {
|
||||||
background: #444;
|
background: #444;
|
||||||
color: #eee;
|
color: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floating-btn#addBtn {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-btn#addBtn:hover {
|
||||||
|
background-color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-theme .floating-btn#addBtn {
|
||||||
|
background-color: #444;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 简洁模式卡片样式 */
|
/* 简洁模式卡片样式 */
|
||||||
.app-item.compact {
|
.app-item.compact {
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
@ -330,7 +352,7 @@
|
|||||||
color: #aaa !important;
|
color: #aaa !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 修改后的筛选容器样式 - 支持横向滚动 */
|
/* 修改后的筛选容器样式 */
|
||||||
.filter-container {
|
.filter-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -346,9 +368,14 @@
|
|||||||
padding: 15px;
|
padding: 15px;
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
margin-left: -15px;
|
||||||
|
margin-right: -15px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
width: calc(100% + 30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 修改后的筛选行样式 - 支持横向滚动 */
|
/* 修改后的筛选行样式 */
|
||||||
.filter-row {
|
.filter-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
@ -357,10 +384,13 @@
|
|||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: #c2c3c9d6;
|
scrollbar-color: #c2c3c9d6;
|
||||||
justify-content: center; /* 默认居中 */
|
justify-content: center;
|
||||||
padding: 0 20px; /* 添加内边距 */
|
padding: 0 15px;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin-left: -10px;
|
||||||
|
margin-right: -10px;
|
||||||
|
width: calc(100% + 20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 当内容超出时靠左 */
|
/* 当内容超出时靠左 */
|
||||||
@ -417,8 +447,8 @@ body.dark-theme .filter-row::after {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
white-space: nowrap; /* 禁止按钮内文字换行 */
|
white-space: nowrap;
|
||||||
flex-shrink: 0; /* 禁止按钮缩小 */
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.filter-btn:hover {
|
.filter-btn:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
@ -463,7 +493,7 @@ body.dark-theme .filter-row::after {
|
|||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 修改后的二级筛选容器样式 - 支持横向滚动 */
|
/* 修改后的二级筛选容器样式 */
|
||||||
.secondary-filters-container {
|
.secondary-filters-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: none;
|
display: none;
|
||||||
@ -473,10 +503,13 @@ body.dark-theme .filter-row::after {
|
|||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: #c2c3c9d6;
|
scrollbar-color: #c2c3c9d6;
|
||||||
justify-content: center; /* 默认居中 */
|
justify-content: center;
|
||||||
padding: 0 20px; /* 添加内边距 */
|
padding: 0 15px;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin-left: -10px;
|
||||||
|
margin-right: -10px;
|
||||||
|
width: calc(100% + 20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 私有主分类 */
|
/* 私有主分类 */
|
||||||
@ -542,6 +575,7 @@ body.dark-theme .secondary-filters-container::after {
|
|||||||
.app-list-container {
|
.app-list-container {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.category-title {
|
.category-title {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@ -558,11 +592,13 @@ body.dark-theme .secondary-filters-container::after {
|
|||||||
}
|
}
|
||||||
.app-group {
|
.app-group {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.app-list {
|
.app-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, 1fr);
|
grid-template-columns: repeat(5, 1fr);
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.app-list {
|
.app-list {
|
||||||
@ -578,11 +614,34 @@ body.dark-theme .secondary-filters-container::after {
|
|||||||
.app-list {
|
.app-list {
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
|
body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
.filter-container {
|
||||||
|
margin-left: -15px;
|
||||||
|
margin-right: -15px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
width: calc(100% + 30px);
|
||||||
|
}
|
||||||
|
.floating-btn::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
.app-list {
|
.app-list {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.filter-container {
|
||||||
|
margin-left: -10px;
|
||||||
|
margin-right: -10px;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
width: calc(100% + 20px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.app-item {
|
.app-item {
|
||||||
background-color: #ffffff69;
|
background-color: #ffffff69;
|
||||||
@ -597,6 +656,7 @@ body.dark-theme .secondary-filters-container::after {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.app-item:hover {
|
.app-item:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
@ -737,6 +797,7 @@ body.dark-theme .secondary-filters-container::after {
|
|||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #777;
|
color: #777;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@ -783,10 +844,12 @@ body.dark-theme .secondary-filters-container::after {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="floating-buttons">
|
<div class="floating-buttons">
|
||||||
<a href="/login?next=/" class="floating-btn" id="loginBtn" title="登录/退出"><i class="fas fa-sign-in-alt"></i></a>
|
<button class="floating-btn" id="backToTopBtn" title="返回顶部" style="display: none;">↑</button>
|
||||||
<a href="/manage" class="floating-btn" id="adminBtn" title="后台管理"><i class="fas fa-cog"></i></a>
|
|
||||||
<button class="floating-btn" id="themeToggle" title="切换主题">🌙</button>
|
<button class="floating-btn" id="themeToggle" title="切换主题">🌙</button>
|
||||||
<button class="floating-btn" id="compactToggle" title="简洁模式">📱</button>
|
<button class="floating-btn" id="compactToggle" title="简洁模式">📱</button>
|
||||||
|
<a href="/manage" class="floating-btn" id="adminBtn" title="后台管理"><i class="fas fa-cog" style="color: #6c757d;"></i></a>
|
||||||
|
<a href="/app/add" class="floating-btn" id="addBtn" title="添加应用"><i class="fas fa-plus" style="color: #28a745;"></i></a>
|
||||||
|
<a href="/login?next=/" class="floating-btn" id="loginBtn" title="登录/退出"><i class="fas fa-sign-in-alt" style="color: #007bff;"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
@ -805,10 +868,12 @@ body.dark-theme .secondary-filters-container::after {
|
|||||||
const loadingSpinner = document.getElementById('loadingSpinner');
|
const loadingSpinner = document.getElementById('loadingSpinner');
|
||||||
const loginBtn = document.getElementById('loginBtn');
|
const loginBtn = document.getElementById('loginBtn');
|
||||||
const adminBtn = document.getElementById('adminBtn');
|
const adminBtn = document.getElementById('adminBtn');
|
||||||
|
const backToTopBtn = document.getElementById('backToTopBtn');
|
||||||
|
|
||||||
let currentPrimaryFilter = 'all';
|
let currentPrimaryFilter = 'all';
|
||||||
let currentSecondaryFilter = 'all';
|
let currentSecondaryFilter = 'all';
|
||||||
let currentExpandedPrimary = null;
|
let currentExpandedPrimary = null;
|
||||||
|
let activeSecondaryFilter = null; // 新增:当前激活的二级筛选
|
||||||
|
|
||||||
// 从后端加载数据
|
// 从后端加载数据
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
@ -935,14 +1000,14 @@ body.dark-theme .secondary-filters-container::after {
|
|||||||
return weightB - weightA; // 降序排列
|
return weightB - weightA; // 降序排列
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 渲染界面
|
// 渲染界面
|
||||||
renderFilters();
|
renderFilters();
|
||||||
renderApps();
|
renderApps();
|
||||||
loadingSpinner.style.display = 'none';
|
loadingSpinner.style.display = 'none';
|
||||||
primaryFilters.querySelector('[data-filter="all"]').textContent = '全部';
|
primaryFilters.querySelector('[data-filter="all"]').textContent = '全部';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载数据失败:', error);
|
console.error('加载数据失败:', error);
|
||||||
appListContainer.innerHTML = `
|
appListContainer.innerHTML = `
|
||||||
@ -951,46 +1016,49 @@ primaryFilters.querySelector('[data-filter="all"]').textContent = '全部';
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新登录按钮状态
|
// 更新登录按钮状态
|
||||||
function updateLoginButton() {
|
function updateLoginButton() {
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
loginBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i>';
|
loginBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i>';
|
||||||
loginBtn.href = '/logout';
|
loginBtn.href = '/logout';
|
||||||
loginBtn.title = '退出登录';
|
loginBtn.title = '退出登录';
|
||||||
adminBtn.href = '/manage'; // 已登录时直接跳转到管理页面
|
adminBtn.href = '/manage'; // 已登录时直接跳转到管理页面
|
||||||
} else {
|
document.getElementById('addBtn').style.display = 'flex'; // 显示添加按钮
|
||||||
|
} else {
|
||||||
loginBtn.innerHTML = '<i class="fas fa-sign-in-alt"></i>';
|
loginBtn.innerHTML = '<i class="fas fa-sign-in-alt"></i>';
|
||||||
loginBtn.href = '/login';
|
loginBtn.href = '/login';
|
||||||
loginBtn.title = '登录';
|
loginBtn.title = '登录';
|
||||||
adminBtn.href = '/login?next=/manage'; // 未登录时跳转到登录页面
|
adminBtn.href = '/login?next=/manage'; // 未登录时跳转到登录页面
|
||||||
}
|
document.getElementById('addBtn').style.display = 'none'; // 隐藏添加按钮
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 根据分类ID获取对应的颜色类
|
// 根据分类ID获取对应的颜色类
|
||||||
function getColorForCategory(categoryId) {
|
function getColorForCategory(categoryId) {
|
||||||
return categoryId;
|
return categoryId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成筛选按钮
|
// 生成筛选按钮
|
||||||
function renderFilters() {
|
function renderFilters() {
|
||||||
// 生成一级标签
|
// 生成一级标签
|
||||||
primaryFilters.innerHTML = '';
|
primaryFilters.innerHTML = '';
|
||||||
const allPrimaryBtn = document.createElement('button');
|
const allPrimaryBtn = document.createElement('button');
|
||||||
allPrimaryBtn.className = 'filter-btn active';
|
allPrimaryBtn.className = 'filter-btn active';
|
||||||
allPrimaryBtn.textContent = '全部';
|
allPrimaryBtn.textContent = '全部';
|
||||||
allPrimaryBtn.dataset.level = '1';
|
allPrimaryBtn.dataset.level = '1';
|
||||||
allPrimaryBtn.dataset.filter = 'all';
|
allPrimaryBtn.dataset.filter = 'all';
|
||||||
allPrimaryBtn.addEventListener('click', () => {
|
allPrimaryBtn.addEventListener('click', () => {
|
||||||
setFilter('all', 'all');
|
setFilter('all', 'all');
|
||||||
showAllSecondaryFilters();
|
showAllSecondaryFilters();
|
||||||
setAllPrimaryCaretDown(true);
|
setAllPrimaryCaretDown(true);
|
||||||
});
|
activeSecondaryFilter = null; // 重置二级筛选状态
|
||||||
primaryFilters.appendChild(allPrimaryBtn);
|
});
|
||||||
|
primaryFilters.appendChild(allPrimaryBtn);
|
||||||
|
|
||||||
// 添加一级标签按钮(从分类数据生成)
|
// 添加一级标签按钮(从分类数据生成)
|
||||||
Object.entries(categories).forEach(([catId, catData]) => {
|
Object.entries(categories).forEach(([catId, catData]) => {
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.className = `filter-btn ${catId}`;
|
btn.className = `filter-btn ${catId}`;
|
||||||
btn.innerHTML = `<i class="fas fa-caret-right caret down"></i> ${catData.name}`;
|
btn.innerHTML = `<i class="fas fa-caret-right caret down"></i> ${catData.name}`;
|
||||||
@ -1007,25 +1075,27 @@ Object.entries(categories).forEach(([catId, catData]) => {
|
|||||||
showAllSecondaryFilters();
|
showAllSecondaryFilters();
|
||||||
setFilter('all', 'all');
|
setFilter('all', 'all');
|
||||||
setAllPrimaryCaretDown(true);
|
setAllPrimaryCaretDown(true);
|
||||||
|
activeSecondaryFilter = null; // 重置二级筛选状态
|
||||||
} else {
|
} else {
|
||||||
// 否则展开该一级分类下的二级分类
|
// 否则展开该一级分类下的二级分类
|
||||||
expandSecondary(catId);
|
expandSecondary(catId);
|
||||||
setFilter(catId, 'all');
|
setFilter(catId, 'all');
|
||||||
setAllPrimaryCaretDown(false);
|
setAllPrimaryCaretDown(false);
|
||||||
setPrimaryCaretDown(catId, true);
|
setPrimaryCaretDown(catId, true);
|
||||||
|
activeSecondaryFilter = null; // 重置二级筛选状态
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
primaryFilters.appendChild(btn);
|
primaryFilters.appendChild(btn);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 初始加载时显示所有二级标签
|
// 初始加载时显示所有二级标签
|
||||||
showAllSecondaryFilters();
|
showAllSecondaryFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置所有一级标签箭头的展开/收起状态
|
// 设置所有一级标签箭头的展开/收起状态
|
||||||
function setAllPrimaryCaretDown(shouldDown) {
|
function setAllPrimaryCaretDown(shouldDown) {
|
||||||
const primaryBtns = primaryFilters.querySelectorAll('[data-level="1"]');
|
const primaryBtns = primaryFilters.querySelectorAll('[data-level="1"]');
|
||||||
primaryBtns.forEach(btn => {
|
primaryBtns.forEach(btn => {
|
||||||
if (btn.dataset.filter !== 'all') {
|
if (btn.dataset.filter !== 'all') {
|
||||||
const caret = btn.querySelector('.caret');
|
const caret = btn.querySelector('.caret');
|
||||||
if (caret) {
|
if (caret) {
|
||||||
@ -1036,13 +1106,13 @@ primaryBtns.forEach(btn => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置特定一级标签箭头的展开/收起状态
|
// 设置特定一级标签箭头的展开/收起状态
|
||||||
function setPrimaryCaretDown(primaryId, shouldDown) {
|
function setPrimaryCaretDown(primaryId, shouldDown) {
|
||||||
const btn = primaryFilters.querySelector(`[data-filter="${primaryId}"]`);
|
const btn = primaryFilters.querySelector(`[data-filter="${primaryId}"]`);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
const caret = btn.querySelector('.caret');
|
const caret = btn.querySelector('.caret');
|
||||||
if (caret) {
|
if (caret) {
|
||||||
if (shouldDown) {
|
if (shouldDown) {
|
||||||
@ -1051,29 +1121,30 @@ if (btn) {
|
|||||||
caret.classList.remove('down');
|
caret.classList.remove('down');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示所有二级标签
|
// 显示所有二级标签
|
||||||
function showAllSecondaryFilters() {
|
function showAllSecondaryFilters() {
|
||||||
collapseAllSecondary();
|
collapseAllSecondary();
|
||||||
|
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.className = 'secondary-filters-container show';
|
container.className = 'secondary-filters-container show';
|
||||||
container.id = 'secondary-all';
|
container.id = 'secondary-all';
|
||||||
|
|
||||||
const allSecondaryBtn = document.createElement('button');
|
const allSecondaryBtn = document.createElement('button');
|
||||||
allSecondaryBtn.className = 'filter-btn active';
|
allSecondaryBtn.className = 'filter-btn active';
|
||||||
allSecondaryBtn.textContent = '全部';
|
allSecondaryBtn.textContent = '全部';
|
||||||
allSecondaryBtn.dataset.level = '2';
|
allSecondaryBtn.dataset.level = '2';
|
||||||
allSecondaryBtn.dataset.filter = 'all';
|
allSecondaryBtn.dataset.filter = 'all';
|
||||||
allSecondaryBtn.addEventListener('click', () => {
|
allSecondaryBtn.addEventListener('click', () => {
|
||||||
setFilter('all', 'all');
|
setFilter('all', 'all');
|
||||||
});
|
activeSecondaryFilter = null; // 重置二级筛选状态
|
||||||
container.appendChild(allSecondaryBtn);
|
});
|
||||||
|
container.appendChild(allSecondaryBtn);
|
||||||
|
|
||||||
// 添加所有二级标签按钮(从分类数据生成)
|
// 添加所有二级标签按钮(从分类数据生成)
|
||||||
Object.entries(categories).forEach(([mainCatId, mainCatData]) => {
|
Object.entries(categories).forEach(([mainCatId, mainCatData]) => {
|
||||||
Object.entries(mainCatData.sub).forEach(([subCatId, subCatData]) => {
|
Object.entries(mainCatData.sub).forEach(([subCatId, subCatData]) => {
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.className = `filter-btn ${mainCatId}`;
|
btn.className = `filter-btn ${mainCatId}`;
|
||||||
@ -1083,26 +1154,33 @@ Object.entries(categories).forEach(([mainCatId, mainCatData]) => {
|
|||||||
btn.style.backgroundColor = subCatData.color;
|
btn.style.backgroundColor = subCatData.color;
|
||||||
btn.style.color = getContrastColor(subCatData.color);
|
btn.style.color = getContrastColor(subCatData.color);
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
|
// 如果点击的是当前激活的二级筛选,则取消筛选
|
||||||
|
if (activeSecondaryFilter === subCatId) {
|
||||||
|
setFilter('all', 'all');
|
||||||
|
activeSecondaryFilter = null;
|
||||||
|
} else {
|
||||||
setFilter('all', subCatId);
|
setFilter('all', subCatId);
|
||||||
|
activeSecondaryFilter = subCatId;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
container.appendChild(btn);
|
container.appendChild(btn);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
secondaryFiltersWrapper.innerHTML = '';
|
secondaryFiltersWrapper.innerHTML = '';
|
||||||
secondaryFiltersWrapper.appendChild(container);
|
secondaryFiltersWrapper.appendChild(container);
|
||||||
currentExpandedPrimary = null;
|
currentExpandedPrimary = null;
|
||||||
|
|
||||||
// 为新创建的二级筛选容器添加滚轮事件
|
// 为新创建的二级筛选容器添加滚轮事件
|
||||||
addWheelEventToContainer(container);
|
addWheelEventToContainer(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 展开二级标签
|
// 展开二级标签
|
||||||
function expandSecondary(primaryFilterId) {
|
function expandSecondary(primaryFilterId) {
|
||||||
collapseAllSecondary();
|
collapseAllSecondary();
|
||||||
|
|
||||||
const primaryBtn = primaryFilters.querySelector(`[data-filter="${primaryFilterId}"]`);
|
const primaryBtn = primaryFilters.querySelector(`[data-filter="${primaryFilterId}"]`);
|
||||||
if (primaryBtn && categories[primaryFilterId]) {
|
if (primaryBtn && categories[primaryFilterId]) {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.className = 'secondary-filters-container show';
|
container.className = 'secondary-filters-container show';
|
||||||
container.id = `secondary-${primaryFilterId}`;
|
container.id = `secondary-${primaryFilterId}`;
|
||||||
@ -1114,6 +1192,7 @@ if (primaryBtn && categories[primaryFilterId]) {
|
|||||||
allSecondaryBtn.dataset.filter = 'all';
|
allSecondaryBtn.dataset.filter = 'all';
|
||||||
allSecondaryBtn.addEventListener('click', () => {
|
allSecondaryBtn.addEventListener('click', () => {
|
||||||
setFilter(primaryFilterId, 'all');
|
setFilter(primaryFilterId, 'all');
|
||||||
|
activeSecondaryFilter = null; // 重置二级筛选状态
|
||||||
});
|
});
|
||||||
container.appendChild(allSecondaryBtn);
|
container.appendChild(allSecondaryBtn);
|
||||||
|
|
||||||
@ -1126,7 +1205,14 @@ if (primaryBtn && categories[primaryFilterId]) {
|
|||||||
btn.style.backgroundColor = subCatData.color;
|
btn.style.backgroundColor = subCatData.color;
|
||||||
btn.style.color = getContrastColor(subCatData.color);
|
btn.style.color = getContrastColor(subCatData.color);
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
|
// 如果点击的是当前激活的二级筛选,则取消筛选
|
||||||
|
if (activeSecondaryFilter === subCatId) {
|
||||||
|
setFilter(primaryFilterId, 'all');
|
||||||
|
activeSecondaryFilter = null;
|
||||||
|
} else {
|
||||||
setFilter(primaryFilterId, subCatId);
|
setFilter(primaryFilterId, subCatId);
|
||||||
|
activeSecondaryFilter = subCatId;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
container.appendChild(btn);
|
container.appendChild(btn);
|
||||||
});
|
});
|
||||||
@ -1137,61 +1223,61 @@ if (primaryBtn && categories[primaryFilterId]) {
|
|||||||
|
|
||||||
// 为新创建的二级筛选容器添加滚轮事件
|
// 为新创建的二级筛选容器添加滚轮事件
|
||||||
addWheelEventToContainer(container);
|
addWheelEventToContainer(container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收起所有二级标签
|
// 收起所有二级标签
|
||||||
function collapseAllSecondary() {
|
function collapseAllSecondary() {
|
||||||
secondaryFiltersWrapper.innerHTML = '';
|
secondaryFiltersWrapper.innerHTML = '';
|
||||||
currentExpandedPrimary = null;
|
currentExpandedPrimary = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为滚动容器添加滚轮事件
|
// 为滚动容器添加滚轮事件
|
||||||
function addWheelEventToContainer(container) {
|
function addWheelEventToContainer(container) {
|
||||||
container.addEventListener('wheel', (e) => {
|
container.addEventListener('wheel', (e) => {
|
||||||
if (e.deltaY !== 0) {
|
if (e.deltaY !== 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
container.scrollLeft += e.deltaY;
|
container.scrollLeft += e.deltaY;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置当前筛选条件
|
// 设置当前筛选条件
|
||||||
function setFilter(primaryFilter, secondaryFilter) {
|
function setFilter(primaryFilter, secondaryFilter) {
|
||||||
currentPrimaryFilter = primaryFilter;
|
currentPrimaryFilter = primaryFilter;
|
||||||
currentSecondaryFilter = secondaryFilter;
|
currentSecondaryFilter = secondaryFilter;
|
||||||
|
|
||||||
// 更新按钮激活状态
|
// 更新按钮激活状态
|
||||||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||||||
btn.classList.remove('active');
|
btn.classList.remove('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 激活一级标签按钮
|
// 激活一级标签按钮
|
||||||
if (primaryFilter === 'all') {
|
if (primaryFilter === 'all') {
|
||||||
primaryFilters.querySelector('[data-filter="all"]').classList.add('active');
|
primaryFilters.querySelector('[data-filter="all"]').classList.add('active');
|
||||||
} else {
|
} else {
|
||||||
const primaryBtn = primaryFilters.querySelector(`[data-filter="${primaryFilter}"]`);
|
const primaryBtn = primaryFilters.querySelector(`[data-filter="${primaryFilter}"]`);
|
||||||
if (primaryBtn) primaryBtn.classList.add('active');
|
if (primaryBtn) primaryBtn.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 激活二级标签按钮
|
// 激活二级标签按钮
|
||||||
if (secondaryFilter === 'all') {
|
if (secondaryFilter === 'all') {
|
||||||
const container = document.querySelector('.secondary-filters-container');
|
const container = document.querySelector('.secondary-filters-container');
|
||||||
if (container) {
|
if (container) {
|
||||||
const allBtn = container.querySelector('[data-filter="all"]');
|
const allBtn = container.querySelector('[data-filter="all"]');
|
||||||
if (allBtn) allBtn.classList.add('active');
|
if (allBtn) allBtn.classList.add('active');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const secondaryBtn = document.querySelector(`[data-filter="${secondaryFilter}"]`);
|
const secondaryBtn = document.querySelector(`[data-filter="${secondaryFilter}"]`);
|
||||||
if (secondaryBtn) secondaryBtn.classList.add('active');
|
if (secondaryBtn) secondaryBtn.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新渲染应用列表
|
// 重新渲染应用列表
|
||||||
renderApps();
|
renderApps();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染应用列表
|
// 渲染应用列表
|
||||||
function renderApps() {
|
function renderApps() {
|
||||||
appListContainer.innerHTML = '';
|
appListContainer.innerHTML = '';
|
||||||
|
|
||||||
// 按一级标签分组
|
// 按一级标签分组
|
||||||
@ -1310,34 +1396,34 @@ function renderApps() {
|
|||||||
noResults.textContent = '没有找到匹配的应用';
|
noResults.textContent = '没有找到匹配的应用';
|
||||||
appListContainer.appendChild(noResults);
|
appListContainer.appendChild(noResults);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助函数:计算对比色
|
// 辅助函数:计算对比色
|
||||||
function getContrastColor(hexColor) {
|
function getContrastColor(hexColor) {
|
||||||
if (!hexColor) return '#ffffff';
|
if (!hexColor) return '#ffffff';
|
||||||
|
|
||||||
// 转换hex颜色为RGB
|
// 转换hex颜色为RGB
|
||||||
const r = parseInt(hexColor.substr(1, 2), 16);
|
const r = parseInt(hexColor.substr(1, 2), 16);
|
||||||
const g = parseInt(hexColor.substr(3, 2), 16);
|
const g = parseInt(hexColor.substr(3, 2), 16);
|
||||||
const b = parseInt(hexColor.substr(5, 2), 16);
|
const b = parseInt(hexColor.substr(5, 2), 16);
|
||||||
|
|
||||||
// 计算亮度
|
// 计算亮度
|
||||||
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||||
|
|
||||||
// 根据亮度返回黑色或白色
|
// 根据亮度返回黑色或白色
|
||||||
return brightness > 128 ? '#000000' : '#ffffff';
|
return brightness > 128 ? '#000000' : '#ffffff';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局设置
|
// 全局设置
|
||||||
let settings = {
|
let settings = {
|
||||||
theme: 'auto',
|
theme: 'auto',
|
||||||
card_style: 'normal',
|
card_style: 'normal',
|
||||||
search_history: []
|
search_history: []
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载设置
|
// 加载设置
|
||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
try {
|
try {
|
||||||
// 检查是否登录
|
// 检查是否登录
|
||||||
isLoggedIn = await checkLoginStatus();
|
isLoggedIn = await checkLoginStatus();
|
||||||
|
|
||||||
@ -1382,18 +1468,18 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applySettings();
|
applySettings();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载设置失败:', error);
|
console.error('加载设置失败:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置背景图片
|
// 设置背景图片
|
||||||
function setBackgroundImages(lightImage, darkImage) {
|
function setBackgroundImages(lightImage, darkImage) {
|
||||||
// 移除现有的视频背景
|
// 移除现有的视频背景
|
||||||
document.querySelectorAll('.video-bg-container').forEach(el => el.remove());
|
document.querySelectorAll('.video-bg-container').forEach(el => el.remove());
|
||||||
|
|
||||||
// 处理明亮模式背景
|
// 处理明亮模式背景
|
||||||
if (lightImage && lightImage.endsWith('.mp4')) {
|
if (lightImage && lightImage.endsWith('.mp4')) {
|
||||||
const lightVideoContainer = document.createElement('div');
|
const lightVideoContainer = document.createElement('div');
|
||||||
lightVideoContainer.className = 'video-bg-container bg-light';
|
lightVideoContainer.className = 'video-bg-container bg-light';
|
||||||
lightVideoContainer.style.display = document.body.classList.contains('dark-theme') ? 'none' : 'block';
|
lightVideoContainer.style.display = document.body.classList.contains('dark-theme') ? 'none' : 'block';
|
||||||
@ -1408,12 +1494,12 @@ if (lightImage && lightImage.endsWith('.mp4')) {
|
|||||||
|
|
||||||
lightVideoContainer.appendChild(lightVideo);
|
lightVideoContainer.appendChild(lightVideo);
|
||||||
document.body.appendChild(lightVideoContainer);
|
document.body.appendChild(lightVideoContainer);
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.style.setProperty('--bg-image', lightImage === 'none' ? 'none' : `url('${lightImage}')`);
|
document.documentElement.style.setProperty('--bg-image', lightImage === 'none' ? 'none' : `url('${lightImage}')`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理暗黑模式背景
|
// 处理暗黑模式背景
|
||||||
if (darkImage && darkImage.endsWith('.mp4')) {
|
if (darkImage && darkImage.endsWith('.mp4')) {
|
||||||
const darkVideoContainer = document.createElement('div');
|
const darkVideoContainer = document.createElement('div');
|
||||||
darkVideoContainer.className = 'video-bg-container bg-dark';
|
darkVideoContainer.className = 'video-bg-container bg-dark';
|
||||||
darkVideoContainer.style.display = document.body.classList.contains('dark-theme') ? 'block' : 'none';
|
darkVideoContainer.style.display = document.body.classList.contains('dark-theme') ? 'block' : 'none';
|
||||||
@ -1428,71 +1514,71 @@ if (darkImage && darkImage.endsWith('.mp4')) {
|
|||||||
|
|
||||||
darkVideoContainer.appendChild(darkVideo);
|
darkVideoContainer.appendChild(darkVideo);
|
||||||
document.body.appendChild(darkVideoContainer);
|
document.body.appendChild(darkVideoContainer);
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.style.setProperty('--dark-bg-image', darkImage === 'none' ? 'none' : `url('${darkImage}')`);
|
document.documentElement.style.setProperty('--dark-bg-image', darkImage === 'none' ? 'none' : `url('${darkImage}')`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加背景加载完成的类
|
// 添加背景加载完成的类
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.body.classList.add('bg-loaded');
|
document.body.classList.add('bg-loaded');
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查登录状态
|
// 检查登录状态
|
||||||
async function checkLoginStatus() {
|
async function checkLoginStatus() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/check_login');
|
const response = await fetch('/api/check_login');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data.logged_in;
|
return data.logged_in;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('检查登录状态失败:', error);
|
console.error('检查登录状态失败:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用设置
|
// 应用设置
|
||||||
function applySettings() {
|
function applySettings() {
|
||||||
// 应用主题
|
// 应用主题
|
||||||
document.body.classList.remove('dark-theme', 'light-theme');
|
document.body.classList.remove('dark-theme', 'light-theme');
|
||||||
if (settings.theme === 'dark' ||
|
if (settings.theme === 'dark' ||
|
||||||
(settings.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
(settings.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
document.body.classList.add('dark-theme');
|
document.body.classList.add('dark-theme');
|
||||||
|
|
||||||
// 显示/隐藏视频背景
|
// 显示/隐藏视频背景
|
||||||
document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'none');
|
document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'none');
|
||||||
document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'block');
|
document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'block');
|
||||||
} else if (settings.theme === 'light') {
|
} else if (settings.theme === 'light') {
|
||||||
document.body.classList.add('light-theme');
|
document.body.classList.add('light-theme');
|
||||||
|
|
||||||
// 显示/隐藏视频背景
|
// 显示/隐藏视频背景
|
||||||
document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'block');
|
document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'block');
|
||||||
document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'none');
|
document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'none');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用卡片样式
|
// 应用卡片样式
|
||||||
document.querySelectorAll('.app-item').forEach(item => {
|
document.querySelectorAll('.app-item').forEach(item => {
|
||||||
item.classList.toggle('compact', settings.card_style === 'compact');
|
item.classList.toggle('compact', settings.card_style === 'compact');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新主题按钮状态
|
// 更新主题按钮状态
|
||||||
updateThemeButtonIcon();
|
updateThemeButtonIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新主题按钮图标
|
// 更新主题按钮图标
|
||||||
function updateThemeButtonIcon() {
|
function updateThemeButtonIcon() {
|
||||||
const themeToggle = document.getElementById('themeToggle');
|
const themeToggle = document.getElementById('themeToggle');
|
||||||
if (document.body.classList.contains('dark-theme')) {
|
if (document.body.classList.contains('dark-theme')) {
|
||||||
themeToggle.textContent = '☀️';
|
themeToggle.textContent = '☀️';
|
||||||
themeToggle.title = '切换至明亮模式';
|
themeToggle.title = '切换至明亮模式';
|
||||||
} else {
|
} else {
|
||||||
themeToggle.textContent = '🌙';
|
themeToggle.textContent = '🌙';
|
||||||
themeToggle.title = '切换至暗黑模式';
|
themeToggle.title = '切换至暗黑模式';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存设置
|
// 保存设置
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
fetch('/api/settings', {
|
fetch('/api/settings', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@ -1500,7 +1586,7 @@ if (isLoggedIn) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(settings)
|
body: JSON.stringify(settings)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 游客只保存主题和卡片样式到本地
|
// 游客只保存主题和卡片样式到本地
|
||||||
const settingsToSave = {
|
const settingsToSave = {
|
||||||
theme: settings.theme,
|
theme: settings.theme,
|
||||||
@ -1508,16 +1594,16 @@ if (isLoggedIn) {
|
|||||||
search_history: settings.search_history
|
search_history: settings.search_history
|
||||||
};
|
};
|
||||||
localStorage.setItem('navSettings', JSON.stringify(settingsToSave));
|
localStorage.setItem('navSettings', JSON.stringify(settingsToSave));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索功能
|
// 搜索功能
|
||||||
function setupSearch() {
|
function setupSearch() {
|
||||||
const searchInput = document.getElementById('searchInput');
|
const searchInput = document.getElementById('searchInput');
|
||||||
const searchResults = document.getElementById('searchResults');
|
const searchResults = document.getElementById('searchResults');
|
||||||
let searchTimeout;
|
let searchTimeout;
|
||||||
|
|
||||||
searchInput.addEventListener('input', () => {
|
searchInput.addEventListener('input', () => {
|
||||||
clearTimeout(searchTimeout);
|
clearTimeout(searchTimeout);
|
||||||
searchTimeout = setTimeout(() => {
|
searchTimeout = setTimeout(() => {
|
||||||
const keyword = searchInput.value.trim();
|
const keyword = searchInput.value.trim();
|
||||||
@ -1555,20 +1641,20 @@ searchInput.addEventListener('input', () => {
|
|||||||
searchResults.style.display = 'none';
|
searchResults.style.display = 'none';
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) {
|
if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) {
|
||||||
searchResults.style.display = 'none';
|
searchResults.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主题切换功能
|
// 主题切换功能
|
||||||
function setupThemeSwitcher() {
|
function setupThemeSwitcher() {
|
||||||
const themeToggle = document.getElementById('themeToggle');
|
const themeToggle = document.getElementById('themeToggle');
|
||||||
|
|
||||||
themeToggle.addEventListener('click', () => {
|
themeToggle.addEventListener('click', () => {
|
||||||
// 只在明亮和暗黑之间切换
|
// 只在明亮和暗黑之间切换
|
||||||
if (document.body.classList.contains('dark-theme')) {
|
if (document.body.classList.contains('dark-theme')) {
|
||||||
settings.theme = 'light';
|
settings.theme = 'light';
|
||||||
@ -1588,22 +1674,22 @@ themeToggle.addEventListener('click', () => {
|
|||||||
|
|
||||||
updateThemeButtonIcon();
|
updateThemeButtonIcon();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 简洁模式切换
|
// 简洁模式切换
|
||||||
function setupCompactToggle() {
|
function setupCompactToggle() {
|
||||||
const compactToggle = document.getElementById('compactToggle');
|
const compactToggle = document.getElementById('compactToggle');
|
||||||
compactToggle.addEventListener('click', () => {
|
compactToggle.addEventListener('click', () => {
|
||||||
settings.card_style = settings.card_style === 'compact' ? 'normal' : 'compact';
|
settings.card_style = settings.card_style === 'compact' ? 'normal' : 'compact';
|
||||||
applySettings();
|
applySettings();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 后台管理按钮点击事件
|
// 后台管理按钮点击事件
|
||||||
function setupAdminButton() {
|
function setupAdminButton() {
|
||||||
adminBtn.addEventListener('click', async (e) => {
|
adminBtn.addEventListener('click', async (e) => {
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const loginCheck = await fetch('/api/check_login');
|
const loginCheck = await fetch('/api/check_login');
|
||||||
@ -1615,29 +1701,51 @@ adminBtn.addEventListener('click', async (e) => {
|
|||||||
window.location.href = '/manage';
|
window.location.href = '/manage';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面加载完成后初始化
|
// 返回顶部功能
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
function setupBackToTop() {
|
||||||
loadData();
|
// 监听滚动事件
|
||||||
loadSettings();
|
window.addEventListener('scroll', () => {
|
||||||
setupSearch();
|
if (window.pageYOffset > 300) {
|
||||||
setupThemeSwitcher();
|
backToTopBtn.style.display = 'flex';
|
||||||
setupCompactToggle();
|
} else {
|
||||||
setupAdminButton();
|
backToTopBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 为静态的一级筛选容器添加滚轮事件
|
// 点击返回顶部按钮
|
||||||
primaryFilters.addEventListener('wheel', (e) => {
|
backToTopBtn.addEventListener('click', () => {
|
||||||
|
// 平滑滚动到顶部
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后初始化
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
loadData();
|
||||||
|
loadSettings();
|
||||||
|
setupSearch();
|
||||||
|
setupThemeSwitcher();
|
||||||
|
setupCompactToggle();
|
||||||
|
setupAdminButton();
|
||||||
|
setupBackToTop(); // 初始化返回顶部功能
|
||||||
|
|
||||||
|
// 为静态的一级筛选容器添加滚轮事件
|
||||||
|
primaryFilters.addEventListener('wheel', (e) => {
|
||||||
if (e.deltaY !== 0) {
|
if (e.deltaY !== 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
primaryFilters.scrollLeft += e.deltaY;
|
primaryFilters.scrollLeft += e.deltaY;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function checkScrollable() {
|
function checkScrollable() {
|
||||||
document.querySelectorAll('.filter-row, .secondary-filters-container').forEach(container => {
|
document.querySelectorAll('.filter-row, .secondary-filters-container').forEach(container => {
|
||||||
// 检查内容宽度是否大于容器宽度
|
// 检查内容宽度是否大于容器宽度
|
||||||
const isScrollable = container.scrollWidth > container.clientWidth;
|
const isScrollable = container.scrollWidth > container.clientWidth;
|
||||||
|
|
||||||
@ -1646,18 +1754,18 @@ document.querySelectorAll('.filter-row, .secondary-filters-container').forEach(c
|
|||||||
} else {
|
} else {
|
||||||
container.classList.remove('scrollable');
|
container.classList.remove('scrollable');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化时检查
|
// 初始化时检查
|
||||||
window.addEventListener('load', checkScrollable);
|
window.addEventListener('load', checkScrollable);
|
||||||
// 窗口大小改变时检查
|
// 窗口大小改变时检查
|
||||||
window.addEventListener('resize', checkScrollable);
|
window.addEventListener('resize', checkScrollable);
|
||||||
// 内容变化时检查(如筛选器更新后)
|
// 内容变化时检查(如筛选器更新后)
|
||||||
new MutationObserver(checkScrollable).observe(document.body, {
|
new MutationObserver(checkScrollable).observe(document.body, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true
|
subtree: true
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -47,6 +47,8 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 桌面端显示表格 -->
|
||||||
|
<div class="d-none d-md-block">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
@ -93,7 +95,7 @@
|
|||||||
<a href="{{ url_for('edit_app', index=(current_page-1)*per_page + loop.index0) }}" class="btn btn-sm btn-warning">
|
<a href="{{ url_for('edit_app', index=(current_page-1)*per_page + loop.index0) }}" class="btn btn-sm btn-warning">
|
||||||
<i class="fas fa-edit"></i> 编辑
|
<i class="fas fa-edit"></i> 编辑
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('delete_app', index=loop.index0) }}" class="btn btn-sm btn-danger" onclick="return confirm('确定删除吗?')">
|
<a href="{{ url_for('delete_app', index=(current_page-1)*per_page + loop.index0) }}" class="btn btn-sm btn-danger" onclick="return confirm('确定删除吗?')">
|
||||||
<i class="fas fa-trash"></i> 删除
|
<i class="fas fa-trash"></i> 删除
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@ -106,6 +108,65 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 移动端显示卡片 -->
|
||||||
|
<div class="d-md-none">
|
||||||
|
<div class="row">
|
||||||
|
{% for app in apps %}
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-3">
|
||||||
|
{% if app.icon.startswith('/upload/icon/') %}
|
||||||
|
<img src="{{ app.icon }}" class="img-fluid rounded">
|
||||||
|
{% else %}
|
||||||
|
<i class="fas {{ app.icon }} fa-2x"></i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<h5 class="card-title mb-1">
|
||||||
|
{{ app.title }}
|
||||||
|
{% if app.get('private', False) %}
|
||||||
|
<span class="badge bg-warning text-dark ms-1">私有</span>
|
||||||
|
{% endif %}
|
||||||
|
</h5>
|
||||||
|
<div class="d-flex flex-wrap gap-1 mb-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>
|
||||||
|
<p class="card-text mb-2">
|
||||||
|
<a href="{{ app.url }}" target="_blank" class="text-truncate d-block">{{ app.url }}</a>
|
||||||
|
</p>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="{{ url_for('edit_app', index=(current_page-1)*per_page + loop.index0) }}" class="btn btn-sm btn-warning flex-grow-1">
|
||||||
|
<i class="fas fa-edit"></i> 编辑
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('delete_app', index=(current_page-1)*per_page + loop.index0) }}" class="btn btn-sm btn-danger flex-grow-1" onclick="return confirm('确定删除吗?')">
|
||||||
|
<i class="fas fa-trash"></i> 删除
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-info text-center">没有找到匹配的应用</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 分页导航 -->
|
<!-- 分页导航 -->
|
||||||
{% if total_pages > 1 %}
|
{% if total_pages > 1 %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user