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