mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-17 21:44:43 +02:00
Add files via upload
This commit is contained in:
+336
-2
@@ -3363,7 +3363,7 @@ header {
|
||||
}
|
||||
|
||||
.pagination-fixed .pagination-info .pagination-page-size select {
|
||||
padding: 4px 8px;
|
||||
padding: 4px 24px 4px 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
@@ -3371,12 +3371,25 @@ header {
|
||||
font-size: 0.8125rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
width: auto;
|
||||
min-width: 60px;
|
||||
max-width: 80px;
|
||||
font-weight: 500;
|
||||
/* 更柔和的边框 */
|
||||
border-color: rgba(233, 236, 239, 0.8);
|
||||
/* 确保四个角都是圆角 */
|
||||
border-radius: 8px !important;
|
||||
/* 确保下拉框不被拉伸 */
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
/* 确保下拉箭头正确显示 */
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 8px center;
|
||||
background-size: 12px;
|
||||
}
|
||||
|
||||
.pagination-fixed .pagination-info .pagination-page-size select:focus {
|
||||
@@ -8600,6 +8613,159 @@ header {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 角色搜索框样式 */
|
||||
.roles-search-box {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.roles-search-box input {
|
||||
width: 100%;
|
||||
padding: 10px 40px 10px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 0.9375rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.roles-search-box input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
||||
}
|
||||
|
||||
.roles-search-clear {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.roles-search-clear:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 角色卡片网格布局 */
|
||||
.roles-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 角色卡片样式 */
|
||||
.role-card {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
transition: all 0.2s;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.role-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--accent-color);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.role-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.role-card-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.role-card-icon {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.role-card-badge {
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.role-card-badge.enabled {
|
||||
background: rgba(40, 167, 69, 0.12);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.role-card-badge.disabled {
|
||||
background: rgba(220, 53, 69, 0.12);
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.role-card-description {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
flex: 1;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.role-card-tools {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary);
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.role-card-tools-label {
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.role-card-tools-value {
|
||||
color: var(--text-primary);
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.role-card-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.role-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -9251,7 +9417,7 @@ header {
|
||||
|
||||
/* Skills管理页面样式 */
|
||||
.skills-controls {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.skills-stats-bar {
|
||||
@@ -9305,6 +9471,174 @@ header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 技能搜索框样式 */
|
||||
.skills-search-box {
|
||||
position: relative;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.skills-search-box input {
|
||||
width: 100%;
|
||||
padding: 8px 40px 8px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 0.9375rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.skills-search-box input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
||||
}
|
||||
|
||||
.skills-search-clear {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.skills-search-clear:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 技能卡片网格布局 */
|
||||
.skills-grid {
|
||||
display: grid;
|
||||
/* 优化网格布局:使用 auto-fit 让卡片更好地分布,减少最小宽度以容纳更多列 */
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 0;
|
||||
/* 确保列表可以滚动,不会把分页栏推出视口 */
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
min-height: 0;
|
||||
/* 统一同一行卡片的高度 */
|
||||
align-items: stretch;
|
||||
/* 让卡片在容器中更好地分布,避免都聚集在左侧 */
|
||||
justify-items: stretch;
|
||||
}
|
||||
|
||||
/* 技能卡片样式 */
|
||||
.skill-card {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
transition: all 0.2s;
|
||||
cursor: default;
|
||||
/* 统一卡片高度:让卡片填满网格单元格高度 */
|
||||
align-items: stretch;
|
||||
height: 100%;
|
||||
/* 确保卡片宽度填满网格单元格 */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.skill-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--accent-color);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.skill-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.skill-card-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
/* 移除 flex: 1,让标题只占据实际需要的高度 */
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
/* 限制标题最多显示2行 */
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skill-card-description {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
/* 让描述区域占据剩余空间,统一卡片高度 */
|
||||
flex: 1;
|
||||
min-height: 48px;
|
||||
/* 优化文字排版 */
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
/* 限制描述的最大行数,保持卡片整洁 */
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skill-card-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: auto;
|
||||
/* 使用 margin-top: auto 将按钮推到底部,统一卡片布局 */
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 技能卡片响应式布局优化 */
|
||||
@media (min-width: 1400px) {
|
||||
.skills-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.skills-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.skills-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.skill-card {
|
||||
padding: 14px;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.skills-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.skill-item {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
+97
-53
@@ -1,6 +1,8 @@
|
||||
// 角色管理相关功能
|
||||
let currentRole = localStorage.getItem('currentRole') || '';
|
||||
let roles = [];
|
||||
let rolesSearchKeyword = ''; // 角色搜索关键词
|
||||
let rolesSearchTimeout = null; // 搜索防抖定时器
|
||||
let allRoleTools = []; // 存储所有工具列表(用于角色工具选择)
|
||||
let roleToolsPagination = {
|
||||
page: 1,
|
||||
@@ -258,6 +260,7 @@ async function refreshRoles() {
|
||||
}
|
||||
// 始终更新侧边栏角色选择列表
|
||||
renderRoleSelectionSidebar();
|
||||
showNotification('已刷新', 'success');
|
||||
}
|
||||
|
||||
// 渲染角色列表
|
||||
@@ -265,30 +268,25 @@ function renderRolesList() {
|
||||
const rolesList = document.getElementById('roles-list');
|
||||
if (!rolesList) return;
|
||||
|
||||
if (roles.length === 0) {
|
||||
rolesList.innerHTML = '<div class="empty-state">暂无角色</div>';
|
||||
// 过滤角色(根据搜索关键词)
|
||||
let filteredRoles = roles;
|
||||
if (rolesSearchKeyword) {
|
||||
const keyword = rolesSearchKeyword.toLowerCase();
|
||||
filteredRoles = roles.filter(role =>
|
||||
role.name.toLowerCase().includes(keyword) ||
|
||||
(role.description && role.description.toLowerCase().includes(keyword))
|
||||
);
|
||||
}
|
||||
|
||||
if (filteredRoles.length === 0) {
|
||||
rolesList.innerHTML = '<div class="empty-state">' +
|
||||
(rolesSearchKeyword ? '没有找到匹配的角色' : '暂无角色') +
|
||||
'</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新统计
|
||||
const stats = document.getElementById('roles-stats');
|
||||
if (stats) {
|
||||
const totalRoles = roles.length;
|
||||
const enabledRoles = roles.filter(r => r.enabled !== false).length;
|
||||
stats.innerHTML = `
|
||||
<div class="role-stat-item">
|
||||
<span class="role-stat-label">总角色数</span>
|
||||
<span class="role-stat-value">${totalRoles}</span>
|
||||
</div>
|
||||
<div class="role-stat-item">
|
||||
<span class="role-stat-label">已启用</span>
|
||||
<span class="role-stat-value">${enabledRoles}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 对角色进行排序:默认角色第一个,其他按名称排序
|
||||
const sortedRoles = sortRoles(roles);
|
||||
const sortedRoles = sortRoles(filteredRoles);
|
||||
|
||||
rolesList.innerHTML = sortedRoles.map(role => {
|
||||
// 获取角色图标,如果是Unicode转义格式则转换为emoji
|
||||
@@ -307,47 +305,93 @@ function renderRolesList() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取工具列表显示
|
||||
let toolsDisplay = '';
|
||||
let toolsCount = 0;
|
||||
if (role.name === '默认') {
|
||||
toolsDisplay = '使用所有工具';
|
||||
} else if (role.tools && role.tools.length > 0) {
|
||||
toolsCount = role.tools.length;
|
||||
// 显示前5个工具名称
|
||||
const toolNames = role.tools.slice(0, 5).map(tool => {
|
||||
// 如果是外部工具,格式为 external_mcp::tool_name,只显示工具名
|
||||
const toolName = tool.includes('::') ? tool.split('::')[1] : tool;
|
||||
return escapeHtml(toolName);
|
||||
});
|
||||
if (toolsCount <= 5) {
|
||||
toolsDisplay = toolNames.join(', ');
|
||||
} else {
|
||||
toolsDisplay = toolNames.join(', ') + ` 等 ${toolsCount} 个`;
|
||||
}
|
||||
} else if (role.mcps && role.mcps.length > 0) {
|
||||
toolsCount = role.mcps.length;
|
||||
toolsDisplay = `等 ${toolsCount} 个`;
|
||||
} else {
|
||||
toolsDisplay = '使用所有工具';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="role-item">
|
||||
<div class="role-item-content">
|
||||
<div class="role-item-header">
|
||||
<div class="role-item-name">
|
||||
<span class="role-item-icon" style="margin-right: 8px;">${roleIcon}</span>
|
||||
${escapeHtml(role.name)}
|
||||
</div>
|
||||
<div class="role-item-badge ${role.enabled !== false ? 'enabled' : 'disabled'}">
|
||||
${role.enabled !== false ? '已启用' : '已禁用'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="role-item-description">${escapeHtml(role.description || '无描述')}</div>
|
||||
<div class="role-item-details">
|
||||
<div class="role-item-detail">
|
||||
<span class="role-item-detail-label">用户提示词:</span>
|
||||
<span class="role-item-detail-value">${role.user_prompt ? (role.user_prompt.length > 100 ? escapeHtml(role.user_prompt.substring(0, 100)) + '...' : escapeHtml(role.user_prompt)) : '无'}</span>
|
||||
</div>
|
||||
<div class="role-item-detail">
|
||||
<span class="role-item-detail-label">关联的工具:</span>
|
||||
<span class="role-item-detail-value">${
|
||||
role.name === '默认'
|
||||
? '使用所有工具'
|
||||
: (role.tools && role.tools.length > 0
|
||||
? `${role.tools.length} 个工具`
|
||||
: (role.mcps && role.mcps.length > 0
|
||||
? `${role.mcps.length} 个工具(兼容旧版)`
|
||||
: '使用所有工具'))
|
||||
}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="role-card">
|
||||
<div class="role-card-header">
|
||||
<h3 class="role-card-title">
|
||||
<span class="role-card-icon">${roleIcon}</span>
|
||||
${escapeHtml(role.name)}
|
||||
</h3>
|
||||
<span class="role-card-badge ${role.enabled !== false ? 'enabled' : 'disabled'}">
|
||||
${role.enabled !== false ? '已启用' : '已禁用'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="role-item-actions">
|
||||
<button class="btn-secondary" onclick="editRole('${escapeHtml(role.name)}')">编辑</button>
|
||||
${role.name !== '默认' ? `<button class="btn-secondary btn-danger" onclick="deleteRole('${escapeHtml(role.name)}')">删除</button>` : ''}
|
||||
<div class="role-card-description">${escapeHtml(role.description || '无描述')}</div>
|
||||
<div class="role-card-tools">
|
||||
<span class="role-card-tools-label">工具:</span>
|
||||
<span class="role-card-tools-value">${toolsDisplay}</span>
|
||||
</div>
|
||||
<div class="role-card-actions">
|
||||
<button class="btn-secondary btn-small" onclick="editRole('${escapeHtml(role.name)}')">编辑</button>
|
||||
${role.name !== '默认' ? `<button class="btn-secondary btn-small btn-danger" onclick="deleteRole('${escapeHtml(role.name)}')">删除</button>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 处理角色搜索输入
|
||||
function handleRolesSearchInput() {
|
||||
clearTimeout(rolesSearchTimeout);
|
||||
rolesSearchTimeout = setTimeout(() => {
|
||||
searchRoles();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// 搜索角色
|
||||
function searchRoles() {
|
||||
const searchInput = document.getElementById('roles-search');
|
||||
if (!searchInput) return;
|
||||
|
||||
rolesSearchKeyword = searchInput.value.trim();
|
||||
const clearBtn = document.getElementById('roles-search-clear');
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = rolesSearchKeyword ? 'block' : 'none';
|
||||
}
|
||||
|
||||
renderRolesList();
|
||||
}
|
||||
|
||||
// 清除角色搜索
|
||||
function clearRolesSearch() {
|
||||
const searchInput = document.getElementById('roles-search');
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
rolesSearchKeyword = '';
|
||||
const clearBtn = document.getElementById('roles-search-clear');
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = 'none';
|
||||
}
|
||||
renderRolesList();
|
||||
}
|
||||
|
||||
// 生成工具唯一标识符(与settings.js中的getToolKey保持一致)
|
||||
function getToolKey(tool) {
|
||||
// 如果是外部工具,使用 external_mcp::tool.name 作为唯一标识符
|
||||
|
||||
@@ -280,6 +280,15 @@ function initPage(pageId) {
|
||||
break;
|
||||
case 'roles-management':
|
||||
// 初始化角色管理页面
|
||||
// 重置搜索UI(变量会在下次搜索时自动更新)
|
||||
const rolesSearchInput = document.getElementById('roles-search');
|
||||
if (rolesSearchInput) {
|
||||
rolesSearchInput.value = '';
|
||||
}
|
||||
const rolesSearchClear = document.getElementById('roles-search-clear');
|
||||
if (rolesSearchClear) {
|
||||
rolesSearchClear.style.display = 'none';
|
||||
}
|
||||
if (typeof loadRoles === 'function') {
|
||||
loadRoles().then(() => {
|
||||
if (typeof renderRolesList === 'function') {
|
||||
@@ -296,6 +305,15 @@ function initPage(pageId) {
|
||||
break;
|
||||
case 'skills-management':
|
||||
// 初始化Skills管理页面
|
||||
// 重置搜索UI(变量会在下次搜索时自动更新)
|
||||
const skillsSearchInput = document.getElementById('skills-search');
|
||||
if (skillsSearchInput) {
|
||||
skillsSearchInput.value = '';
|
||||
}
|
||||
const skillsSearchClear = document.getElementById('skills-search-clear');
|
||||
if (skillsSearchClear) {
|
||||
skillsSearchClear.style.display = 'none';
|
||||
}
|
||||
if (typeof initSkillsPagination === 'function') {
|
||||
initSkillsPagination();
|
||||
}
|
||||
|
||||
+50
-39
@@ -24,7 +24,7 @@ function getSkillsPageSize() {
|
||||
const saved = localStorage.getItem('skillsPageSize');
|
||||
if (saved) {
|
||||
const size = parseInt(saved);
|
||||
if ([10, 20, 50, 100].includes(size)) {
|
||||
if ([20, 50, 100].includes(size)) {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ function renderSkillsList() {
|
||||
|
||||
if (filteredSkills.length === 0) {
|
||||
skillsListEl.innerHTML = '<div class="empty-state">' +
|
||||
(skillsSearchKeyword ? '没有找到匹配的skills' : '暂无skills,点击"添加Skill"创建第一个skill') +
|
||||
(skillsSearchKeyword ? '没有找到匹配的skills' : '暂无skills,点击"创建Skill"创建第一个skill') +
|
||||
'</div>';
|
||||
// 搜索时隐藏分页
|
||||
const paginationContainer = document.getElementById('skills-pagination');
|
||||
@@ -105,47 +105,31 @@ function renderSkillsList() {
|
||||
}
|
||||
|
||||
skillsListEl.innerHTML = filteredSkills.map(skill => {
|
||||
const fileSize = skill.file_size || 0;
|
||||
const fileSizeStr = fileSize < 1024 ? fileSize + ' B' :
|
||||
fileSize < 1024 * 1024 ? (fileSize / 1024).toFixed(2) + ' KB' :
|
||||
(fileSize / (1024 * 1024)).toFixed(2) + ' MB';
|
||||
|
||||
return `
|
||||
<div class="skill-item">
|
||||
<div class="skill-item-header">
|
||||
<div class="skill-item-info">
|
||||
<h3 class="skill-item-name">${escapeHtml(skill.name || '')}</h3>
|
||||
${skill.description ? `<p class="skill-item-desc">${escapeHtml(skill.description)}</p>` : ''}
|
||||
</div>
|
||||
<div class="skill-item-actions">
|
||||
<button class="btn-icon" onclick="viewSkill('${escapeHtml(skill.name)}')" title="查看">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-icon" onclick="editSkill('${escapeHtml(skill.name)}')" title="编辑">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-icon btn-danger" onclick="deleteSkill('${escapeHtml(skill.name)}')" title="删除">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="skill-card">
|
||||
<div class="skill-card-header">
|
||||
<h3 class="skill-card-title">${escapeHtml(skill.name || '')}</h3>
|
||||
</div>
|
||||
<div class="skill-item-meta">
|
||||
<span class="skill-meta-item">路径: ${escapeHtml(skill.path || '')}</span>
|
||||
<span class="skill-meta-item">大小: ${fileSizeStr}</span>
|
||||
${skill.mod_time ? `<span class="skill-meta-item">修改时间: ${escapeHtml(skill.mod_time)}</span>` : ''}
|
||||
<div class="skill-card-description">${escapeHtml(skill.description || '无描述')}</div>
|
||||
<div class="skill-card-actions">
|
||||
<button class="btn-secondary btn-small" onclick="viewSkill('${escapeHtml(skill.name)}')">查看</button>
|
||||
<button class="btn-secondary btn-small" onclick="editSkill('${escapeHtml(skill.name)}')">编辑</button>
|
||||
<button class="btn-secondary btn-small btn-danger" onclick="deleteSkill('${escapeHtml(skill.name)}')">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// 确保列表容器可以滚动,分页栏可见
|
||||
// 使用 setTimeout 确保 DOM 更新完成后再检查
|
||||
setTimeout(() => {
|
||||
const paginationContainer = document.getElementById('skills-pagination');
|
||||
if (paginationContainer && !skillsSearchKeyword) {
|
||||
// 确保分页栏可见
|
||||
paginationContainer.style.display = 'block';
|
||||
paginationContainer.style.visibility = 'visible';
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// 渲染分页组件(参考MCP管理页面样式)
|
||||
@@ -177,7 +161,6 @@ function renderSkillsPagination() {
|
||||
<label class="pagination-page-size">
|
||||
每页显示
|
||||
<select id="skills-page-size-pagination" onchange="changeSkillsPageSize()">
|
||||
<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>
|
||||
@@ -205,6 +188,11 @@ function renderSkillsPagination() {
|
||||
function alignPaginationWidth() {
|
||||
const skillsList = document.getElementById('skills-list');
|
||||
if (skillsList && paginationContainer) {
|
||||
// 确保分页容器始终可见
|
||||
paginationContainer.style.display = '';
|
||||
paginationContainer.style.visibility = 'visible';
|
||||
paginationContainer.style.opacity = '1';
|
||||
|
||||
// 获取列表的实际内容宽度(不包括滚动条)
|
||||
const listClientWidth = skillsList.clientWidth; // 可视区域宽度(不包括滚动条)
|
||||
const listScrollHeight = skillsList.scrollHeight; // 内容总高度
|
||||
@@ -213,7 +201,7 @@ function renderSkillsPagination() {
|
||||
|
||||
// 如果列表有垂直滚动条,分页组件应该与列表内容区域对齐(clientWidth)
|
||||
// 如果没有滚动条,使用100%宽度
|
||||
if (hasScrollbar) {
|
||||
if (hasScrollbar && listClientWidth > 0) {
|
||||
// 分页组件应该与列表内容区域对齐,不包括滚动条
|
||||
paginationContainer.style.width = `${listClientWidth}px`;
|
||||
} else {
|
||||
@@ -235,6 +223,10 @@ function renderSkillsPagination() {
|
||||
if (skillsList) {
|
||||
resizeObserver.observe(skillsList);
|
||||
}
|
||||
|
||||
// 确保分页容器始终可见(防止被隐藏)
|
||||
paginationContainer.style.display = 'block';
|
||||
paginationContainer.style.visibility = 'visible';
|
||||
}
|
||||
|
||||
// 改变每页显示数量
|
||||
@@ -288,6 +280,10 @@ async function searchSkills() {
|
||||
if (!searchInput) return;
|
||||
|
||||
skillsSearchKeyword = searchInput.value.trim();
|
||||
const clearBtn = document.getElementById('skills-search-clear');
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = skillsSearchKeyword ? 'block' : 'none';
|
||||
}
|
||||
|
||||
if (skillsSearchKeyword) {
|
||||
// 有搜索关键词时,使用后端搜索API(加载所有匹配结果,不分页)
|
||||
@@ -317,6 +313,21 @@ async function searchSkills() {
|
||||
}
|
||||
}
|
||||
|
||||
// 清除skills搜索
|
||||
function clearSkillsSearch() {
|
||||
const searchInput = document.getElementById('skills-search');
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
skillsSearchKeyword = '';
|
||||
const clearBtn = document.getElementById('skills-search-clear');
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = 'none';
|
||||
}
|
||||
// 恢复分页加载
|
||||
loadSkills(1, skillsPagination.pageSize);
|
||||
}
|
||||
|
||||
// 刷新skills
|
||||
async function refreshSkills() {
|
||||
await loadSkills(skillsPagination.currentPage, skillsPagination.pageSize);
|
||||
|
||||
+20
-22
@@ -676,23 +676,22 @@
|
||||
<h2>角色管理</h2>
|
||||
<div class="page-header-actions">
|
||||
<button class="btn-secondary" onclick="refreshRoles()">刷新</button>
|
||||
<button class="btn-primary" onclick="showAddRoleModal()">添加角色</button>
|
||||
<button class="btn-primary" onclick="showAddRoleModal()">创建角色</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<div class="roles-controls">
|
||||
<div class="roles-stats-bar" id="roles-stats">
|
||||
<div class="role-stat-item">
|
||||
<span class="role-stat-label">总角色数</span>
|
||||
<span class="role-stat-value">-</span>
|
||||
</div>
|
||||
<div class="role-stat-item">
|
||||
<span class="role-stat-label">已启用</span>
|
||||
<span class="role-stat-value">-</span>
|
||||
</div>
|
||||
<div class="roles-search-box">
|
||||
<input type="text" id="roles-search" placeholder="搜索角色..." oninput="handleRolesSearchInput()" onkeydown="if(event.key==='Enter') searchRoles()" />
|
||||
<button class="roles-search-clear" id="roles-search-clear" onclick="clearRolesSearch()" style="display: none;" title="清除搜索">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="roles-list" class="roles-list">
|
||||
<div id="roles-list" class="roles-grid">
|
||||
<div class="loading-spinner">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -737,23 +736,22 @@
|
||||
<h2>Skills管理</h2>
|
||||
<div class="page-header-actions">
|
||||
<button class="btn-secondary" onclick="refreshSkills()">刷新</button>
|
||||
<button class="btn-primary" onclick="showAddSkillModal()">添加Skill</button>
|
||||
<button class="btn-primary" onclick="showAddSkillModal()">创建Skill</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-content page-content-with-pagination">
|
||||
<div class="skills-controls">
|
||||
<div class="skills-stats-bar" id="skills-management-stats">
|
||||
<div class="skill-stat-item">
|
||||
<span class="skill-stat-label">总Skills数</span>
|
||||
<span class="skill-stat-value">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="skills-filters">
|
||||
<input type="text" id="skills-search" placeholder="搜索skill..." oninput="handleSkillsSearchInput()" onkeydown="if(event.key==='Enter') searchSkills()" />
|
||||
<button class="btn-search" onclick="searchSkills()" title="搜索">🔍</button>
|
||||
<div class="skills-search-box">
|
||||
<input type="text" id="skills-search" placeholder="搜索Skills..." oninput="handleSkillsSearchInput()" onkeydown="if(event.key==='Enter') searchSkills()" />
|
||||
<button class="skills-search-clear" id="skills-search-clear" onclick="clearSkillsSearch()" style="display: none;" title="清除搜索">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="skills-list" class="skills-list skills-list-with-pagination">
|
||||
<div id="skills-list" class="skills-grid">
|
||||
<div class="loading-spinner">加载中...</div>
|
||||
</div>
|
||||
<div id="skills-pagination" class="pagination-container pagination-fixed"></div>
|
||||
|
||||
Reference in New Issue
Block a user