feat: API Keys图标系统和UI优化

主要功能增强:
- 实现API Key自定义图标功能,支持图片上传、裁剪和智能压缩
- 新增IconPicker组件,提供内置图标选择和图片上传功能
- 支持固定尺寸裁剪区域,可拖拽定位选择头像区域
- 智能图片压缩:PNG保留透明度,JPEG用于不透明图片

UI/UX改进:
- 优化表格布局:移除账号列,在名称下方显示账号绑定信息
- 调整行高和字体大小,提升信息密度
- 最后使用时间改为相对时间显示,悬浮显示具体时间
- 过期时间编辑改为点击文本触发,带悬浮下划线效果
- 更新默认API Key图标为蓝色渐变设计
- 修复表格悬浮偏移和横向滚动条问题
- 将"TOKEN 数量"改为"Token数"

后端支持:
- apiKeyService增加icon字段持久化
- admin路由增加图标数据处理和验证

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Edric Li
2025-09-07 11:41:22 +08:00
parent fc5c60a9b4
commit 8c9d6381f3
6 changed files with 1313 additions and 207 deletions

View File

@@ -534,7 +534,8 @@ router.post('/api-keys', authenticateAdmin, async (req, res) => {
weeklyOpusCostLimit,
tags,
activationDays, // 新增:激活后有效天数
expirationMode // 新增:过期模式
expirationMode, // 新增:过期模式
icon // 新增图标base64编码
} = req.body
// 输入验证
@@ -660,7 +661,8 @@ router.post('/api-keys', authenticateAdmin, async (req, res) => {
weeklyOpusCostLimit,
tags,
activationDays,
expirationMode
expirationMode,
icon
})
logger.success(`🔑 Admin created new API key: ${name}`)
@@ -696,7 +698,8 @@ router.post('/api-keys/batch', authenticateAdmin, async (req, res) => {
weeklyOpusCostLimit,
tags,
activationDays,
expirationMode
expirationMode,
icon
} = req.body
// 输入验证
@@ -742,7 +745,8 @@ router.post('/api-keys/batch', authenticateAdmin, async (req, res) => {
weeklyOpusCostLimit,
tags,
activationDays,
expirationMode
expirationMode,
icon
})
// 保留原始 API Key 供返回
@@ -985,7 +989,8 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
dailyCostLimit,
weeklyOpusCostLimit,
tags,
ownerId // 新增所有者ID字段
ownerId, // 新增所有者ID字段
icon // 新增图标base64编码
} = req.body
// 只允许更新指定字段
@@ -1159,6 +1164,19 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
updates.tags = tags
}
// 处理图标
if (icon !== undefined) {
// icon 可以是空字符串(清除图标)或 base64 编码的字符串
if (icon !== '' && typeof icon !== 'string') {
return res.status(400).json({ error: 'Icon must be a string' })
}
// 简单验证 base64 格式(如果不为空)
if (icon && !icon.startsWith('data:image/')) {
return res.status(400).json({ error: 'Icon must be a valid base64 image' })
}
updates.icon = icon
}
// 处理活跃/禁用状态状态, 放在过期处理后以确保后续增加禁用key功能
if (isActive !== undefined) {
if (typeof isActive !== 'boolean') {

View File

@@ -36,7 +36,8 @@ class ApiKeyService {
weeklyOpusCostLimit = 0,
tags = [],
activationDays = 0, // 新增激活后有效天数0表示不使用此功能
expirationMode = 'fixed' // 新增:过期模式 'fixed'(固定时间) 或 'activation'(首次使用后激活)
expirationMode = 'fixed', // 新增:过期模式 'fixed'(固定时间) 或 'activation'(首次使用后激活)
icon = '' // 新增图标base64编码
} = options
// 生成简单的API Key (64字符十六进制)
@@ -78,7 +79,8 @@ class ApiKeyService {
expiresAt: expirationMode === 'fixed' ? expiresAt || '' : '', // 固定模式才设置过期时间
createdBy: options.createdBy || 'admin',
userId: options.userId || '',
userUsername: options.userUsername || ''
userUsername: options.userUsername || '',
icon: icon || '' // 新增图标base64编码
}
// 保存API Key数据并建立哈希映射
@@ -410,7 +412,8 @@ class ApiKeyService {
'tags',
'userId', // 新增用户ID所有者变更
'userUsername', // 新增:用户名(所有者变更)
'createdBy' // 新增:创建者(所有者变更)
'createdBy', // 新增:创建者(所有者变更)
'icon' // 新增图标base64编码
]
const updatedData = { ...keyData }