Files
replypier/frontend/src/components/widget-app/ApiKeyModal.vue
T
WangShen007 0cc6299d1c feat(widget): Widget 配置、loader 与右下角悬浮入口
生成可嵌入站点的 loader 与悬浮按钮,支持展开收起和移动端;附独立宿主测试页。
2026-09-25 20:50:00 +08:00

209 lines
4.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
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>