Merge pull request #322 from f3n9/dev-um-8

用户API Key管理相关优化
This commit is contained in:
Wesley Liddick
2025-09-03 15:24:00 +08:00
committed by GitHub
10 changed files with 436 additions and 18 deletions

View File

@@ -41,6 +41,26 @@
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">名称不可修改</p>
</div>
<!-- 所有者选择 -->
<div>
<label
class="mb-1.5 block text-xs font-semibold text-gray-700 dark:text-gray-300 sm:mb-3 sm:text-sm"
>所有者</label
>
<select
v-model="form.ownerId"
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
>
<option v-for="user in availableUsers" :key="user.id" :value="user.id">
{{ user.displayName }} ({{ user.username }})
<span v-if="user.role === 'admin'" class="text-gray-500">- 管理员</span>
</option>
</select>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">
分配此 API Key 给指定用户或管理员管理员分配时不受用户 API Key 数量限制
</p>
</div>
<!-- 标签 -->
<div>
<label
@@ -666,6 +686,9 @@ const localAccounts = ref({
// 支持的客户端列表
const supportedClients = ref([])
// 可用用户列表
const availableUsers = ref([])
// 标签相关
const newTag = ref('')
const availableTags = ref([])
@@ -696,7 +719,8 @@ const form = reactive({
enableClientRestriction: false,
allowedClients: [],
tags: [],
isActive: true
isActive: true,
ownerId: '' // 新增所有者ID
})
// 添加限制的模型
@@ -856,6 +880,11 @@ const updateApiKey = async () => {
// 活跃状态
data.isActive = form.isActive
// 所有者
if (form.ownerId !== undefined) {
data.ownerId = form.ownerId
}
const result = await apiClient.put(`/admin/api-keys/${props.apiKey.id}`, data)
if (result.success) {
@@ -947,11 +976,45 @@ const refreshAccounts = async () => {
}
}
// 加载用户列表
const loadUsers = async () => {
try {
const response = await apiClient.get('/admin/users')
if (response.success) {
availableUsers.value = response.data || []
}
} catch (error) {
console.error('Failed to load users:', error)
availableUsers.value = [
{
id: 'admin',
username: 'admin',
displayName: 'Admin',
email: '',
role: 'admin'
}
]
}
}
// 初始化表单数据
onMounted(async () => {
// 加载支持的客户端和已存在的标签
supportedClients.value = await clientsStore.loadSupportedClients()
availableTags.value = await apiKeysStore.fetchTags()
try {
// 并行加载所有需要的数据
const [clients, tags] = await Promise.all([
clientsStore.loadSupportedClients(),
apiKeysStore.fetchTags(),
loadUsers()
])
supportedClients.value = clients || []
availableTags.value = tags || []
} catch (error) {
console.error('Error loading initial data:', error)
// Fallback to empty arrays if loading fails
supportedClients.value = []
availableTags.value = []
}
// 初始化账号数据
if (props.accounts) {
@@ -1001,6 +1064,9 @@ onMounted(async () => {
form.enableClientRestriction = props.apiKey.enableClientRestriction || false
// 初始化活跃状态,默认为 true
form.isActive = props.apiKey.isActive !== undefined ? props.apiKey.isActive : true
// 初始化所有者
form.ownerId = props.apiKey.userId || 'admin'
})
</script>

View File

@@ -159,7 +159,11 @@
</button>
<button
v-if="!(apiKey.isDeleted === 'true' || apiKey.deletedAt) && apiKey.isActive"
v-if="
!(apiKey.isDeleted === 'true' || apiKey.deletedAt) &&
apiKey.isActive &&
allowUserDeleteApiKeys
"
class="inline-flex items-center rounded border border-transparent p-1 text-red-400 hover:text-red-600"
title="Delete API Key"
@click="deleteApiKey(apiKey)"
@@ -255,6 +259,7 @@ const userStore = useUserStore()
const loading = ref(true)
const apiKeys = ref([])
const maxApiKeys = computed(() => userStore.config?.maxApiKeysPerUser || 5)
const allowUserDeleteApiKeys = computed(() => userStore.config?.allowUserDeleteApiKeys === true)
const showCreateModal = ref(false)
const showViewModal = ref(false)

View File

@@ -105,7 +105,7 @@
<input
v-model="searchKeyword"
class="w-full rounded-lg border border-gray-200 bg-white px-3 py-2 pl-9 text-sm text-gray-700 placeholder-gray-400 shadow-sm transition-all duration-200 hover:border-gray-300 focus:border-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:placeholder-gray-500 dark:hover:border-gray-500"
placeholder="搜索名称..."
placeholder="搜索名称或所有者..."
type="text"
@input="currentPage = 1"
/>
@@ -404,6 +404,11 @@
使用共享池
</div>
</div>
<!-- 显示所有者信息 -->
<div v-if="key.ownerDisplayName" class="mt-1 text-xs text-red-600">
<i class="fas fa-user mr-1" />
{{ key.ownerDisplayName }}
</div>
</div>
</div>
</td>
@@ -1025,6 +1030,11 @@
<i class="fas fa-share-alt mr-1" />
使用共享池
</div>
<!-- 显示所有者信息 -->
<div v-if="key.ownerDisplayName" class="text-xs text-red-600">
<i class="fas fa-user mr-1" />
{{ key.ownerDisplayName }}
</div>
</div>
<!-- 统计信息 -->
@@ -1647,12 +1657,18 @@ const sortedApiKeys = computed(() => {
)
}
// 然后进行名称搜索
// 然后进行名称搜索搜索API Key名称和所有者名称
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase().trim()
filteredKeys = filteredKeys.filter(
(key) => key.name && key.name.toLowerCase().includes(keyword)
)
filteredKeys = filteredKeys.filter((key) => {
// 搜索API Key名称
const nameMatch = key.name && key.name.toLowerCase().includes(keyword)
// 搜索所有者名称
const ownerMatch =
key.ownerDisplayName && key.ownerDisplayName.toLowerCase().includes(keyword)
// 如果API Key名称或所有者名称匹配则包含该条目
return nameMatch || ownerMatch
})
}
// 如果没有排序字段,返回筛选后的结果