分页查询API
@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.mobileFROM user_activity_log ualLEFT JOIN user u ON ual.user_id = u.idWHERE 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
分页条容器
<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 %}
分页条初始化
// 初始化分页(优化版)
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);}// 生成分页HTMLconst 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);
}
后端通用分页工具
from flask import request, render_template
from utils import dbclass Paginator:@staticmethoddef 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 0else:# 如果没有提供计数查询,尝试从基础查询生成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 1return {'data': data,'page': page,'per_page': per_page,'total': total,'total_pages': total_pages}@staticmethoddef 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)