变迁左右滑动、自定义页脚等功能,应用编辑图标BUG修复
This commit is contained in:
parent
ee114dda50
commit
e9a755fbe1
23
README.md
23
README.md
@ -14,29 +14,28 @@
|
|||||||
### 界面优化
|
### 界面优化
|
||||||
1. 每行卡片数量支持自定义(4个、5个、6个、8个)
|
1. 每行卡片数量支持自定义(4个、5个、6个、8个)
|
||||||
2. 应用的图标支持上传图片自定义 - 已完成
|
2. 应用的图标支持上传图片自定义 - 已完成
|
||||||
3. 首页增加页脚
|
3. 自定义页脚 - 已完成
|
||||||
4. 一级分类和二级分类固定宽度,超出宽度可左右滑动查看
|
4. 一级分类和二级分类固定宽度,超出宽度可左右滑动查看 - 已完成
|
||||||
5. 气泡的小箭头靠左对齐 - 已完成
|
5. 气泡的小箭头靠左对齐 - 已完成
|
||||||
6. logo图标设置区分明亮和暗黑模式
|
6. logo图标设置区分明亮和暗黑模式
|
||||||
7. 私有应用在首页添加标识
|
7. 私有应用在首页添加标识
|
||||||
8. 首页卡片右键菜单
|
8. 首页卡片右键菜单
|
||||||
9. 应用支持配置多个URL,左键打开默认URL,右键可选择URL进行复制地址或者打开或者编辑应用
|
9. 应用支持配置多个URL,左键打开默认URL,右键可选择URL进行复制地址或者打开或者编辑应用
|
||||||
|
10. 新增应用界面便捷增加分类
|
||||||
|
11. 新增应用界面便捷增加图标图片
|
||||||
|
|
||||||
### 功能增强
|
### 功能增强
|
||||||
8. 应用管理页支持分页和按一级分类筛选
|
1. 应用管理页支持分页和按一级分类筛选 - 已完成
|
||||||
9. 应用分页和附件分页功能
|
2. 应用分页和附件分页功能 - 已完成
|
||||||
10. 网站图标自动获取功能
|
3. 网站图标自动获取功能
|
||||||
11. 书签收藏工具
|
4. 书签收藏工具
|
||||||
|
|
||||||
### BUG修复
|
### BUG修复
|
||||||
12. 应用编辑页面没有回显带入图片
|
1. 应用编辑页面没有回显带入图片 - 已解决
|
||||||
|
|
||||||
### 批量操作
|
### 批量操作
|
||||||
13. 应用批量选择功能:
|
1. 应用批量选择功能:
|
||||||
- 批量删除
|
- 批量删除
|
||||||
- 批量设置私有化/公有化
|
- 批量设置私有化/公有化
|
||||||
14. 附件批量选择功能:
|
2. 附件批量选择功能:
|
||||||
- 批量删除
|
- 批量删除
|
||||||
|
|
||||||
15. 新增应用界面便捷增加分类
|
|
||||||
16. 新增应用界面便捷增加图标图片
|
|
||||||
95
app.py
95
app.py
@ -11,6 +11,7 @@ from functools import wraps
|
|||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
import requests
|
import requests
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = 'your_secret_key_here' # 请更改为安全的密钥
|
app.secret_key = 'your_secret_key_here' # 请更改为安全的密钥
|
||||||
@ -44,6 +45,7 @@ SETTINGS_FILE = os.path.join(DATA_DIR, 'settings.json')
|
|||||||
|
|
||||||
GUEST_SETTINGS_FILE = os.path.join(DATA_DIR, 'guest_settings.json')
|
GUEST_SETTINGS_FILE = os.path.join(DATA_DIR, 'guest_settings.json')
|
||||||
|
|
||||||
|
|
||||||
def migrate_settings(settings):
|
def migrate_settings(settings):
|
||||||
"""迁移旧版设置到新版格式"""
|
"""迁移旧版设置到新版格式"""
|
||||||
if 'admin_password' in settings:
|
if 'admin_password' in settings:
|
||||||
@ -59,6 +61,12 @@ def migrate_settings(settings):
|
|||||||
'123456',
|
'123456',
|
||||||
method='pbkdf2:sha256'
|
method='pbkdf2:sha256'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 确保有页脚设置
|
||||||
|
if 'footer_html' not in settings:
|
||||||
|
settings[
|
||||||
|
'footer_html'] = '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
|
|
||||||
@ -78,7 +86,8 @@ def init_settings():
|
|||||||
"logo_icon": "fa-th-list",
|
"logo_icon": "fa-th-list",
|
||||||
"logo_image": "",
|
"logo_image": "",
|
||||||
"uploaded_backgrounds": [],
|
"uploaded_backgrounds": [],
|
||||||
"uploaded_logos": []
|
"uploaded_logos": [],
|
||||||
|
"footer_html": '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
|
||||||
}
|
}
|
||||||
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||||
json.dump(default_settings, f, ensure_ascii=False, indent=2)
|
json.dump(default_settings, f, ensure_ascii=False, indent=2)
|
||||||
@ -90,6 +99,14 @@ def init_settings():
|
|||||||
if 'admin_password_hash' not in settings:
|
if 'admin_password_hash' not in settings:
|
||||||
# 需要迁移
|
# 需要迁移
|
||||||
settings = migrate_settings(settings)
|
settings = migrate_settings(settings)
|
||||||
|
# 添加新字段的默认值
|
||||||
|
if 'footer_html' not in settings:
|
||||||
|
settings['footer_html'] = '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
|
||||||
|
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(settings, f, ensure_ascii=False, indent=2)
|
||||||
|
elif 'footer_html' not in settings:
|
||||||
|
# 已有密码哈希但没有页脚设置的情况
|
||||||
|
settings['footer_html'] = '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
|
||||||
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||||
json.dump(settings, f, ensure_ascii=False, indent=2)
|
json.dump(settings, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
@ -356,6 +373,7 @@ def system_settings():
|
|||||||
settings['site_title'] = request.form.get('site_title', '应用导航中心')
|
settings['site_title'] = request.form.get('site_title', '应用导航中心')
|
||||||
settings['show_logo'] = 'show_logo' in request.form
|
settings['show_logo'] = 'show_logo' in request.form
|
||||||
settings['logo_type'] = request.form.get('logo_type', 'icon')
|
settings['logo_type'] = request.form.get('logo_type', 'icon')
|
||||||
|
settings['footer_html'] = request.form.get('footer_html', '')
|
||||||
|
|
||||||
# 处理logo设置
|
# 处理logo设置
|
||||||
if settings['logo_type'] == 'icon':
|
if settings['logo_type'] == 'icon':
|
||||||
@ -467,9 +485,38 @@ def system_settings():
|
|||||||
@app.route('/attachments')
|
@app.route('/attachments')
|
||||||
@login_required
|
@login_required
|
||||||
def manage_attachments():
|
def manage_attachments():
|
||||||
|
type_filter = request.args.get('type', 'all')
|
||||||
|
search_query = request.args.get('search', '').lower()
|
||||||
|
page = int(request.args.get('page', 1))
|
||||||
|
per_page = 10 # 每页显示10条数据
|
||||||
|
|
||||||
settings = load_settings()
|
settings = load_settings()
|
||||||
attachments = load_attachments()
|
attachments = load_attachments()
|
||||||
return render_template('attachments.html', settings=settings, attachments=attachments)
|
|
||||||
|
# 应用筛选
|
||||||
|
filtered_attachments = []
|
||||||
|
for attachment in attachments:
|
||||||
|
# 类型筛选
|
||||||
|
if type_filter != 'all' and attachment['type'] != type_filter:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 搜索筛选
|
||||||
|
if search_query and search_query not in attachment['filename'].lower():
|
||||||
|
continue
|
||||||
|
|
||||||
|
filtered_attachments.append(attachment)
|
||||||
|
|
||||||
|
# 分页处理
|
||||||
|
total_pages = ceil(len(filtered_attachments) / per_page)
|
||||||
|
paginated_attachments = filtered_attachments[(page - 1) * per_page: page * per_page]
|
||||||
|
|
||||||
|
return render_template('attachments.html',
|
||||||
|
settings=settings,
|
||||||
|
attachments=paginated_attachments,
|
||||||
|
current_page=page,
|
||||||
|
total_pages=total_pages,
|
||||||
|
search_query=search_query,
|
||||||
|
type_filter=type_filter)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/upload_attachment', methods=['POST'])
|
@app.route('/upload_attachment', methods=['POST'])
|
||||||
@ -573,21 +620,45 @@ def handle_settings():
|
|||||||
@login_required
|
@login_required
|
||||||
def index():
|
def index():
|
||||||
category_filter = request.args.get('category')
|
category_filter = request.args.get('category')
|
||||||
|
search_query = request.args.get('search', '').lower()
|
||||||
|
page = int(request.args.get('page', 1))
|
||||||
|
per_page = 10 # 每页显示10条数据
|
||||||
|
|
||||||
apps = load_apps()
|
apps = load_apps()
|
||||||
categories = load_categories()
|
categories = load_categories()
|
||||||
settings = load_settings()
|
settings = load_settings()
|
||||||
|
|
||||||
if category_filter:
|
# 应用筛选
|
||||||
filtered_apps = []
|
filtered_apps = []
|
||||||
for app in apps:
|
for app in apps:
|
||||||
# 检查是否匹配主分类或子分类
|
# 分类筛选
|
||||||
if (app['category']['main'] == category_filter or
|
if category_filter and not (app['category']['main'] == category_filter or
|
||||||
app['category']['sub'] == category_filter or
|
app['category']['sub'] == category_filter or
|
||||||
(category_filter in categories and app['category']['main'] == category_filter)):
|
(category_filter in categories and app['category']['main'] == category_filter)):
|
||||||
filtered_apps.append(app)
|
continue
|
||||||
apps = filtered_apps
|
|
||||||
|
|
||||||
return render_template('manage.html', apps=apps, settings=settings, categories=categories)
|
# 搜索筛选
|
||||||
|
if search_query and not (search_query in app['title'].lower() or
|
||||||
|
search_query in app['url'].lower() or
|
||||||
|
search_query in app.get('description', '').lower() or
|
||||||
|
search_query in app['category']['main'].lower() or
|
||||||
|
search_query in app['category']['sub'].lower()):
|
||||||
|
continue
|
||||||
|
|
||||||
|
filtered_apps.append(app)
|
||||||
|
|
||||||
|
# 分页处理
|
||||||
|
total_pages = ceil(len(filtered_apps) / per_page)
|
||||||
|
paginated_apps = filtered_apps[(page - 1) * per_page: page * per_page]
|
||||||
|
|
||||||
|
return render_template('manage.html',
|
||||||
|
apps=paginated_apps,
|
||||||
|
settings=settings,
|
||||||
|
categories=categories,
|
||||||
|
current_page=page,
|
||||||
|
total_pages=total_pages,
|
||||||
|
search_query=search_query,
|
||||||
|
category_filter=category_filter)
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def navigation():
|
def navigation():
|
||||||
|
|||||||
@ -8,7 +8,8 @@
|
|||||||
"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/5378dda810964da9a7515ec844628738.png",
|
"logo_image": "/upload/logo/b2c128cf2d4e47daa349c5e7f38c932c.png",
|
||||||
"dark_bg_rotate": false,
|
"dark_bg_rotate": false,
|
||||||
"admin_password_hash": "scrypt:32768:8:1$mPFCfRRzOrcjE6z3$e72ef50a2d3f7292f64bcfc5e21f32c95ea8665414ea8d5f6b216735d68f151166c99fae21132c7949bd92ea32041f969cd4a471adb110a99328089541f7dccb"
|
"admin_password_hash": "scrypt:32768:8:1$mPFCfRRzOrcjE6z3$e72ef50a2d3f7292f64bcfc5e21f32c95ea8665414ea8d5f6b216735d68f151166c99fae21132c7949bd92ea32041f969cd4a471adb110a99328089541f7dccb",
|
||||||
|
"footer_html": "<div class=\"flex flex-col items-center text-slate-400 text-sm py-4\" style=\"margin-top:100px\">\r\n <div class=\"flex items-center\">\r\n <span>© 2023 AIDaohang. All rights reserved.</span>\r\n <span class=\"mx-2\">|</span>\r\n <span>Powered by <a href=\"https://github.com\" target=\"_blank\" class=\"hover:text-slate-200 ml-1\">AIDaohang</a></span>\r\n </div>\r\n</div>"
|
||||||
}
|
}
|
||||||
@ -32,7 +32,25 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h5 class="border-bottom pb-2 mb-3">附件列表</h5>
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h5 class="border-bottom pb-2 mb-0">附件列表</h5>
|
||||||
|
<form class="d-flex" method="GET">
|
||||||
|
<select name="type" class="form-select me-2" style="width: 120px;">
|
||||||
|
<option value="all" {% if type_filter == 'all' %}selected{% endif %}>所有类型</option>
|
||||||
|
<option value="logo" {% if type_filter == 'logo' %}selected{% endif %}>Logo</option>
|
||||||
|
<option value="background" {% if type_filter == 'background' %}selected{% endif %}>背景图片</option>
|
||||||
|
<option value="video" {% if type_filter == 'video' %}selected{% endif %}>背景视频</option>
|
||||||
|
<option value="icon" {% if type_filter == 'icon' %}selected{% endif %}>图标图片</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" name="search" class="form-control me-2" placeholder="搜索文件名..." value="{{ search_query }}">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</button>
|
||||||
|
{% if type_filter != 'all' or search_query %}
|
||||||
|
<a href="{{ url_for('manage_attachments') }}" class="btn btn-secondary ms-2">重置</a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
@ -83,12 +101,37 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="text-center">暂无附件</td>
|
<td colspan="5" class="text-center">没有找到匹配的附件</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页导航 -->
|
||||||
|
{% if total_pages > 1 %}
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
<li class="page-item {% if current_page == 1 %}disabled{% endif %}">
|
||||||
|
<a class="page-link" href="{{ url_for('manage_attachments', page=current_page-1, type=type_filter, search=search_query) }}" aria-label="Previous">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% for page_num in range(1, total_pages + 1) %}
|
||||||
|
<li class="page-item {% if page_num == current_page %}active{% endif %}">
|
||||||
|
<a class="page-link" href="{{ url_for('manage_attachments', page=page_num, type=type_filter, search=search_query) }}">{{ page_num }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<li class="page-item {% if current_page == total_pages %}disabled{% endif %}">
|
||||||
|
<a class="page-link" href="{{ url_for('manage_attachments', page=current_page+1, type=type_filter, search=search_query) }}" aria-label="Next">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<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">
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<link rel="stylesheet" href="/static/css/all.min.css">
|
<link rel="stylesheet" href="/static/css/all.min.css">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
padding-top: 20px; /* 修改了这里 */
|
padding-top: 0px;
|
||||||
padding-bottom: 40px;
|
padding-bottom: 40px;
|
||||||
}
|
}
|
||||||
.navbar {
|
.navbar {
|
||||||
@ -120,9 +120,9 @@
|
|||||||
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<p>© 2025 导航管理系统</p>
|
{% include 'footer.html' %}
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/bootstrap.bundle.min.js"></script>
|
<script src="/static/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|||||||
@ -17,64 +17,69 @@
|
|||||||
<label class="form-label">URL</label>
|
<label class="form-label">URL</label>
|
||||||
<input type="url" name="url" class="form-control" value="{{ app.url }}" required>
|
<input type="url" name="url" class="form-control" value="{{ app.url }}" required>
|
||||||
</div>
|
</div>
|
||||||
<!-- 在图标选择部分修改 -->
|
<!-- 修改后的图标选择部分 -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">图标</label>
|
<label class="form-label">图标</label>
|
||||||
<input type="hidden" name="icon" id="selectedIcon" value="" required>
|
<input type="hidden" name="icon" id="selectedIcon" value="{{ app.icon }}" required>
|
||||||
<div class="d-flex align-items-center mb-3">
|
<div class="d-flex align-items-center mb-3">
|
||||||
<div class="icon-preview me-3">
|
<div class="icon-preview me-3">
|
||||||
<i id="iconPreview" class="fas fa-question-circle fa-2x"></i>
|
{% if app.icon.startswith('/upload/icon/') %}
|
||||||
<img id="iconImagePreview" src="" style="display: none; max-width: 32px; max-height: 32px;">
|
<img id="iconImagePreview" src="{{ app.icon }}" style="max-width: 32px; max-height: 32px;">
|
||||||
</div>
|
<i id="iconPreview" class="fas fa-question-circle fa-2x" style="display: none;"></i>
|
||||||
<button type="button" class="btn btn-outline-primary me-2" data-bs-toggle="modal" data-bs-target="#iconModal">
|
{% else %}
|
||||||
<i class="fas fa-icons me-2"></i>选择图标
|
<i id="iconPreview" class="{{ app.icon }} fa-2x"></i>
|
||||||
</button>
|
<img id="iconImagePreview" src="" style="display: none; max-width: 32px; max-height: 32px;">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#iconImageModal">
|
{% endif %}
|
||||||
<i class="fas fa-image me-2"></i>选择图片
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 添加图标图片选择模态框 -->
|
|
||||||
<div class="modal fade" id="iconImageModal" tabindex="-1" aria-labelledby="iconImageModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-xl">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="iconImageModalLabel">选择图标图片</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
|
||||||
<input type="text" id="iconImageSearch" class="form-control" placeholder="搜索图标图片...">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row row-cols-3 row-cols-sm-4 row-cols-md-5 row-cols-lg-6 row-cols-xl-8 g-3" id="iconImageGrid">
|
<button type="button" class="btn btn-outline-primary me-2" data-bs-toggle="modal" data-bs-target="#iconModal">
|
||||||
{% for attachment in attachments if attachment.type == 'icon' %}
|
<i class="fas fa-icons me-2"></i>选择图标
|
||||||
<div class="col">
|
</button>
|
||||||
<div class="icon-image-item p-3 text-center rounded" data-filename="{{ attachment.filename }}" title="{{ attachment.filename }}">
|
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#iconImageModal">
|
||||||
<img src="{{ url_for('uploaded_icon', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail mb-2">
|
<i class="fas fa-image me-2"></i>选择图片
|
||||||
<div class="small text-truncate">{{ attachment.filename }}</div>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图标图片选择模态框 -->
|
||||||
|
<div class="modal fade" id="iconImageModal" tabindex="-1" aria-labelledby="iconImageModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="iconImageModalLabel">选择图标图片</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
|
<input type="text" id="iconImageSearch" class="form-control" placeholder="搜索图标图片...">
|
||||||
|
</div>
|
||||||
|
<div class="row row-cols-3 row-cols-sm-4 row-cols-md-5 row-cols-lg-6 row-cols-xl-8 g-3" id="iconImageGrid">
|
||||||
|
{% for attachment in attachments if attachment.type == 'icon' %}
|
||||||
|
<div class="col">
|
||||||
|
<div class="icon-image-item p-3 text-center rounded" data-filename="{{ attachment.filename }}" title="{{ attachment.filename }}">
|
||||||
|
<img src="{{ url_for('uploaded_icon', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail mb-2">
|
||||||
|
<div class="small text-truncate">{{ attachment.filename }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-12 text-center py-3">
|
||||||
|
<p>暂无图标图片</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
<div class="modal-footer">
|
||||||
<div class="col-12 text-center py-3">
|
<div class="me-auto">
|
||||||
<p>暂无图标图片</p>
|
<span id="selectedIconImageName">未选择图片</span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="confirmIconImage">确认选择</button>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<div class="me-auto">
|
|
||||||
<span id="selectedIconImageName">未选择图片</span>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
|
||||||
<button type="button" class="btn btn-primary" id="confirmIconImage">确认选择</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">主分类</label>
|
<label class="form-label">主分类</label>
|
||||||
<select name="main_category" id="main_category" class="form-select" required>
|
<select name="main_category" id="main_category" class="form-select" required>
|
||||||
@ -205,7 +210,10 @@
|
|||||||
document.getElementById('confirmIcon').addEventListener('click', function() {
|
document.getElementById('confirmIcon').addEventListener('click', function() {
|
||||||
if (selectedIcon) {
|
if (selectedIcon) {
|
||||||
document.getElementById('selectedIcon').value = selectedIcon;
|
document.getElementById('selectedIcon').value = selectedIcon;
|
||||||
document.getElementById('iconPreview').className = selectedIcon + ' fa-2x';
|
const iconPreview = document.getElementById('iconPreview');
|
||||||
|
iconPreview.className = selectedIcon + ' fa-2x';
|
||||||
|
iconPreview.style.display = 'inline';
|
||||||
|
document.getElementById('iconImagePreview').style.display = 'none';
|
||||||
bootstrap.Modal.getInstance(document.getElementById('iconModal')).hide();
|
bootstrap.Modal.getInstance(document.getElementById('iconModal')).hide();
|
||||||
} else {
|
} else {
|
||||||
alert('请先选择一个图标');
|
alert('请先选择一个图标');
|
||||||
@ -238,63 +246,61 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 初始化时高亮已选图标
|
// 初始化时高亮已选图标
|
||||||
if (selectedIcon) {
|
if (selectedIcon && !selectedIcon.startsWith('/upload/icon/')) {
|
||||||
const selectedItem = document.querySelector(`.icon-item[data-icon="${selectedIcon}"]`);
|
const selectedItem = document.querySelector(`.icon-item[data-icon="${selectedIcon}"]`);
|
||||||
if (selectedItem) {
|
if (selectedItem) {
|
||||||
selectedItem.classList.add('selected');
|
selectedItem.classList.add('selected');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
// 图标图片选择功能
|
|
||||||
let selectedIconImage = '';
|
|
||||||
let selectedIconImageName = '';
|
|
||||||
|
|
||||||
// 图标图片搜索功能
|
// 图标图片搜索功能
|
||||||
document.getElementById('iconImageSearch').addEventListener('input', function() {
|
document.getElementById('iconImageSearch').addEventListener('input', function() {
|
||||||
const searchTerm = this.value.toLowerCase();
|
const searchTerm = this.value.toLowerCase();
|
||||||
const iconImageItems = document.querySelectorAll('.icon-image-item');
|
const iconImageItems = document.querySelectorAll('.icon-image-item');
|
||||||
|
|
||||||
iconImageItems.forEach(item => {
|
iconImageItems.forEach(item => {
|
||||||
const filename = item.dataset.filename.toLowerCase();
|
const filename = item.dataset.filename.toLowerCase();
|
||||||
if (filename.includes(searchTerm)) {
|
if (filename.includes(searchTerm)) {
|
||||||
item.closest('.col').style.display = 'block';
|
item.closest('.col').style.display = 'block';
|
||||||
} else {
|
} else {
|
||||||
item.closest('.col').style.display = 'none';
|
item.closest('.col').style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// 图标图片点击选择
|
|
||||||
document.querySelectorAll('.icon-image-item').forEach(item => {
|
|
||||||
item.addEventListener('click', function() {
|
|
||||||
// 移除所有选中状态
|
|
||||||
document.querySelectorAll('.icon-image-item').forEach(i => {
|
|
||||||
i.classList.remove('selected');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加选中状态
|
// 图标图片点击选择
|
||||||
this.classList.add('selected');
|
document.querySelectorAll('.icon-image-item').forEach(item => {
|
||||||
selectedIconImage = this.dataset.filename;
|
item.addEventListener('click', function() {
|
||||||
selectedIconImageName = this.getAttribute('title');
|
// 移除所有选中状态
|
||||||
|
document.querySelectorAll('.icon-image-item').forEach(i => {
|
||||||
|
i.classList.remove('selected');
|
||||||
|
});
|
||||||
|
|
||||||
// 更新底部显示
|
// 添加选中状态
|
||||||
document.getElementById('selectedIconImageName').textContent = selectedIconImageName;
|
this.classList.add('selected');
|
||||||
|
selectedIconImage = this.dataset.filename;
|
||||||
|
selectedIconImageName = this.getAttribute('title');
|
||||||
|
|
||||||
|
// 更新底部显示
|
||||||
|
document.getElementById('selectedIconImageName').textContent = selectedIconImageName;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 确认选择图标图片
|
||||||
|
document.getElementById('confirmIconImage').addEventListener('click', function() {
|
||||||
|
if (selectedIconImage) {
|
||||||
|
const iconPath = '/upload/icon/' + selectedIconImage;
|
||||||
|
document.getElementById('selectedIcon').value = iconPath;
|
||||||
|
const iconImagePreview = document.getElementById('iconImagePreview');
|
||||||
|
iconImagePreview.src = iconPath;
|
||||||
|
iconImagePreview.style.display = 'inline';
|
||||||
|
document.getElementById('iconPreview').style.display = 'none';
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById('iconImageModal')).hide();
|
||||||
|
} else {
|
||||||
|
alert('请先选择一张图片');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// 确认选择图标图片
|
|
||||||
document.getElementById('confirmIconImage').addEventListener('click', function() {
|
|
||||||
if (selectedIconImage) {
|
|
||||||
document.getElementById('selectedIcon').value = '/upload/icon/' + selectedIconImage;
|
|
||||||
document.getElementById('iconPreview').style.display = 'none';
|
|
||||||
const iconImagePreview = document.getElementById('iconImagePreview');
|
|
||||||
iconImagePreview.src = '/upload/icon/' + selectedIconImage;
|
|
||||||
iconImagePreview.style.display = 'inline';
|
|
||||||
bootstrap.Modal.getInstance(document.getElementById('iconImageModal')).hide();
|
|
||||||
} else {
|
|
||||||
alert('请先选择一张图片');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -325,12 +331,29 @@ document.getElementById('confirmIconImage').addEventListener('click', function()
|
|||||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
#iconGrid {
|
.icon-image-item {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-image-item:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-image-item.selected {
|
||||||
|
background-color: #e7f1ff;
|
||||||
|
border-color: #86b7fe;
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
#iconGrid, #iconImageGrid {
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#selectedIconName {
|
#selectedIconName, #selectedIconImageName {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #0d6efd;
|
color: #0d6efd;
|
||||||
}
|
}
|
||||||
|
|||||||
6
templates/footer.html
Normal file
6
templates/footer.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<!-- templates/footer.html -->
|
||||||
|
{% if settings and settings.footer_html %}
|
||||||
|
{{ settings.footer_html|safe }}
|
||||||
|
{% else %}
|
||||||
|
<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>
|
||||||
|
{% endif %}
|
||||||
2298
templates/index.html
2298
templates/index.html
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,15 @@
|
|||||||
<i class="fas fa-plus"></i> 添加应用
|
<i class="fas fa-plus"></i> 添加应用
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<form class="d-flex" method="GET">
|
||||||
|
<input type="hidden" name="category" value="{{ category_filter }}">
|
||||||
|
<input type="text" name="search" class="form-control form-control-sm me-2" placeholder="搜索应用..." value="{{ search_query }}">
|
||||||
|
<button type="submit" class="btn btn-sm btn-light">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@ -31,6 +40,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button type="submit" class="btn btn-primary">筛选</button>
|
<button type="submit" class="btn btn-primary">筛选</button>
|
||||||
|
{% if category_filter or search_query %}
|
||||||
|
<a href="{{ url_for('index') }}" class="btn btn-secondary ms-2">重置</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -86,10 +98,39 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center">没有找到匹配的应用</td>
|
||||||
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页导航 -->
|
||||||
|
{% if total_pages > 1 %}
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
<li class="page-item {% if current_page == 1 %}disabled{% endif %}">
|
||||||
|
<a class="page-link" href="{{ url_for('index', page=current_page-1, category=category_filter, search=search_query) }}" aria-label="Previous">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% for page_num in range(1, total_pages + 1) %}
|
||||||
|
<li class="page-item {% if page_num == current_page %}active{% endif %}">
|
||||||
|
<a class="page-link" href="{{ url_for('index', page=page_num, category=category_filter, search=search_query) }}">{{ page_num }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<li class="page-item {% if current_page == total_pages %}disabled{% endif %}">
|
||||||
|
<a class="page-link" href="{{ url_for('index', page=current_page+1, category=category_filter, search=search_query) }}" aria-label="Next">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -280,6 +280,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 页脚设置 -->
|
||||||
|
<div class="card mb-4 border-light shadow-sm">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<h5 class="mb-0"><i class="fas fa-code me-2"></i>页脚设置</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="footer_html" class="form-label">自定义页脚HTML代码</label>
|
||||||
|
<textarea class="form-control" id="footer_html" name="footer_html" rows="4">{{ settings.footer_html if settings.footer_html else '' }}</textarea>
|
||||||
|
<div class="form-text">支持HTML代码,可用于添加版权信息、统计代码等</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="preview-area mt-3 p-3 bg-light rounded">
|
||||||
|
<h6>预览效果:</h6>
|
||||||
|
<div id="footer_preview">
|
||||||
|
{{ settings.footer_html|safe if settings.footer_html else '' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 修改密码 -->
|
<!-- 修改密码 -->
|
||||||
<div class="card mb-4 border-light shadow-sm">
|
<div class="card mb-4 border-light shadow-sm">
|
||||||
<div class="card-header bg-light">
|
<div class="card-header bg-light">
|
||||||
@ -464,6 +485,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// 页脚预览
|
||||||
|
document.getElementById('footer_html').addEventListener('input', function() {
|
||||||
|
document.getElementById('footer_preview').innerHTML = this.value;
|
||||||
|
});
|
||||||
// 全局选择函数
|
// 全局选择函数
|
||||||
function selectLogo(filename) {
|
function selectLogo(filename) {
|
||||||
document.getElementById('selected_logo').value = filename;
|
document.getElementById('selected_logo').value = filename;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user