|
|
@ -18,6 +18,7 @@ |
|
|
<template #enabled="{row}"><span :style="{color:row.enabled?'var(--td-success-color)':'var(--td-error-color)'}">{{ row.enabled?'✅ 有效':'❌ 已吊销' }}</span></template> |
|
|
<template #enabled="{row}"><span :style="{color:row.enabled?'var(--td-success-color)':'var(--td-error-color)'}">{{ row.enabled?'✅ 有效':'❌ 已吊销' }}</span></template> |
|
|
<template #op="{row}"> |
|
|
<template #op="{row}"> |
|
|
<t-space :size="4"> |
|
|
<t-space :size="4"> |
|
|
|
|
|
<t-button size="small" variant="text" @click="openEditDialog(row)">编辑</t-button> |
|
|
<t-button size="small" variant="text" @click="openRoleDialog(row)">绑定角色</t-button> |
|
|
<t-button size="small" variant="text" @click="openRoleDialog(row)">绑定角色</t-button> |
|
|
<t-button v-if="row.enabled" size="small" variant="text" theme="danger" @click="doRevoke(row.id)">吊销</t-button> |
|
|
<t-button v-if="row.enabled" size="small" variant="text" theme="danger" @click="doRevoke(row.id)">吊销</t-button> |
|
|
<t-button v-else size="small" variant="text" theme="success" @click="doEnable(row.id)">启用</t-button> |
|
|
<t-button v-else size="small" variant="text" theme="success" @click="doEnable(row.id)">启用</t-button> |
|
|
@ -47,6 +48,22 @@ |
|
|
</div> |
|
|
</div> |
|
|
</t-dialog> |
|
|
</t-dialog> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 编辑弹窗 --> |
|
|
|
|
|
<t-dialog v-model:visible="showEditDialog" :header="'编辑 — '+editingKeyName" width="520px" :footer="false"> |
|
|
|
|
|
<t-form label-align="top"> |
|
|
|
|
|
<t-form-item label="名称"><t-input v-model="editForm.name" placeholder="Key 名称" /></t-form-item> |
|
|
|
|
|
<t-form-item label="描述"><t-input v-model="editForm.description" placeholder="用途说明" /></t-form-item> |
|
|
|
|
|
<t-form-item label="频率限制/分钟"><t-input-number v-model="editForm.rateLimit" :min="1" /></t-form-item> |
|
|
|
|
|
<t-form-item label="状态"> |
|
|
|
|
|
<t-switch v-model="editForm.enabled" :label="['有效', '已吊销']" /> |
|
|
|
|
|
</t-form-item> |
|
|
|
|
|
</t-form> |
|
|
|
|
|
<div class="dialog-footer"> |
|
|
|
|
|
<t-button variant="outline" @click="showEditDialog=false">取消</t-button> |
|
|
|
|
|
<t-button theme="primary" @click="doUpdate">保存</t-button> |
|
|
|
|
|
</div> |
|
|
|
|
|
</t-dialog> |
|
|
|
|
|
|
|
|
<!-- 角色绑定弹窗 --> |
|
|
<!-- 角色绑定弹窗 --> |
|
|
<t-dialog v-model:visible="showRoleDialog" :header="'绑定角色 — '+bindingKeyName" width="480px" :footer="false"> |
|
|
<t-dialog v-model:visible="showRoleDialog" :header="'绑定角色 — '+bindingKeyName" width="480px" :footer="false"> |
|
|
<t-checkbox-group v-model="selectedRoleIds"> |
|
|
<t-checkbox-group v-model="selectedRoleIds"> |
|
|
@ -62,7 +79,7 @@ |
|
|
|
|
|
|
|
|
<script setup lang="ts"> |
|
|
<script setup lang="ts"> |
|
|
import { ref, onMounted } from 'vue' |
|
|
import { ref, onMounted } from 'vue' |
|
|
import { listApiKeys, createApiKey, revokeApiKey, enableApiKey, deleteApiKey, updateApiKeyRoles } from '@/api/api-key' |
|
|
|
|
|
|
|
|
import { listApiKeys, createApiKey, revokeApiKey, enableApiKey, deleteApiKey, updateApiKeyRoles, updateApiKey } from '@/api/api-key' |
|
|
import { getAllRoles } from '@/api/role' |
|
|
import { getAllRoles } from '@/api/role' |
|
|
import { toast } from '@/utils/toast' |
|
|
import { toast } from '@/utils/toast' |
|
|
import { useConfirm } from '@/composables/useConfirm' |
|
|
import { useConfirm } from '@/composables/useConfirm' |
|
|
@ -71,6 +88,9 @@ const { confirm } = useConfirm() |
|
|
|
|
|
|
|
|
const keys=ref<any[]>([]);const loading=ref(false);const page=ref(1);const pageSize=ref(20);const total=ref(0) |
|
|
const keys=ref<any[]>([]);const loading=ref(false);const page=ref(1);const pageSize=ref(20);const total=ref(0) |
|
|
const showCreateDialog=ref(false);const showRoleDialog=ref(false) |
|
|
const showCreateDialog=ref(false);const showRoleDialog=ref(false) |
|
|
|
|
|
const showEditDialog=ref(false) |
|
|
|
|
|
const editingKeyId=ref<any>(null);const editingKeyName=ref('') |
|
|
|
|
|
const editForm=ref({name:'',description:'',rateLimit:60,enabled:true}) |
|
|
const form=ref({name:'',description:'',rateLimit:60}) |
|
|
const form=ref({name:'',description:'',rateLimit:60}) |
|
|
const createdKey=ref('') |
|
|
const createdKey=ref('') |
|
|
const allRoles=ref<any[]>([]);const selectedRoleIds=ref<string[]>([]);const bindingKeyId=ref<any>(null);const bindingKeyName=ref('') |
|
|
const allRoles=ref<any[]>([]);const selectedRoleIds=ref<string[]>([]);const bindingKeyId=ref<any>(null);const bindingKeyName=ref('') |
|
|
@ -103,6 +123,9 @@ function openRoleDialog(k:any){bindingKeyId.value=k.id;bindingKeyName.value=k.na |
|
|
|
|
|
|
|
|
async function doBindRoles(){try{const r=await updateApiKeyRoles(bindingKeyId.value,selectedRoleIds.value);if(r.success){toast('保存成功','success');showRoleDialog.value=false;loadList()}else toast(r.message||'保存失败','error')}catch(e:any){toast('保存失败:'+e.message,'error')}} |
|
|
async function doBindRoles(){try{const r=await updateApiKeyRoles(bindingKeyId.value,selectedRoleIds.value);if(r.success){toast('保存成功','success');showRoleDialog.value=false;loadList()}else toast(r.message||'保存失败','error')}catch(e:any){toast('保存失败:'+e.message,'error')}} |
|
|
|
|
|
|
|
|
|
|
|
function openEditDialog(row:any){editingKeyId.value=row.id;editingKeyName.value=row.name||'未命名';editForm.value={name:row.name||'',description:row.description||'',rateLimit:row.rateLimit??60,enabled:row.enabled};showEditDialog.value=true} |
|
|
|
|
|
async function doUpdate(){try{const r=await updateApiKey(editingKeyId.value,editForm.value);if(r.success){toast('更新成功','success');showEditDialog.value=false;loadList()}else toast(r.message||'更新失败','error')}catch(e:any){toast('更新失败:'+e.message,'error')}} |
|
|
|
|
|
|
|
|
async function copyKey(k:string){try{await navigator.clipboard.writeText(k);toast('已复制','success')}catch{toast('复制失败','error')}} |
|
|
async function copyKey(k:string){try{await navigator.clipboard.writeText(k);toast('已复制','success')}catch{toast('复制失败','error')}} |
|
|
</script> |
|
|
</script> |
|
|
<style scoped>.key-code{font-size:12px;background:#f3f4f6;padding:2px 6px;border-radius:4px;}.created-key-box{margin-top:12px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px;}.created-key-box p{font-size:12px;color:#166534;margin-bottom:6px;}.created-key{font-size:13px;word-break:break-all;display:block;margin-bottom:6px;}</style> |
|
|
<style scoped>.key-code{font-size:12px;background:#f3f4f6;padding:2px 6px;border-radius:4px;}.created-key-box{margin-top:12px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px;}.created-key-box p{font-size:12px;color:#166534;margin-bottom:6px;}.created-key{font-size:13px;word-break:break-all;display:block;margin-bottom:6px;}</style> |