优化移动端样式,优化后台逻辑

This commit is contained in:
wzj 2025-07-09 00:35:26 +08:00
parent ef39536ed9
commit 75c691c183
9 changed files with 1395 additions and 878 deletions

View File

@ -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
View File

@ -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='导航系统管理工具')

View File

@ -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"
} }
] ]

View File

@ -155,6 +155,11 @@
"name": "营养", "name": "营养",
"color": "#4d908e", "color": "#4d908e",
"weight": 2 "weight": 2
},
"swim": {
"name": "游泳",
"color": "#4895ef",
"weight": 4
} }
}, },
"weight": 4, "weight": 4,

View File

@ -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>"
} }

View File

@ -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,61 +53,118 @@
{% endif %} {% endif %}
</form> </form>
</div> </div>
<div class="table-responsive">
<table class="table table-striped"> <!-- 桌面端显示表格 -->
<thead> <div class="d-none d-md-block">
<tr> <div class="table-responsive">
<th>预览</th> <table class="table table-striped">
<th>文件名</th> <thead>
<th>类型</th> <tr>
<th>上传时间</th> <th>预览</th>
<th>操作</th> <th>文件名</th>
</tr> <th>类型</th>
</thead> <th>上传时间</th>
<tbody> <th>操作</th>
{% for attachment in attachments %} </tr>
<tr> </thead>
<td> <tbody>
{% if attachment.type == 'logo' %} {% for attachment in attachments %}
<img src="{{ url_for('uploaded_logo', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail"> <tr>
{% elif attachment.type == 'background' %} <td>
<img src="{{ url_for('uploaded_background', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail"> {% if attachment.type == 'logo' %}
{% elif attachment.type == 'video' %} <img src="{{ url_for('uploaded_logo', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
<video width="80" height="45" muted style="max-width: 50px; max-height: 50px;" class="img-thumbnail"> {% elif attachment.type == 'background' %}
<source src="{{ url_for('uploaded_video', filename=attachment.filename) }}" type="video/mp4"> <img src="{{ url_for('uploaded_background', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
</video> {% elif attachment.type == 'video' %}
{% else %} <video width="80" height="45" muted style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
<img src="{{ url_for('uploaded_icon', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail"> <source src="{{ url_for('uploaded_video', filename=attachment.filename) }}" type="video/mp4">
{% endif %} </video>
</td> {% else %}
<td>{{ attachment.filename }}</td> <img src="{{ url_for('uploaded_icon', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
<td> {% endif %}
{% if attachment.type == 'logo' %} </td>
Logo <td>{{ attachment.filename }}</td>
{% elif attachment.type == 'background' %} <td>
背景图片 {% if attachment.type == 'logo' %}
{% elif attachment.type == 'video' %} Logo
背景视频 {% elif attachment.type == 'background' %}
{% else %} 背景图片
图标图片 {% elif attachment.type == 'video' %}
{% endif %} 背景视频
</td> {% else %}
<td>{{ attachment.upload_time }}</td> 图标图片
<td> {% endif %}
<form method="POST" action="{{ url_for('delete_attachment_route', filename=attachment.filename) }}" style="display: inline;"> </td>
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('确定要删除这个附件吗?')"> <td>{{ attachment.upload_time }}</td>
<i class="fas fa-trash"></i> 删除 <td>
</button> <form method="POST" action="{{ url_for('delete_attachment_route', filename=attachment.filename) }}" style="display: inline;">
</form> <button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('确定要删除这个附件吗?')">
</td> <i class="fas fa-trash"></i> 删除
</tr> </button>
{% else %} </form>
<tr> </td>
<td colspan="5" class="text-center">没有找到匹配的附件</td> </tr>
</tr> {% else %}
{% endfor %} <tr>
</tbody> <td colspan="5" class="text-center">没有找到匹配的附件</td>
</table> </tr>
{% endfor %}
</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> </div>
<!-- 分页导航 --> <!-- 分页导航 -->
@ -133,11 +192,11 @@
</nav> </nav>
{% endif %} {% endif %}
</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> 返回首页
</a> </a>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -3,99 +3,310 @@
{% 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="checkbox" class="form-check-input" id="main_private" name="private"> <input type="text" name="name" class="form-control" placeholder="主分类名称" required>
<label class="form-check-label" for="main_private">私有</label> </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>
</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> </div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-plus"></i> 添加
</button>
</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">
<option value="">选择主分类</option> <div class="col-12 col-md-2">
{% for main_id, cat in categories.items() %} <select name="main_id" class="form-select" required>
<option value="{{ main_id }}">{{ cat.name }}</option> <option value="">选择主分类</option>
{% endfor %} {% for main_id, cat in categories.items()|sort(attribute='1.weight', reverse=True) %}
</select> <option value="{{ main_id }}">{{ cat.name }}</option>
<input type="text" name="sub_id" class="form-control" placeholder="子分类 ID" required style="width: 120px;"> {% endfor %}
<input type="text" name="sub_name" class="form-control" placeholder="子分类名称" required style="width: 150px;"> </select>
<input type="number" name="weight" class="form-control" placeholder="权重" value="0" style="width: 80px;"> </div>
<input type="color" name="color" class="form-control form-control-color" value="#4895ef" title="选择颜色" style="width: 50px;"> <div class="col-12 col-md-2">
<div class="form-check form-check-inline"> <input type="text" name="sub_id" class="form-control" placeholder="子分类 ID" required>
<input type="checkbox" class="form-check-input" id="sub_private" name="private"> </div>
<label class="form-check-label" for="sub_private">私有</label> <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>
</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> </div>
<button type="submit" class="btn btn-success">
<i class="fas fa-plus"></i> 添加
</button>
</form> </form>
</div> </div>
<div> <div>
<h5>已存在的分类</h5> <h5>已存在的分类</h5>
<div class="list-group"> <!-- 桌面端显示 -->
{% for main_id, cat in categories.items() %} <div class="d-none d-md-block">
<div class="list-group-item"> <div class="list-group">
<div class="d-flex justify-content-between align-items-center"> {% for main_id, cat in categories.items()|sort(attribute='1.weight', reverse=True) %}
<div> <div class="list-group-item">
<strong>{{ cat.name }}</strong>ID: {{ main_id }}) <div class="d-flex justify-content-between align-items-center flex-wrap">
<span class="badge" style="background-color: {{ cat.color }}; color: white;">{{ cat.color }}</span> <div class="mb-2 mb-md-0">
{% if cat.get('private', False) %} <strong>{{ cat.name }}</strong>ID: {{ main_id }})
<span class="badge bg-warning text-dark ms-2">私有</span> <span class="badge" style="background-color: {{ cat.color }}; color: white;">{{ cat.get('weight', 0) }}</span>
{% endif %} {% if cat.get('private', False) %}
</div> <span class="badge bg-warning text-dark ms-2">私有</span>
<div> {% endif %}
<a href="{{ url_for('edit_main_category', main_id=main_id) }}" </div>
class="btn btn-sm btn-primary me-2"> <div>
<i class="fas fa-edit"></i> 编辑 <button class="btn btn-sm btn-success me-2"
</a> data-bs-toggle="modal"
<a href="{{ url_for('delete_main_category', main_id=main_id) }}" data-bs-target="#addSubCategoryModal"
class="btn btn-sm btn-danger" onclick="setMainId('{{ main_id }}')">
onclick="return confirm('确认删除整个主分类?')"> <i class="fas fa-plus"></i> 添加子分类
<i class="fas fa-trash"></i> 删除 </button>
</a> <a href="{{ url_for('edit_main_category', main_id=main_id) }}"
class="btn btn-sm btn-primary me-2">
<i class="fas fa-edit"></i> 编辑
</a>
<a href="{{ url_for('delete_main_category', main_id=main_id) }}"
class="btn btn-sm btn-danger"
onclick="return confirm('确认删除整个主分类?')">
<i class="fas fa-trash"></i> 删除
</a>
</div>
</div> </div>
<ul class="mt-2 list-group list-group-flush">
{% 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.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> 编辑
</a>
<a href="{{ url_for('delete_sub_category', main_id=main_id, sub_id=sub_id) }}"
class="btn btn-sm btn-outline-danger"
onclick="return confirm('确认删除该子分类?')">
<i class="fas fa-trash"></i> 删除
</a>
</div>
</li>
{% endfor %}
</ul>
</div> </div>
<ul class="mt-2 list-group list-group-flush"> {% endfor %}
{% for sub_id, subData in cat.sub.items() %} </div>
<li class="list-group-item d-flex justify-content-between align-items-center"> </div>
<span>
{{ subData.name }}ID: {{ sub_id }}) <!-- 移动端显示 -->
<span class="badge" style="background-color: {{ subData.color }}; color: white;">{{ subData.color }}</span> <div class="d-md-none">
{% if cat.get('sub_private', {}).get(sub_id, False) %} <div class="accordion" id="categoryAccordion">
<span class="badge bg-warning text-dark ms-2">私有</span> {% for main_id, cat in categories.items()|sort(attribute='1.weight', reverse=True) %}
{% endif %} <div class="card mb-3">
</span> <div class="card-header d-flex justify-content-between align-items-center" style="background-color: {{ cat.color }}; color: white;">
<div> <div>
<a href="{{ url_for('edit_sub_category', main_id=main_id, sub_id=sub_id) }}" <strong>{{ cat.name }}</strong> (ID: {{ main_id }})
class="btn btn-sm btn-primary me-2"> <span class="badge bg-light text-dark ms-2">{{ cat.get('weight', 0) }}</span>
<i class="fas fa-edit"></i> 编辑 {% 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>
<a href="{{ url_for('delete_sub_category', main_id=main_id, sub_id=sub_id) }}" <a href="{{ url_for('delete_main_category', main_id=main_id) }}"
class="btn btn-sm btn-outline-danger" class="btn btn-sm btn-danger flex-grow-1"
onclick="return confirm('确认删除该子分类?')"> onclick="return confirm('确认删除整个主分类?')">
<i class="fas fa-trash"></i> 删除 <i class="fas fa-trash"></i> 删除
</a> </a>
</div> </div>
</li>
{% endfor %} <h6 class="mb-3">子分类列表</h6>
</ul> {% for sub_id, subData in cat.sub.items()|sort(attribute='1.weight', reverse=True) %}
</div> <div class="card mb-2">
{% endfor %} <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> </div>
@ -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 %}

File diff suppressed because it is too large Load Diff

View File

@ -47,64 +47,125 @@
</form> </form>
</div> </div>
<div class="table-responsive"> <!-- 桌面端显示表格 -->
<table class="table table-striped table-hover"> <div class="d-none d-md-block">
<thead class="table-light"> <div class="table-responsive">
<tr> <table class="table table-striped table-hover">
<th>图标</th> <thead class="table-light">
<th>标题</th> <tr>
<th>分类/权重</th> <th>图标</th>
<th>URL</th> <th>标题</th>
<th>操作</th> <th>分类/权重</th>
</tr> <th>URL</th>
</thead> <th>操作</th>
<tbody> </tr>
{% for app in apps %} </thead>
<tr> <tbody>
<td> {% for app in apps %}
{% if app.icon.startswith('/upload/icon/') %} <tr>
<img src="{{ app.icon }}" style="max-width: 24px; max-height: 24px;" class="img-fluid"> <td>
{% else %} {% if app.icon.startswith('/upload/icon/') %}
<i class="fas {{ app.icon }} fa-lg"></i> <img src="{{ app.icon }}" style="max-width: 24px; max-height: 24px;" class="img-fluid">
{% endif %} {% else %}
</td> <i class="fas {{ app.icon }} fa-lg"></i>
<td>
{{ app.title }}
{% if app.get('private', False) %}
<span class="badge bg-warning text-dark ms-2">私有</span>
{% endif %}
</td>
<td>
<div class="d-flex flex-wrap gap-1">
<span class="badge d-flex align-items-center" style="background-color: {{ categories[app.category.main].color }}; color: white;">
{{ categories[app.category.main].name }}
<span class="badge bg-light text-dark ms-1">{{ categories[app.category.main].weight }}</span>
</span>
{% if app.category.sub %}
<span class="badge d-flex align-items-center" style="background-color: {{ categories[app.category.main].sub[app.category.sub].color }}; color: white;">
{{ categories[app.category.main].sub[app.category.sub].name }}
<span class="badge bg-light text-dark ms-1">{{ categories[app.category.main].sub[app.category.sub].weight }}</span>
</span>
{% endif %} {% endif %}
</td>
<td>
{{ app.title }}
{% if app.get('private', False) %}
<span class="badge bg-warning text-dark ms-2">私有</span>
{% endif %}
</td>
<td>
<div class="d-flex flex-wrap gap-1">
<span class="badge d-flex align-items-center" style="background-color: {{ categories[app.category.main].color }}; color: white;">
{{ categories[app.category.main].name }}
<span class="badge bg-light text-dark ms-1">{{ categories[app.category.main].weight }}</span>
</span>
{% if app.category.sub %}
<span class="badge d-flex align-items-center" style="background-color: {{ categories[app.category.main].sub[app.category.sub].color }}; color: white;">
{{ categories[app.category.main].sub[app.category.sub].name }}
<span class="badge bg-light text-dark ms-1">{{ categories[app.category.main].sub[app.category.sub].weight }}</span>
</span>
{% endif %}
</div>
</td>
<td><a href="{{ app.url }}" target="_blank">{{ app.url[:30] }}...</a></td>
<td>
<a href="{{ url_for('edit_app', index=(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=(current_page-1)*per_page + loop.index0) }}" class="btn btn-sm btn-danger" onclick="return confirm('确定删除吗?')">
<i class="fas fa-trash"></i> 删除
</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="text-center">没有找到匹配的应用</td>
</tr>
{% endfor %}
</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>
</td> </div>
<td><a href="{{ app.url }}" target="_blank">{{ app.url[:30] }}...</a></td> </div>
<td> </div>
<a href="{{ url_for('edit_app', index=(current_page-1)*per_page + loop.index0) }}" class="btn btn-sm btn-warning"> {% else %}
<i class="fas fa-edit"></i> 编辑 <div class="col-12">
</a> <div class="alert alert-info text-center">没有找到匹配的应用</div>
<a href="{{ url_for('delete_app', index=loop.index0) }}" class="btn btn-sm btn-danger" onclick="return confirm('确定删除吗?')"> </div>
<i class="fas fa-trash"></i> 删除 {% endfor %}
</a> </div>
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="text-center">没有找到匹配的应用</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
<!-- 分页导航 --> <!-- 分页导航 -->