208 lines
4.7 KiB
Vue
208 lines
4.7 KiB
Vue
<!--
|
||||
|
|
Copyright 2024-2026 ReplyPier
|
|||
|
|
|
|||
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|||
|
|
you may not use this file except in compliance with the License.
|
|||
|
|
You may obtain a copy of the License at
|
|||
|
|
|
|||
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|||
|
|
|
|||
|
|
Unless required by applicable law or agreed to in writing, software
|
|||
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|||
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
|
|
See the License for the specific language governing permissions and
|
|||
|
|
limitations under the License.
|
|||
|
|
-->
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { ref } from 'vue'
|
|||
|
|
import { ClipboardIcon, CheckIcon } from '@heroicons/vue/24/outline'
|
|||
|
|
import Modal from '@/components/common/Modal.vue'
|
|||
|
|
import { toast } from 'vue-sonner'
|
|||
|
|
|
|||
|
|
const props = defineProps<{
|
|||
|
|
apiKey: string
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
const emit = defineEmits<{
|
|||
|
|
close: []
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
const copied = ref(false)
|
|||
|
|
|
|||
|
|
const copyToClipboard = async () => {
|
|||
|
|
try {
|
|||
|
|
await navigator.clipboard.writeText(props.apiKey)
|
|||
|
|
copied.value = true
|
|||
|
|
toast.success('已复制!', {
|
|||
|
|
description: 'API 密钥已复制到剪贴板',
|
|||
|
|
duration: 2000
|
|||
|
|
})
|
|||
|
|
setTimeout(() => {
|
|||
|
|
copied.value = false
|
|||
|
|
}, 2000)
|
|||
|
|
} catch (err) {
|
|||
|
|
toast.error('复制失败', {
|
|||
|
|
description: '请手动复制',
|
|||
|
|
duration: 2000
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<Modal @close="emit('close')">
|
|||
|
|
<template #title>API 密钥已创建</template>
|
|||
|
|
<template #content>
|
|||
|
|
<div class="api-key-modal">
|
|||
|
|
<div class="warning-banner">
|
|||
|
|
<strong>⚠️ 请立即保存此密钥!</strong>
|
|||
|
|
<p>这是唯一一次显示该 API 密钥,之后将无法再次查看。</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="key-container">
|
|||
|
|
<label>API 密钥:</label>
|
|||
|
|
<div class="key-display">
|
|||
|
|
<code class="api-key">{{ apiKey }}</code>
|
|||
|
|
<button
|
|||
|
|
class="copy-button"
|
|||
|
|
@click="copyToClipboard"
|
|||
|
|
:title="copied ? '已复制!' : '复制到剪贴板'"
|
|||
|
|
>
|
|||
|
|
<CheckIcon v-if="copied" class="icon success" />
|
|||
|
|
<ClipboardIcon v-else class="icon" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="instructions">
|
|||
|
|
<h4>后续步骤:</h4>
|
|||
|
|
<ol>
|
|||
|
|
<li>复制该 API 密钥并妥善保管</li>
|
|||
|
|
<li>用它来验证组件 Token 生成请求</li>
|
|||
|
|
<li>将其放入 Authorization 请求头:<code>Bearer {{ apiKey.substring(0, 20) }}...</code></li>
|
|||
|
|
</ol>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="modal-actions">
|
|||
|
|
<button class="btn btn-primary" @click="emit('close')">
|
|||
|
|
我已保存密钥
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
</Modal>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.api-key-modal {
|
|||
|
|
padding: var(--space-md);
|
|||
|
|
max-width: 600px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.warning-banner {
|
|||
|
|
background: var(--warning-bg);
|
|||
|
|
border: 1px solid var(--warning-color);
|
|||
|
|
border-radius: var(--radius-md);
|
|||
|
|
padding: var(--space-md);
|
|||
|
|
margin-bottom: var(--space-lg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.warning-banner strong {
|
|||
|
|
display: block;
|
|||
|
|
color: var(--warning-color);
|
|||
|
|
margin-bottom: var(--space-xs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.warning-banner p {
|
|||
|
|
margin: 0;
|
|||
|
|
font-size: var(--text-sm);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.key-container {
|
|||
|
|
margin-bottom: var(--space-lg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.key-container label {
|
|||
|
|
display: block;
|
|||
|
|
font-weight: 500;
|
|||
|
|
margin-bottom: var(--space-sm);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.key-display {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: var(--space-sm);
|
|||
|
|
padding: var(--space-sm);
|
|||
|
|
background: var(--code-bg);
|
|||
|
|
border: 1px solid var(--border-color);
|
|||
|
|
border-radius: var(--radius-md);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.api-key {
|
|||
|
|
flex: 1;
|
|||
|
|
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|||
|
|
font-size: var(--text-sm);
|
|||
|
|
word-break: break-all;
|
|||
|
|
user-select: all;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.copy-button {
|
|||
|
|
padding: var(--space-xs);
|
|||
|
|
border: none;
|
|||
|
|
background: none;
|
|||
|
|
cursor: pointer;
|
|||
|
|
border-radius: var(--radius-sm);
|
|||
|
|
transition: background var(--transition-normal);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.copy-button:hover {
|
|||
|
|
background: var(--hover-bg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.icon {
|
|||
|
|
width: 20px;
|
|||
|
|
height: 20px;
|
|||
|
|
color: var(--text-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.icon.success {
|
|||
|
|
color: var(--success-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.instructions {
|
|||
|
|
background: var(--info-bg);
|
|||
|
|
border: 1px solid var(--info-color);
|
|||
|
|
border-radius: var(--radius-md);
|
|||
|
|
padding: var(--space-md);
|
|||
|
|
margin-bottom: var(--space-lg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.instructions h4 {
|
|||
|
|
margin-top: 0;
|
|||
|
|
margin-bottom: var(--space-sm);
|
|||
|
|
color: var(--info-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.instructions ol {
|
|||
|
|
margin: 0;
|
|||
|
|
padding-left: var(--space-lg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.instructions li {
|
|||
|
|
margin-bottom: var(--space-xs);
|
|||
|
|
font-size: var(--text-sm);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.instructions code {
|
|||
|
|
background: var(--code-bg);
|
|||
|
|
padding: 2px 4px;
|
|||
|
|
border-radius: var(--radius-sm);
|
|||
|
|
font-size: var(--text-xs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.modal-actions {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: flex-end;
|
|||
|
|
}
|
|||
|
|
</style>
|