mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-04-22 10:46:12 +02:00
Add files via upload
This commit is contained in:
@@ -349,6 +349,18 @@ func New(cfg *config.Config, log *logger.Logger) (*App, error) {
|
||||
}
|
||||
configHandler.SetVulnerabilityToolRegistrar(vulnerabilityRegistrar)
|
||||
|
||||
// 设置Skills工具注册器(内置工具,必须设置)
|
||||
skillsRegistrar := func() error {
|
||||
// 创建一个适配器,将database.DB适配为SkillStatsStorage接口
|
||||
var skillStatsStorage skills.SkillStatsStorage
|
||||
if db != nil {
|
||||
skillStatsStorage = &skillStatsDBAdapter{db: db}
|
||||
}
|
||||
skills.RegisterSkillsToolWithStorage(mcpServer, skillsManager, skillStatsStorage, log.Logger)
|
||||
return nil
|
||||
}
|
||||
configHandler.SetSkillsToolRegistrar(skillsRegistrar)
|
||||
|
||||
// 设置知识库初始化器(用于动态初始化,需要在 App 创建后设置)
|
||||
configHandler.SetKnowledgeInitializer(func() (*handler.KnowledgeHandler, error) {
|
||||
knowledgeHandler, err := initializeKnowledge(cfg, db, knowledgeDBConn, mcpServer, agentHandler, app, log.Logger)
|
||||
|
||||
@@ -28,6 +28,9 @@ type KnowledgeToolRegistrar func() error
|
||||
// VulnerabilityToolRegistrar 漏洞工具注册器接口
|
||||
type VulnerabilityToolRegistrar func() error
|
||||
|
||||
// SkillsToolRegistrar Skills工具注册器接口
|
||||
type SkillsToolRegistrar func() error
|
||||
|
||||
// RetrieverUpdater 检索器更新接口
|
||||
type RetrieverUpdater interface {
|
||||
UpdateConfig(config *knowledge.RetrievalConfig)
|
||||
@@ -52,6 +55,7 @@ type ConfigHandler struct {
|
||||
externalMCPMgr *mcp.ExternalMCPManager // 外部MCP管理器
|
||||
knowledgeToolRegistrar KnowledgeToolRegistrar // 知识库工具注册器(可选)
|
||||
vulnerabilityToolRegistrar VulnerabilityToolRegistrar // 漏洞工具注册器(可选)
|
||||
skillsToolRegistrar SkillsToolRegistrar // Skills工具注册器(可选)
|
||||
retrieverUpdater RetrieverUpdater // 检索器更新器(可选)
|
||||
knowledgeInitializer KnowledgeInitializer // 知识库初始化器(可选)
|
||||
appUpdater AppUpdater // App更新器(可选)
|
||||
@@ -110,6 +114,13 @@ func (h *ConfigHandler) SetVulnerabilityToolRegistrar(registrar VulnerabilityToo
|
||||
h.vulnerabilityToolRegistrar = registrar
|
||||
}
|
||||
|
||||
// SetSkillsToolRegistrar 设置Skills工具注册器
|
||||
func (h *ConfigHandler) SetSkillsToolRegistrar(registrar SkillsToolRegistrar) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.skillsToolRegistrar = registrar
|
||||
}
|
||||
|
||||
// SetRetrieverUpdater 设置检索器更新器
|
||||
func (h *ConfigHandler) SetRetrieverUpdater(updater RetrieverUpdater) {
|
||||
h.mu.Lock()
|
||||
@@ -869,6 +880,16 @@ func (h *ConfigHandler) ApplyConfig(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 重新注册Skills工具(内置工具,必须注册)
|
||||
if h.skillsToolRegistrar != nil {
|
||||
h.logger.Info("重新注册Skills工具")
|
||||
if err := h.skillsToolRegistrar(); err != nil {
|
||||
h.logger.Error("重新注册Skills工具失败", zap.Error(err))
|
||||
} else {
|
||||
h.logger.Info("Skills工具已重新注册")
|
||||
}
|
||||
}
|
||||
|
||||
// 如果知识库启用,重新注册知识库工具
|
||||
if h.config.Knowledge.Enabled && h.knowledgeToolRegistrar != nil {
|
||||
h.logger.Info("重新注册知识库工具")
|
||||
|
||||
+88
-48
@@ -398,7 +398,7 @@ body {
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: 20px 24px;
|
||||
padding: 12px 20px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
@@ -410,14 +410,14 @@ body {
|
||||
|
||||
.page-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.page-header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -425,10 +425,18 @@ body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 24px;
|
||||
padding: 16px 20px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 任务管理页面内容区域底部圆角 */
|
||||
#page-tasks .page-content {
|
||||
/* 确保底部左右角都是圆角 */
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 对话页面布局 */
|
||||
.chat-page-layout {
|
||||
display: flex;
|
||||
@@ -3150,18 +3158,20 @@ header {
|
||||
}
|
||||
|
||||
.btn-search {
|
||||
padding: 8px 16px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-width: 40px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.btn-search:hover {
|
||||
@@ -3263,7 +3273,7 @@ header {
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Skills管理页面分页优化 */
|
||||
@@ -3297,16 +3307,19 @@ header {
|
||||
position: relative;
|
||||
/* 添加四个角的圆角,与上方卡片保持一致 */
|
||||
border-radius: 8px;
|
||||
/* 确保底部左右角都是圆角 */
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
|
||||
.pagination-fixed .pagination {
|
||||
margin-top: 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 16px 20px;
|
||||
padding: 10px 16px;
|
||||
background: var(--bg-primary);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
/* 确保分页内容与列表内容对齐 */
|
||||
width: 100%;
|
||||
@@ -3315,15 +3328,18 @@ header {
|
||||
border-top-color: rgba(233, 236, 239, 0.6);
|
||||
/* 添加四个角的圆角,与上方卡片保持一致 */
|
||||
border-radius: 8px;
|
||||
/* 确保底部左右角都是圆角 */
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
|
||||
/* 左侧:信息显示和每页数量选择器 - 更自然的设计 */
|
||||
.pagination-fixed .pagination-info {
|
||||
font-size: 0.875rem;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
/* 去掉背景色和边框,更自然 */
|
||||
padding: 0;
|
||||
@@ -3340,25 +3356,27 @@ header {
|
||||
.pagination-fixed .pagination-info .pagination-page-size {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 0.875rem;
|
||||
gap: 6px;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.pagination-fixed .pagination-info .pagination-page-size select {
|
||||
padding: 6px 10px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 0.875rem;
|
||||
font-size: 0.8125rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 70px;
|
||||
min-width: 60px;
|
||||
font-weight: 500;
|
||||
/* 更柔和的边框 */
|
||||
border-color: rgba(233, 236, 239, 0.8);
|
||||
/* 确保四个角都是圆角 */
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.pagination-fixed .pagination-info .pagination-page-size select:focus {
|
||||
@@ -3382,14 +3400,16 @@ header {
|
||||
}
|
||||
|
||||
.pagination-fixed .pagination-controls .btn-secondary {
|
||||
padding: 7px 14px;
|
||||
font-size: 0.875rem;
|
||||
padding: 5px 12px;
|
||||
font-size: 0.8125rem;
|
||||
min-width: auto;
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 500;
|
||||
border-radius: 8px;
|
||||
border-radius: 6px;
|
||||
/* 更柔和的边框 */
|
||||
border-color: rgba(233, 236, 239, 0.8);
|
||||
/* 确保四个角都是圆角 */
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.pagination-fixed .pagination-controls .btn-secondary:hover:not(:disabled) {
|
||||
@@ -3414,11 +3434,18 @@ header {
|
||||
}
|
||||
|
||||
.pagination-fixed .pagination-page {
|
||||
font-size: 0.875rem;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary);
|
||||
padding: 0 12px;
|
||||
padding: 5px 10px;
|
||||
white-space: nowrap;
|
||||
font-weight: 400;
|
||||
/* 添加圆角设计,四个角都是圆的 */
|
||||
border-radius: 6px !important;
|
||||
background: var(--bg-secondary) !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 响应式优化 */
|
||||
@@ -3598,12 +3625,12 @@ header {
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 10px 20px;
|
||||
padding: 7px 16px;
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
@@ -3616,12 +3643,12 @@ header {
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
padding: 10px 20px;
|
||||
padding: 7px 16px;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
font-size: 0.9375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
@@ -6865,9 +6892,10 @@ header {
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
padding: 16px;
|
||||
background: var(--bg-secondary);
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tasks-filters label {
|
||||
@@ -7260,8 +7288,12 @@ header {
|
||||
|
||||
/* 批量任务相关样式 */
|
||||
.batch-queues-section {
|
||||
margin-top: 16px;
|
||||
padding-top: 8px;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
/* 确保底部左右角都是圆角 */
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.batch-queues-header {
|
||||
@@ -7283,6 +7315,7 @@ header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.batch-queue-item {
|
||||
@@ -9218,17 +9251,17 @@ header {
|
||||
|
||||
/* Skills管理页面样式 */
|
||||
.skills-controls {
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.skills-stats-bar {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.skill-stat-item {
|
||||
@@ -9238,41 +9271,45 @@ header {
|
||||
}
|
||||
|
||||
.skill-stat-label {
|
||||
font-size: 0.8125rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.skill-stat-value {
|
||||
font-size: 1.25rem;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.skills-filters {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.skills-filters input {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
font-size: 0.875rem;
|
||||
box-sizing: border-box;
|
||||
height: 32px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.skills-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.skill-item {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
@@ -9285,7 +9322,7 @@ header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.skill-item-info {
|
||||
@@ -9293,28 +9330,30 @@ header {
|
||||
}
|
||||
|
||||
.skill-item-name {
|
||||
font-size: 1.125rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 8px 0;
|
||||
margin: 0 0 4px 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.skill-item-desc {
|
||||
font-size: 0.875rem;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.skill-item-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
background: none;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 6px;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -9337,10 +9376,11 @@ header {
|
||||
|
||||
.skill-item-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.8125rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.skill-meta-item {
|
||||
|
||||
+62
-78
@@ -1051,106 +1051,90 @@ function renderBatchQueues() {
|
||||
renderBatchQueuesPagination();
|
||||
}
|
||||
|
||||
// 渲染批量任务队列分页控件
|
||||
// 渲染批量任务队列分页控件(参考Skills管理页面样式)
|
||||
function renderBatchQueuesPagination() {
|
||||
const paginationContainer = document.getElementById('batch-queues-pagination');
|
||||
if (!paginationContainer) return;
|
||||
|
||||
const { currentPage, pageSize, total, totalPages } = batchQueuesState;
|
||||
|
||||
// 如果没有数据,不显示分页控件
|
||||
// 即使只有一页也显示分页信息(参考Skills样式)
|
||||
if (total === 0) {
|
||||
paginationContainer.innerHTML = '';
|
||||
paginationContainer.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保分页控件可见
|
||||
paginationContainer.style.display = '';
|
||||
|
||||
// 即使只有一页,也显示分页信息(总数和每页条数选择器)
|
||||
|
||||
// 计算显示的页码范围
|
||||
let startPage = Math.max(1, currentPage - 2);
|
||||
let endPage = Math.min(totalPages, currentPage + 2);
|
||||
|
||||
// 确保显示5个页码(如果可能)
|
||||
if (endPage - startPage < 4) {
|
||||
if (startPage === 1) {
|
||||
endPage = Math.min(totalPages, startPage + 4);
|
||||
} else if (endPage === totalPages) {
|
||||
startPage = Math.max(1, endPage - 4);
|
||||
}
|
||||
}
|
||||
// 计算显示范围
|
||||
const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1;
|
||||
const end = total === 0 ? 0 : Math.min(currentPage * pageSize, total);
|
||||
|
||||
let paginationHTML = '<div class="pagination">';
|
||||
|
||||
const startItem = (currentPage - 1) * pageSize + 1;
|
||||
const endItem = Math.min(currentPage * pageSize, total);
|
||||
paginationHTML += `<div class="pagination-info">显示 ${startItem}-${endItem} / 共 ${total} 条</div>`;
|
||||
|
||||
// 每页条数选择器
|
||||
// 左侧:显示范围信息和每页数量选择器(参考Skills样式)
|
||||
paginationHTML += `
|
||||
<div class="pagination-page-size">
|
||||
<label for="batch-queues-page-size-pagination">每页:</label>
|
||||
<select id="batch-queues-page-size-pagination" onchange="changeBatchQueuesPageSize()">
|
||||
<option value="10" ${pageSize === 10 ? 'selected' : ''}>10</option>
|
||||
<option value="20" ${pageSize === 20 ? 'selected' : ''}>20</option>
|
||||
<option value="50" ${pageSize === 50 ? 'selected' : ''}>50</option>
|
||||
<option value="100" ${pageSize === 100 ? 'selected' : ''}>100</option>
|
||||
</select>
|
||||
<div class="pagination-info">
|
||||
<span>显示 ${start}-${end} / 共 ${total} 条</span>
|
||||
<label class="pagination-page-size">
|
||||
每页显示
|
||||
<select id="batch-queues-page-size-pagination" onchange="changeBatchQueuesPageSize()">
|
||||
<option value="10" ${pageSize === 10 ? 'selected' : ''}>10</option>
|
||||
<option value="20" ${pageSize === 20 ? 'selected' : ''}>20</option>
|
||||
<option value="50" ${pageSize === 50 ? 'selected' : ''}>50</option>
|
||||
<option value="100" ${pageSize === 100 ? 'selected' : ''}>100</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 只有当有多页时才显示页码导航
|
||||
if (totalPages > 1) {
|
||||
paginationHTML += '<div class="pagination-controls">';
|
||||
|
||||
// 上一页按钮
|
||||
if (currentPage > 1) {
|
||||
paginationHTML += `<button class="pagination-btn" onclick="goBatchQueuesPage(${currentPage - 1})" title="上一页">‹</button>`;
|
||||
} else {
|
||||
paginationHTML += '<button class="pagination-btn disabled" disabled>‹</button>';
|
||||
}
|
||||
|
||||
// 第一页
|
||||
if (startPage > 1) {
|
||||
paginationHTML += `<button class="pagination-btn" onclick="goBatchQueuesPage(1)">1</button>`;
|
||||
if (startPage > 2) {
|
||||
paginationHTML += '<span class="pagination-ellipsis">...</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// 页码按钮
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
if (i === currentPage) {
|
||||
paginationHTML += `<button class="pagination-btn active">${i}</button>`;
|
||||
} else {
|
||||
paginationHTML += `<button class="pagination-btn" onclick="goBatchQueuesPage(${i})">${i}</button>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 最后一页
|
||||
if (endPage < totalPages) {
|
||||
if (endPage < totalPages - 1) {
|
||||
paginationHTML += '<span class="pagination-ellipsis">...</span>';
|
||||
}
|
||||
paginationHTML += `<button class="pagination-btn" onclick="goBatchQueuesPage(${totalPages})">${totalPages}</button>`;
|
||||
}
|
||||
|
||||
// 下一页按钮
|
||||
if (currentPage < totalPages) {
|
||||
paginationHTML += `<button class="pagination-btn" onclick="goBatchQueuesPage(${currentPage + 1})" title="下一页">›</button>`;
|
||||
} else {
|
||||
paginationHTML += '<button class="pagination-btn disabled" disabled>›</button>';
|
||||
}
|
||||
|
||||
paginationHTML += '</div>';
|
||||
}
|
||||
// 右侧:分页按钮(参考Skills样式:首页、上一页、第X/Y页、下一页、末页)
|
||||
paginationHTML += `
|
||||
<div class="pagination-controls">
|
||||
<button class="btn-secondary" onclick="goBatchQueuesPage(1)" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>首页</button>
|
||||
<button class="btn-secondary" onclick="goBatchQueuesPage(${currentPage - 1})" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>上一页</button>
|
||||
<span class="pagination-page">第 ${currentPage} / ${totalPages || 1} 页</span>
|
||||
<button class="btn-secondary" onclick="goBatchQueuesPage(${currentPage + 1})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>下一页</button>
|
||||
<button class="btn-secondary" onclick="goBatchQueuesPage(${totalPages || 1})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>末页</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
paginationHTML += '</div>';
|
||||
|
||||
paginationContainer.innerHTML = paginationHTML;
|
||||
|
||||
// 确保分页组件与列表内容区域对齐(不包括滚动条)
|
||||
function alignPaginationWidth() {
|
||||
const batchQueuesList = document.getElementById('batch-queues-list');
|
||||
if (batchQueuesList && paginationContainer) {
|
||||
// 获取列表的实际内容宽度(不包括滚动条)
|
||||
const listClientWidth = batchQueuesList.clientWidth; // 可视区域宽度(不包括滚动条)
|
||||
const listScrollHeight = batchQueuesList.scrollHeight; // 内容总高度
|
||||
const listClientHeight = batchQueuesList.clientHeight; // 可视区域高度
|
||||
const hasScrollbar = listScrollHeight > listClientHeight;
|
||||
|
||||
// 如果列表有垂直滚动条,分页组件应该与列表内容区域对齐(clientWidth)
|
||||
// 如果没有滚动条,使用100%宽度
|
||||
if (hasScrollbar) {
|
||||
// 分页组件应该与列表内容区域对齐,不包括滚动条
|
||||
paginationContainer.style.width = `${listClientWidth}px`;
|
||||
} else {
|
||||
// 如果没有滚动条,使用100%宽度
|
||||
paginationContainer.style.width = '100%';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 立即执行一次
|
||||
alignPaginationWidth();
|
||||
|
||||
// 监听窗口大小变化和列表内容变化
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
alignPaginationWidth();
|
||||
});
|
||||
|
||||
const batchQueuesList = document.getElementById('batch-queues-list');
|
||||
if (batchQueuesList) {
|
||||
resizeObserver.observe(batchQueuesList);
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到指定页面
|
||||
|
||||
@@ -665,7 +665,7 @@
|
||||
</div>
|
||||
<div id="batch-queues-list" class="batch-queues-list"></div>
|
||||
<!-- 分页控件 -->
|
||||
<div id="batch-queues-pagination"></div>
|
||||
<div id="batch-queues-pagination" class="pagination-container pagination-fixed"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user