Add files via upload

This commit is contained in:
公明
2026-01-17 00:07:26 +08:00
committed by GitHub
parent 7a1fc8313c
commit d50fa3d633
5 changed files with 521 additions and 116 deletions
+336 -2
View File
@@ -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
View File
@@ -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 作为唯一标识符
+18
View File
@@ -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
View File
@@ -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
View File
@@ -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>