fix: 修复PR #814 遗留bug

This commit is contained in:
shaw
2025-12-26 15:47:33 +08:00
parent bbaa850809
commit 85dd3a2cc6
4 changed files with 174 additions and 37 deletions

View File

@@ -21,11 +21,28 @@ function validatePermissions(permissions) {
if (permissions === undefined || permissions === null || permissions === '') { if (permissions === undefined || permissions === null || permissions === '') {
return null return null
} }
// 兼容旧格式字符串 // 兼容字符串格式
if (typeof permissions === 'string') { if (typeof permissions === 'string') {
if (permissions === 'all' || VALID_PERMISSIONS.includes(permissions)) { // 旧格式 'all' 表示全部服务
if (permissions === 'all') {
return null return null
} }
// 单个有效权限
if (VALID_PERMISSIONS.includes(permissions)) {
return null
}
// 尝试解析 JSON 数组字符串(如 "[]" 或 '["claude","gemini"]'
if (permissions.startsWith('[')) {
try {
const parsed = JSON.parse(permissions)
if (Array.isArray(parsed)) {
// 递归验证解析后的数组
return validatePermissions(parsed)
}
} catch (e) {
// 解析失败,返回错误
}
}
return `Invalid permissions value. Must be an array of: ${VALID_PERMISSIONS.join(', ')}` return `Invalid permissions value. Must be an array of: ${VALID_PERMISSIONS.join(', ')}`
} }
// 新格式数组 // 新格式数组

View File

@@ -750,8 +750,13 @@ class ApiKeyService {
for (const [field, value] of Object.entries(updates)) { for (const [field, value] of Object.entries(updates)) {
if (allowedUpdates.includes(field)) { if (allowedUpdates.includes(field)) {
if (field === 'restrictedModels' || field === 'allowedClients' || field === 'tags') { if (
// 特殊处理数组字段 field === 'restrictedModels' ||
field === 'allowedClients' ||
field === 'tags' ||
field === 'permissions'
) {
// 特殊处理数组字段,使用 JSON.stringify
updatedData[field] = JSON.stringify(value || []) updatedData[field] = JSON.stringify(value || [])
} else if ( } else if (
field === 'enableModelRestriction' || field === 'enableModelRestriction' ||

View File

@@ -290,31 +290,79 @@
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300" <label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>服务权限</label >服务权限</label
> >
<div class="flex flex-wrap gap-4"> <div class="space-y-3">
<label class="flex cursor-pointer items-center"> <!-- 权限操作模式选择 -->
<input v-model="form.permissions" class="mr-2" type="radio" value="" /> <div class="flex flex-wrap gap-4">
<span class="text-sm text-gray-700">不修改</span> <label class="flex cursor-pointer items-center">
</label> <input
<label class="flex cursor-pointer items-center"> v-model="permissionsOperation"
<input v-model="form.permissions" class="mr-2" type="radio" value="all" /> class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
<span class="text-sm text-gray-700">全部服务</span> type="radio"
</label> value="none"
<label class="flex cursor-pointer items-center"> />
<input v-model="form.permissions" class="mr-2" type="radio" value="claude" /> <span class="text-sm text-gray-700 dark:text-gray-300">不修改</span>
<span class="text-sm text-gray-700"> Claude</span> </label>
</label> <label class="flex cursor-pointer items-center">
<label class="flex cursor-pointer items-center"> <input
<input v-model="form.permissions" class="mr-2" type="radio" value="gemini" /> v-model="permissionsOperation"
<span class="text-sm text-gray-700"> Gemini</span> class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
</label> type="radio"
<label class="flex cursor-pointer items-center"> value="all"
<input v-model="form.permissions" class="mr-2" type="radio" value="openai" /> />
<span class="text-sm text-gray-700"> OpenAI</span> <span class="text-sm text-gray-700 dark:text-gray-300">全部服务</span>
</label> </label>
<label class="flex cursor-pointer items-center"> <label class="flex cursor-pointer items-center">
<input v-model="form.permissions" class="mr-2" type="radio" value="droid" /> <input
<span class="text-sm text-gray-700"> Droid</span> v-model="permissionsOperation"
</label> class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="radio"
value="custom"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">自定义选择</span>
</label>
</div>
<!-- 自定义选择时显示复选框 -->
<div v-if="permissionsOperation === 'custom'" class="flex flex-wrap gap-4 pl-6">
<label class="flex cursor-pointer items-center">
<input
v-model="form.permissions"
class="mr-2 rounded text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="checkbox"
value="claude"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Claude</span>
</label>
<label class="flex cursor-pointer items-center">
<input
v-model="form.permissions"
class="mr-2 rounded text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="checkbox"
value="gemini"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Gemini</span>
</label>
<label class="flex cursor-pointer items-center">
<input
v-model="form.permissions"
class="mr-2 rounded text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="checkbox"
value="openai"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">OpenAI</span>
</label>
<label class="flex cursor-pointer items-center">
<input
v-model="form.permissions"
class="mr-2 rounded text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="checkbox"
value="droid"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Droid</span>
</label>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">
选择"全部服务"表示允许访问所有服务"自定义选择"可以选择多个特定服务
</p>
</div> </div>
</div> </div>
@@ -494,6 +542,7 @@ const localAccounts = ref({
const newTag = ref('') const newTag = ref('')
const availableTags = ref([]) const availableTags = ref([])
const tagOperation = ref('none') // 'replace', 'add', 'remove', 'none' const tagOperation = ref('none') // 'replace', 'add', 'remove', 'none'
const permissionsOperation = ref('none') // 'none', 'all', 'custom'
const selectedCount = computed(() => props.selectedKeys.length) const selectedCount = computed(() => props.selectedKeys.length)
@@ -511,7 +560,7 @@ const form = reactive({
dailyCostLimit: '', dailyCostLimit: '',
totalCostLimit: '', totalCostLimit: '',
weeklyOpusCostLimit: '', // 新增Opus周费用限制 weeklyOpusCostLimit: '', // 新增Opus周费用限制
permissions: '', // 空字符串表示不修改 permissions: [], // 数组格式,用于多选
claudeAccountId: '', claudeAccountId: '',
geminiAccountId: '', geminiAccountId: '',
openaiAccountId: '', openaiAccountId: '',
@@ -547,9 +596,15 @@ const bedrockAccountSelectorValue = createAccountSelectorModel('bedrockAccountId
const droidAccountSelectorValue = createAccountSelectorModel('droidAccountId') const droidAccountSelectorValue = createAccountSelectorModel('droidAccountId')
const isServiceSelectable = (service) => { const isServiceSelectable = (service) => {
if (!form.permissions) return true // 不修改权限时,所有服务都可选
if (form.permissions === 'all') return true if (permissionsOperation.value === 'none') return true
return form.permissions === service // 全部服务时,所有服务都可选
if (permissionsOperation.value === 'all') return true
// 自定义选择时,根据选择的权限判断
if (permissionsOperation.value === 'custom') {
return form.permissions.length === 0 || form.permissions.includes(service)
}
return true
} }
// 标签管理方法 // 标签管理方法
@@ -737,8 +792,14 @@ const batchUpdateApiKeys = async () => {
} }
// 权限设置 // 权限设置
if (form.permissions !== '') { if (permissionsOperation.value !== 'none') {
updates.permissions = form.permissions if (permissionsOperation.value === 'all') {
// 全部服务:发送空数组
updates.permissions = []
} else if (permissionsOperation.value === 'custom') {
// 自定义选择:发送选中的权限数组
updates.permissions = form.permissions
}
} }
// 账户绑定 // 账户绑定

View File

@@ -1235,11 +1235,65 @@ onMounted(async () => {
// 处理权限数据,兼容旧格式(字符串)和新格式(数组) // 处理权限数据,兼容旧格式(字符串)和新格式(数组)
const perms = props.apiKey.permissions const perms = props.apiKey.permissions
if (Array.isArray(perms)) { if (Array.isArray(perms)) {
form.permissions = perms // 过滤掉损坏的数据(如 "claude,droid" 这种逗号分隔的字符串)
const validPerms = ['claude', 'gemini', 'openai', 'droid']
const cleaned = []
for (const p of perms) {
if (validPerms.includes(p)) {
cleaned.push(p)
} else if (typeof p === 'string' && p.includes(',')) {
// 处理逗号分隔的旧格式
const parts = p.split(',').map((s) => s.trim())
for (const part of parts) {
if (validPerms.includes(part) && !cleaned.includes(part)) {
cleaned.push(part)
}
}
}
}
form.permissions = cleaned
} else if (perms === 'all' || !perms) { } else if (perms === 'all' || !perms) {
form.permissions = [] form.permissions = []
} else if (typeof perms === 'string') { } else if (typeof perms === 'string') {
form.permissions = [perms] // 尝试解析 JSON 数组字符串(如 "[]" 或 '["claude","gemini"]'
if (perms.startsWith('[')) {
try {
const parsed = JSON.parse(perms)
if (Array.isArray(parsed)) {
// 递归处理解析后的数组
const validPerms = ['claude', 'gemini', 'openai', 'droid']
const cleaned = []
for (const p of parsed) {
if (validPerms.includes(p)) {
cleaned.push(p)
} else if (typeof p === 'string' && p.includes(',')) {
const parts = p.split(',').map((s) => s.trim())
for (const part of parts) {
if (validPerms.includes(part) && !cleaned.includes(part)) {
cleaned.push(part)
}
}
}
}
form.permissions = cleaned
} else {
form.permissions = []
}
} catch (e) {
// 解析失败,尝试按逗号分隔处理
const validPerms = ['claude', 'gemini', 'openai', 'droid']
const parts = perms.split(',').map((s) => s.trim())
form.permissions = parts.filter((p) => validPerms.includes(p))
}
} else if (perms.includes(',')) {
// 逗号分隔的旧格式
const validPerms = ['claude', 'gemini', 'openai', 'droid']
const parts = perms.split(',').map((s) => s.trim())
form.permissions = parts.filter((p) => validPerms.includes(p))
} else {
const validPerms = ['claude', 'gemini', 'openai', 'droid']
form.permissions = validPerms.includes(perms) ? [perms] : []
}
} else { } else {
form.permissions = [] form.permissions = []
} }