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