Files
Yi.Framework/Yi.Ai.Vue3/src/components/ModelSelect/index.vue
2025-10-15 00:05:10 +08:00

210 lines
6.4 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 type { GetSessionListVO } from '@/api/model/types';
import { Lock } from '@element-plus/icons-vue';
import { useRouter } from 'vue-router';
import Popover from '@/components/Popover/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
import { useUserStore } from '@/stores';
import { useModelStore } from '@/stores/modules/model';
import { showProductPackage } from '@/utils/product-package.ts';
import { isUserVip } from '@/utils/user';
const router = useRouter();
const userStore = useUserStore();
const modelStore = useModelStore();
// 检查模型是否可用
function isModelAvailable(item: GetSessionListVO) {
return isUserVip() || item.modelId?.includes('DeepSeek-R1-0528') || userStore.userInfo?.user?.userName === 'cc';
}
onMounted(async () => {
await modelStore.requestModelList();
if (modelStore.modelList.length > 0 && !modelStore.currentModelInfo?.modelId) {
modelStore.setCurrentModelInfo(modelStore.modelList[0]);
}
});
const currentModelName = computed(() => modelStore.currentModelInfo?.modelName);
const popoverList = computed(() => modelStore.modelList);
const popoverStyle = ref({
width: '200px',
padding: '4px',
height: 'fit-content',
background: 'var(--el-bg-color, #fff)',
border: '1px solid var(--el-border-color-light)',
borderRadius: '8px',
boxShadow: '0 2px 12px rgba(0,0,0,0.1)',
});
const popoverRef = ref();
async function showPopover() {
await modelStore.requestModelList();
}
/* -------------------------------
模型点击逻辑
-------------------------------- */
function handleModelClick(item: GetSessionListVO) {
if (!isModelAvailable(item)) {
ElMessageBox.confirm(
`
<div class="text-center leading-relaxed">
<h3 class="text-lg font-bold mb-3">${isUserVip() ? 'YiXinAI-VIP 会员' : '成为 YiXinAI-VIP'}</h3>
<p class="mb-2">
${isUserVip()
? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!'
: '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。'}
</p>
<p class="text-sm text-gray-500">
${isUserVip() ? '您可随时访问产品页面查看更多特权内容。' : '请点击右上角登录按钮,登录后进行购买!'}
</p>
</div>
`,
isUserVip() ? '会员状态' : '会员尊享',
{
confirmButtonText: '产品查看',
cancelButtonText: '关闭',
dangerouslyUseHTMLString: true,
type: 'info',
center: true,
roundButton: true,
},
).then(() => showProductPackage());
}
modelStore.setCurrentModelInfo(item);
popoverRef.value?.hide?.();
}
/* -------------------------------
模型样式规则
规则1普通灰色免费模型
规则2金色光泽VIP/付费)
规则3彩色流光尊享/高级)
-------------------------------- */
function getModelStyleClass(item: GetSessionListVO) {
const name = item.modelName.toLowerCase();
// 规则3彩色流光
if (name.includes('claude-sonnet-4-5-20250929')) {
return `
text-transparent bg-clip-text
bg-[linear-gradient(45deg,#ff0000,#ff8000,#ffff00,#00ff00,#00ffff,#0000ff,#8000ff,#ff0080)]
bg-[length:400%_400%] animate-gradientFlow
`;
}
// 规则2普通灰
if (name.includes('deepseek-r1')) {
return 'text-gray-700';
}
// 规则1金色光泽
return `
text-[#B38728] font-semibold relative overflow-hidden
before:content-[''] before:absolute before:-inset-2 before:-z-10
before:animate-goldShine
`;
// 金色背景
// before:bg-[linear-gradient(135deg,#BF953F,#FCF6BA,#B38728,#FBF5B7,#AA771C)]
}
/* -------------------------------
外层卡片样式(选中态 + hover 动效)
-------------------------------- */
function getWrapperClass(item: GetSessionListVO) {
const isSelected = item.modelName === currentModelName.value;
const available = isModelAvailable(item);
return [
'p-2 rounded-md text-sm transition-all duration-300 relative select-none flex items-center justify-between',
available
? 'hover:scale-[1.03] hover:shadow-[0_0_8px_rgba(0,0,0,0.1)] hover:border-gray-300'
: 'opacity-60 cursor-not-allowed',
isSelected
? 'border-2 border-blue-700 shadow-[0_0_10px_rgba(29,78,216,0.6)]'
: 'border border-transparent cursor-pointer',
];
}
</script>
<template>
<div class="model-select">
<Popover
ref="popoverRef"
placement="top-start"
:offset="[4, 0]"
:popover-style="popoverStyle"
trigger="clickTarget"
@show="showPopover"
>
<template #trigger>
<div
class="flex items-center gap-1 p-2 rounded-md border border-blue-500 text-blue-600 cursor-pointer select-none model-select-box "
>
<SvgIcon name="models" size="12" />
<span class="text-sm font-medium">{{ currentModelName }}</span>
</div>
</template>
<div class="flex flex-col gap-1 max-h-100 overflow-y-auto p-1">
<div
v-for="item in popoverList"
:key="item.id"
:class="getWrapperClass(item)"
@click="handleModelClick(item)"
>
<span :class="getModelStyleClass(item)">
{{ item.modelName }}
</span>
<el-icon
v-if="!isModelAvailable(item)"
class="absolute right-1 top-1/2 -translate-y-1/2 text-gray-400"
>
<Lock />
</el-icon>
</div>
</div>
</Popover>
</div>
</template>
<style scoped>
.model-select-box {
color: var(--el-color-primary, #409eff);
background: var(--el-color-primary-light-9, rgb(235.9 245.3 255));
border: 1px solid var(--el-color-primary, #409eff);
border-radius: 10px;
}
/* 彩色流光动画 */
@keyframes gradientFlow {
0%, 100% { background-position: 0 50%; }
50% { background-position: 100% 50%; }
}
/* 金色光泽动画 */
@keyframes goldShine {
0% { transform: translateX(-100%) translateY(-100%); }
100% { transform: translateX(100%) translateY(100%); }
}
/* 柔光 hover 动效 */
@keyframes glowPulse {
0%, 100% { box-shadow: 0 0 6px rgba(37,99,235,0.2); }
50% { box-shadow: 0 0 10px rgba(37,99,235,0.5); }
}
.animate-gradientFlow {
animation: gradientFlow 3s ease infinite;
}
.animate-goldShine {
animation: goldShine 4s linear infinite;
}
.animate-glowPulse {
animation: glowPulse 2s ease-in-out infinite;
}
</style>