Files
Yi.Admin/Yi.Ai.Vue3/src/pages/chat/api/index.vue
2026-02-06 00:41:13 +08:00

306 lines
5.9 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.
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { CopyDocument } from '@element-plus/icons-vue';
// API配置
const apiList = [
{ url: '/v1/chat/completions', name: 'OpenAi Completions' },
{ url: '/v1/messages', name: 'Claude Messages' },
{ url: '/v1/responses', name: 'OpenAi Responses' },
{ url: '/v1beta/models/{model}/streamGenerateContent', name: 'Gemini GenerateContent' },
];
const baseUrl = 'https://yxai.chat';
const currentIndex = ref(0);
let autoPlayTimer: ReturnType<typeof setInterval> | null = null;
// 当前选中的API
const currentApi = computed(() => apiList[currentIndex.value]);
// 完整URL
const fullUrl = computed(() => `${baseUrl}${currentApi.value.url}`);
// 切换API
function selectApi(index: number) {
currentIndex.value = index;
resetAutoPlay();
}
// 复制URL
async function copyUrl() {
try {
await navigator.clipboard.writeText(fullUrl.value);
ElMessage.success('复制成功');
} catch {
ElMessage.error('复制失败');
}
}
// 自动轮播
function startAutoPlay() {
autoPlayTimer = setInterval(() => {
currentIndex.value = (currentIndex.value + 1) % apiList.length;
}, 3000);
}
function resetAutoPlay() {
if (autoPlayTimer) {
clearInterval(autoPlayTimer);
}
startAutoPlay();
}
onMounted(() => {
startAutoPlay();
});
onUnmounted(() => {
if (autoPlayTimer) {
clearInterval(autoPlayTimer);
}
});
</script>
<template>
<div class="api-page">
<div class="api-container">
<!-- 标题 -->
<h1 class="page-title">AI 接口地址</h1>
<p class="page-subtitle">支持多种主流AI接口协议一键切换</p>
<!-- 主内容区 -->
<div class="content-wrapper">
<!-- 左侧URL展示 -->
<div class="url-section">
<div class="url-card">
<div class="url-label">接口地址</div>
<div class="url-display">
<span class="base-url">{{ baseUrl }}</span>
<span class="api-path">{{ currentApi.url }}</span>
</div>
<button class="copy-btn" @click="copyUrl">
<el-icon><CopyDocument /></el-icon>
复制
</button>
</div>
</div>
<!-- 右侧按钮列表 -->
<div class="api-buttons">
<button
v-for="(api, index) in apiList"
:key="index"
class="api-btn"
:class="{ active: currentIndex === index }"
@click="selectApi(index)"
>
{{ api.name }}
</button>
</div>
</div>
<!-- 进度指示器 -->
<div class="progress-dots">
<span
v-for="(_, index) in apiList"
:key="index"
class="dot"
:class="{ active: currentIndex === index }"
@click="selectApi(index)"
/>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.api-page {
min-height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.api-container {
width: 100%;
max-width: 900px;
padding: 50px;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(20px);
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.page-title {
text-align: center;
font-size: 36px;
font-weight: 700;
color: #fff;
margin: 0 0 10px 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.page-subtitle {
text-align: center;
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
margin: 0 0 40px 0;
}
.content-wrapper {
display: flex;
gap: 30px;
align-items: stretch;
}
.url-section {
flex: 1;
}
.url-card {
position: relative;
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
padding: 30px;
height: 100%;
display: flex;
flex-direction: column;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.url-label {
font-size: 14px;
color: #666;
margin-bottom: 15px;
font-weight: 500;
}
.url-display {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
font-family: 'Monaco', 'Menlo', monospace;
word-break: break-all;
}
.base-url {
font-size: 18px;
color: #409eff;
font-weight: 600;
}
.api-path {
font-size: 16px;
color: #333;
padding: 12px;
background: #f5f7fa;
border-radius: 8px;
transition: all 0.3s ease;
}
.copy-btn {
position: absolute;
top: 15px;
right: 15px;
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: #409eff;
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
&:hover {
background: #66b1ff;
transform: translateY(-2px);
}
}
.api-buttons {
display: flex;
flex-direction: column;
gap: 12px;
min-width: 220px;
}
.api-btn {
padding: 16px 24px;
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
color: #fff;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
text-align: left;
&:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateX(5px);
}
&.active {
background: #fff;
color: #667eea;
border-color: #fff;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
}
}
.progress-dots {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 30px;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: rgba(255, 255, 255, 0.7);
}
&.active {
background: #fff;
transform: scale(1.3);
}
}
@media (max-width: 768px) {
.api-container {
padding: 30px 20px;
}
.page-title {
font-size: 28px;
}
.content-wrapper {
flex-direction: column;
}
.api-buttons {
min-width: auto;
}
.api-btn {
text-align: center;
}
}
</style>