Files
Yi.Admin/Yi.Ai.Vue3/src/components/ModelSelect/index.vue

203 lines
6.1 KiB
Vue
Raw Normal View History

2025-06-17 22:37:37 +08:00
<script setup lang="ts">
import type { GetSessionListVO } from '@/api/model/types';
2025-06-30 17:53:59 +08:00
import { Lock } from '@element-plus/icons-vue';
import { useRouter } from 'vue-router';
2025-06-17 22:37:37 +08:00
import Popover from '@/components/Popover/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
2025-06-30 16:02:39 +08:00
import { useUserStore } from '@/stores';
2025-06-17 22:37:37 +08:00
import { useModelStore } from '@/stores/modules/model';
2025-09-03 10:56:44 +08:00
import { showProductPackage } from '@/utils/product-package.ts';
import { isUserVip } from '@/utils/user';
2025-06-17 22:37:37 +08:00
2025-06-30 17:53:59 +08:00
const router = useRouter();
2025-06-30 16:02:39 +08:00
const userStore = useUserStore();
2025-06-17 22:37:37 +08:00
const modelStore = useModelStore();
2025-06-30 17:53:59 +08:00
// 检查模型是否可用
function isModelAvailable(item: GetSessionListVO) {
return isUserVip() || item.modelId?.includes('DeepSeek-R1-0528') || userStore.userInfo?.user?.userName === 'cc';
2025-06-30 17:53:59 +08:00
}
2025-06-17 22:37:37 +08:00
onMounted(async () => {
await modelStore.requestModelList();
if (modelStore.modelList.length > 0 && !modelStore.currentModelInfo?.modelId) {
2025-06-17 22:37:37 +08:00
modelStore.setCurrentModelInfo(modelStore.modelList[0]);
}
});
const currentModelName = computed(() => modelStore.currentModelInfo?.modelName);
2025-06-17 22:37:37 +08:00
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)',
2025-06-17 22:37:37 +08:00
});
const popoverRef = ref();
async function showPopover() {
await modelStore.requestModelList();
}
2025-06-30 17:53:59 +08:00
/* -------------------------------
模型点击逻辑
-------------------------------- */
2025-06-30 17:53:59 +08:00
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()
2025-06-30 21:08:32 +08:00
? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!'
: '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。'}
</p>
<p class="text-sm text-gray-500">
${isUserVip() ? '您可随时访问产品页面查看更多特权内容。' : '请点击右上角登录按钮,登录后进行购买!'}
</p>
</div>
`,
isUserVip() ? '会员状态' : '会员尊享',
2025-06-30 17:53:59 +08:00
{
2025-09-03 10:56:44 +08:00
confirmButtonText: '产品查看',
2025-06-30 17:53:59 +08:00
cancelButtonText: '关闭',
dangerouslyUseHTMLString: true,
type: 'info',
center: true,
roundButton: true,
},
).then(() => showProductPackage());
2025-06-30 17:53:59 +08:00
}
2025-06-17 22:37:37 +08:00
modelStore.setCurrentModelInfo(item);
popoverRef.value?.hide?.();
}
/* -------------------------------
模型样式规则
规则1普通灰色免费模型
规则2金色光泽VIP/付费
规则3彩色流光尊享/高级
-------------------------------- */
function getModelStyleClass(item: GetSessionListVO) {
const name = item.modelName.toLowerCase();
// 规则3彩色流光
2025-10-14 21:29:20 +08:00
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',
];
}
2025-06-17 22:37:37 +08:00
</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"
2025-06-17 22:37:37 +08:00
>
<SvgIcon name="models" size="12" />
<span class="text-sm font-medium">{{ currentModelName }}</span>
2025-06-17 22:37:37 +08:00
</div>
</template>
<div class="flex flex-col gap-1 max-h-52 overflow-y-auto p-1">
2025-06-17 22:37:37 +08:00
<div
v-for="item in popoverList"
:key="item.id"
:class="getWrapperClass(item)"
@click="handleModelClick(item)"
2025-06-17 22:37:37 +08:00
>
<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"
2025-06-17 22:37:37 +08:00
>
<Lock />
</el-icon>
2025-06-17 22:37:37 +08:00
</div>
</div>
</Popover>
</div>
</template>
<style scoped>
/* 彩色流光动画 */
@keyframes gradientFlow {
0%, 100% { background-position: 0 50%; }
50% { background-position: 100% 50%; }
2025-06-17 22:37:37 +08:00
}
/* 金色光泽动画 */
@keyframes goldShine {
0% { transform: translateX(-100%) translateY(-100%); }
100% { transform: translateX(100%) translateY(100%); }
2025-06-17 22:37:37 +08:00
}
/* 柔光 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;
2025-06-17 22:37:37 +08:00
}
</style>