分页查询API
python
复制代码
@user_activity_bp.route('/api/activity_list')
def activity_list():
"""获取活动日志列表"""
try:
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
# 将user_id作为字符串处理
user_id = request.args.get('user_id', type=str)
days = request.args.get('days', 7, type=int)
start_date = datetime.now() - timedelta(days=days)
# 修改:关联用户表获取用户信息
base_query = """
SELECT
ual.id, ual.user_id, ual.request_path, ual.request_method, ual.user_agent,
ual.ip_address, ual.access_time, ual.duration_ms, ual.status_code,
u.real_name, u.mobile
FROM user_activity_log ual
LEFT JOIN user u ON ual.user_id = u.id
WHERE ual.access_time >= %s
"""
params = [start_date]
# 用户筛选
if user_id and user_id != '' and user_id != 'null':
base_query += " AND ual.user_id = %s"
params.append(user_id)
# 搜索条件
search = request.args.get('search', '')
if search:
base_query += " AND (ual.request_path LIKE %s OR ual.request_method LIKE %s)"
params.extend([f"%{search}%", f"%{search}%"])
# 排序
sort_field = request.args.get('sort_field', 'access_time')
sort_order = request.args.get('sort_order', 'desc')
if sort_field == 'access_time':
order_by = "ual.access_time DESC" if sort_order == 'desc' else "ual.access_time ASC"
elif sort_field == 'duration_ms':
order_by = "ual.duration_ms DESC" if sort_order == 'desc' else "ual.duration_ms ASC"
else:
order_by = "ual.access_time DESC"
base_query += f" ORDER BY {order_by}"
# 使用分页工具
return Paginator.paginate_template(
template_path='user_activity/activity_list.html',
data_key='activities',
query=base_query,
base_params=params
)
except Exception as e:
print(f"获取活动日志列表错误: {str(e)}")
return f"<div class='alert alert-danger'>加载数据失败: {str(e)}</div>", 500
分页条容器
html
复制代码
<div class="table-responsive">
<table class="table table-hover table-sm">
<thead>
<tr>
<th>用户ID</th>
<th>姓名</th>
<th>手机号</th>
<th>请求路径</th>
<th>方法</th>
<th>IP地址</th>
<th>访问时间</th>
<th>耗时(ms)</th>
<th>状态码</th>
</tr>
</thead>
<tbody>
{% if activities and activities|length > 0 %}
{% for activity in activities %}
<tr>
<td>{{ activity.user_id or '匿名' }}</td>
<td>{{ activity.real_name or '-' }}</td>
<td>{{ activity.mobile or '-' }}</td>
<td title="{{ activity.request_path }}">{{ activity.request_path[:40] }}{% if activity.request_path|length > 40 %}...{% endif %}</td>
<td><span class="badge bg-secondary">{{ activity.request_method }}</span></td>
<td>{{ activity.ip_address }}</td>
<td>{{ activity.access_time.strftime('%Y-%m-%d %H:%M:%S') }}</td>
<td>{{ activity.duration_ms or '-' }}</td>
<td>
{% if activity.status_code %}
{% if activity.status_code >= 200 and activity.status_code < 300 %}
<span class="badge badge-success">{{ activity.status_code }}</span>
{% elif activity.status_code >= 400 and activity.status_code < 500 %}
<span class="badge badge-warning">{{ activity.status_code }}</span>
{% else %}
<span class="badge badge-danger">{{ activity.status_code }}</span>
{% endif %}
{% else %}
<span class="badge bg-secondary">-</span>
{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="9" class="text-center py-4">
<i class="fas fa-inbox fa-2x text-muted mb-2"></i>
<p class="text-muted">暂无活动日志数据</p>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<!-- 分页信息 -->
{% if total_pages is defined %}
<div id="pageInfo"
data-total="{{ total }}"
data-page="{{ page }}"
data-per-page="{{ per_page }}"
data-total-pages="{{ total_pages }}">
</div>
{% endif %}
分页条初始化
python
复制代码
// 初始化分页(优化版)
function initPagination(data) {
const container = $('#paginationContainer');
container.empty();
// 如果总页数小于等于1,不显示分页
if (data.totalPages <= 1) return;
// 生成需要显示的页码数组(当前页前后各2页,加上首尾页)
const pageNumbers = [];
const currentPage = data.page;
const totalPages = data.totalPages;
// 添加首页
pageNumbers.push(1);
// 计算显示范围
let startPage = Math.max(2, currentPage - 2);
let endPage = Math.min(totalPages - 1, currentPage + 2);
// 如果当前页附近与首页有间隔,添加省略号
if (startPage > 2) {
pageNumbers.push('...');
}
// 添加当前页附近的页码
for (let i = startPage; i <= endPage; i++) {
pageNumbers.push(i);
}
// 如果当前页附近与末页有间隔,添加省略号
if (endPage < totalPages - 1) {
pageNumbers.push('...');
}
// 添加末页(如果总页数大于1)
if (totalPages > 1) {
pageNumbers.push(totalPages);
}
// 生成分页HTML
const pagination = `
<nav>
<ul class="pagination pagination-sm justify-content-center">
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
<a class="page-link" href="javascript:void(0)" onclick="loadActivityList(${currentPage - 1})">上一页</a>
</li>
${pageNumbers.map(p => {
// 处理省略号
if (p === '...') {
return `<li class="page-item disabled"><span class="page-link">...</span></li>`;
}
// 处理正常页码
return `
<li class="page-item ${p === currentPage ? 'active' : ''}">
<a class="page-link" href="javascript:void(0)" onclick="loadActivityList(${p})">${p}</a>
</li>
`;
}).join('')}
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
<a class="page-link" href="javascript:void(0)" onclick="loadActivityList(${currentPage + 1})">下一页</a>
</li>
</ul>
</nav>
`;
container.html(pagination);
}
后端通用分页工具
python
复制代码
from flask import request, render_template
from utils import db
class Paginator:
@staticmethod
def paginate_query(query, base_params=[], count_query=None, default_per_page=50):
"""
通用分页方法
:param query: 基础查询语句
:param base_params: 基础查询的参数列表
:param count_query: 计数查询语句(可选)
:param default_per_page: 默认每页数量
:return: 包含分页结果的字典
"""
# 获取分页参数
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', default_per_page, type=int)
# 计算偏移量
offset = (page - 1) * per_page
# 执行分页查询 - 合并基础参数和分页参数
data_query = f"{query} LIMIT %s OFFSET %s"
data_params = base_params + [per_page, offset]
data = db.fetch_all(data_query, data_params)
# 执行计数查询
if count_query:
count_result = db.fetch_one(count_query, base_params)
total = count_result['total'] if count_result else 0
else:
# 如果没有提供计数查询,尝试从基础查询生成
count_query = f"SELECT COUNT(*) AS total FROM ({query}) AS subquery"
count_result = db.fetch_one(count_query, base_params)
total = count_result['total'] if count_result else 0
# 计算总页数
total_pages = (total + per_page - 1) // per_page if per_page > 0 else 1
return {
'data': data,
'page': page,
'per_page': per_page,
'total': total,
'total_pages': total_pages
}
@staticmethod
def paginate_template(template_path, data_key, query, base_params=[], **kwargs):
"""
分页并渲染模板
:param template_path: 模板路径
:param data_key: 模板中数据的变量名
:param query: 基础查询语句
:param base_params: 基础查询的参数列表
:param kwargs: 传递给模板的额外参数
:return: 渲染后的模板
"""
result = Paginator.paginate_query(query, base_params, default_per_page=50)
return render_template(
template_path,
**{
data_key: result['data'],
'page': result['page'],
'per_page': result['per_page'],
'total': result['total'],
'total_pages': result['total_pages']
},
**kwargs
)