2025-10-16 01:20:11 +08:00
|
|
|
|
<!-- 切换模型 -->
|
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';
|
2025-10-16 01:20:11 +08:00
|
|
|
|
|
2025-06-17 22:37:37 +08:00
|
|
|
|
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';
|
2025-08-04 23:11:42 +08:00
|
|
|
|
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-10-16 01:20:11 +08:00
|
|
|
|
|
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) {
|
2025-08-04 23:11:42 +08:00
|
|
|
|
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();
|
2025-10-16 01:20:11 +08:00
|
|
|
|
// 设置默认模型
|
|
|
|
|
|
if (
|
|
|
|
|
|
modelStore.modelList.length > 0
|
|
|
|
|
|
&& (!modelStore.currentModelInfo || !modelStore.currentModelInfo.modelId)
|
|
|
|
|
|
) {
|
2025-06-17 22:37:37 +08:00
|
|
|
|
modelStore.setCurrentModelInfo(modelStore.modelList[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-16 01:20:11 +08:00
|
|
|
|
const currentModelName = computed(
|
|
|
|
|
|
() => modelStore.currentModelInfo && modelStore.currentModelInfo.modelName,
|
|
|
|
|
|
);
|
2025-06-17 22:37:37 +08:00
|
|
|
|
const popoverList = computed(() => modelStore.modelList);
|
|
|
|
|
|
|
2025-10-16 01:20:11 +08:00
|
|
|
|
/* 弹出面板 开始 */
|
2025-06-17 22:37:37 +08:00
|
|
|
|
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',
|
2025-10-16 01:20:11 +08:00
|
|
|
|
boxShadow: '0 2px 12px 0 rgba(0, 0, 0, 0.1)',
|
2025-06-17 22:37:37 +08:00
|
|
|
|
});
|
|
|
|
|
|
const popoverRef = ref();
|
|
|
|
|
|
|
2025-10-16 01:20:11 +08:00
|
|
|
|
// 显示
|
2025-06-17 22:37:37 +08:00
|
|
|
|
async function showPopover() {
|
2025-10-16 01:20:11 +08:00
|
|
|
|
// 获取最新的模型列表
|
2025-06-17 22:37:37 +08:00
|
|
|
|
await modelStore.requestModelList();
|
|
|
|
|
|
}
|
2025-06-30 17:53:59 +08:00
|
|
|
|
|
2025-10-16 01:20:11 +08:00
|
|
|
|
// 点击
|
|
|
|
|
|
// 处理模型点击
|
2025-06-30 17:53:59 +08:00
|
|
|
|
function handleModelClick(item: GetSessionListVO) {
|
|
|
|
|
|
if (!isModelAvailable(item)) {
|
|
|
|
|
|
ElMessageBox.confirm(
|
|
|
|
|
|
`
|
2025-10-16 01:20:11 +08:00
|
|
|
|
<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 模型与专属服务。感谢支持!'
|
2025-10-16 01:20:11 +08:00
|
|
|
|
: '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。'
|
|
|
|
|
|
}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
${
|
|
|
|
|
|
isUserVip()
|
|
|
|
|
|
? '<p class="text-sm text-gray-500">您可随时访问产品页面查看更多特权内容。</p>'
|
|
|
|
|
|
: '<p class="text-sm text-gray-500">请点击右上角登录按钮,登录后进行购买!</p>'
|
|
|
|
|
|
}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`,
|
2025-08-04 23:11:42 +08:00
|
|
|
|
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,
|
|
|
|
|
|
},
|
2025-10-16 01:20:11 +08:00
|
|
|
|
)
|
|
|
|
|
|
.then(() => {
|
|
|
|
|
|
showProductPackage();
|
|
|
|
|
|
|
|
|
|
|
|
// router.push({
|
|
|
|
|
|
// name: 'products', // 使用命名路由
|
|
|
|
|
|
// query: { from: isUserVip() ? 'vip' : 'user' }, // 可选:添加来源标识
|
|
|
|
|
|
// });
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
// 点击右上角关闭或“关闭”按钮,不执行任何操作
|
|
|
|
|
|
});
|
2025-06-30 17:53:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-17 22:37:37 +08:00
|
|
|
|
modelStore.setCurrentModelInfo(item);
|
|
|
|
|
|
popoverRef.value?.hide?.();
|
|
|
|
|
|
}
|
2025-10-12 18:30:34 +08:00
|
|
|
|
|
|
|
|
|
|
/* -------------------------------
|
|
|
|
|
|
模型样式规则
|
|
|
|
|
|
规则1:普通灰色(免费模型)
|
|
|
|
|
|
规则2:金色光泽(VIP/付费)
|
|
|
|
|
|
规则3:彩色流光(尊享/高级)
|
|
|
|
|
|
-------------------------------- */
|
2025-10-16 01:20:11 +08:00
|
|
|
|
function getModelStyleClass(modelName: any) {
|
|
|
|
|
|
const name = modelName.toLowerCase();
|
2025-10-12 18:30:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 规则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
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
2025-10-12 18:30:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 规则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
|
2025-10-16 01:20:11 +08:00
|
|
|
|
? 'border-2 border-blue-700 shadow-[0_0_10px_rgba(29,78,216,1)]'
|
2025-10-12 18:30:34 +08:00
|
|
|
|
: '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]"
|
2025-10-16 01:20:11 +08:00
|
|
|
|
popover-class="popover-content"
|
2025-06-17 22:37:37 +08:00
|
|
|
|
:popover-style="popoverStyle"
|
|
|
|
|
|
trigger="clickTarget"
|
|
|
|
|
|
@show="showPopover"
|
|
|
|
|
|
>
|
2025-10-16 01:20:11 +08:00
|
|
|
|
<!-- 触发元素插槽 -->
|
2025-06-17 22:37:37 +08:00
|
|
|
|
<template #trigger>
|
|
|
|
|
|
<div
|
2025-10-16 01:20:11 +08:00
|
|
|
|
class="model-select-box select-none flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-12px border-[rgba()] leading-snug"
|
2025-06-17 22:37:37 +08:00
|
|
|
|
>
|
2025-10-16 01:20:11 +08:00
|
|
|
|
<div class="model-select-box-icon">
|
|
|
|
|
|
<SvgIcon name="models" size="12" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div :class="getModelStyleClass(currentModelName)" class="model-select-box-text font-size-12px">
|
|
|
|
|
|
{{ currentModelName }}
|
|
|
|
|
|
</div>
|
2025-06-17 22:37:37 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2025-10-16 01:20:11 +08:00
|
|
|
|
<div class="popover-content-box">
|
2025-06-17 22:37:37 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-for="item in popoverList"
|
|
|
|
|
|
:key="item.id"
|
2025-10-12 18:30:34 +08:00
|
|
|
|
:class="getWrapperClass(item)"
|
|
|
|
|
|
@click="handleModelClick(item)"
|
2025-06-17 22:37:37 +08:00
|
|
|
|
>
|
2025-10-16 01:20:11 +08:00
|
|
|
|
<Popover
|
|
|
|
|
|
trigger-class="popover-trigger-item-text"
|
|
|
|
|
|
popover-class="rounded-tooltip"
|
|
|
|
|
|
placement="right"
|
|
|
|
|
|
trigger="hover"
|
|
|
|
|
|
:offset="[12, 0]"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #trigger>
|
|
|
|
|
|
<span :class="getModelStyleClass(item.modelName)">
|
|
|
|
|
|
{{ item.modelName }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="popover-content-box-item-text text-wrap max-w-200px rounded-lg p-8px font-size-12px line-height-tight"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ item.remark }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Popover>
|
2025-10-12 18:30:34 +08:00
|
|
|
|
|
2025-10-16 01:20:11 +08:00
|
|
|
|
<!-- VIP锁定图标 -->
|
2025-10-12 18:30:34 +08:00
|
|
|
|
<el-icon
|
|
|
|
|
|
v-if="!isModelAvailable(item)"
|
2025-10-16 01:20:11 +08:00
|
|
|
|
class="absolute right-1 top-1/2 transform -translate-y-1/2"
|
2025-06-17 22:37:37 +08:00
|
|
|
|
>
|
2025-10-12 18:30:34 +08:00
|
|
|
|
<Lock />
|
|
|
|
|
|
</el-icon>
|
2025-06-17 22:37:37 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Popover>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2025-10-16 01:20:11 +08:00
|
|
|
|
<style scoped lang="scss">
|
2025-10-15 00:04:17 +08:00
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 01:20:11 +08:00
|
|
|
|
.popover-content-box {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 4px;
|
2025-10-16 22:50:10 +08:00
|
|
|
|
height: 300px;
|
2025-10-16 01:20:11 +08:00
|
|
|
|
overflow: hidden auto;
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.popover-trigger-item-text) {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.popover-content-box-item-text {
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
background-color: black;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 滚动条样式
|
|
|
|
|
|
&::-webkit-scrollbar {
|
|
|
|
|
|
width: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
&::-webkit-scrollbar-track {
|
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
|
}
|
|
|
|
|
|
&::-webkit-scrollbar-thumb {
|
|
|
|
|
|
background: #cccccc;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-12 18:30:34 +08:00
|
|
|
|
/* 彩色流光动画 */
|
|
|
|
|
|
@keyframes gradientFlow {
|
|
|
|
|
|
0%, 100% { background-position: 0 50%; }
|
|
|
|
|
|
50% { background-position: 100% 50%; }
|
2025-06-17 22:37:37 +08:00
|
|
|
|
}
|
2025-10-12 18:30:34 +08:00
|
|
|
|
|
|
|
|
|
|
/* 金色光泽动画 */
|
|
|
|
|
|
@keyframes goldShine {
|
|
|
|
|
|
0% { transform: translateX(-100%) translateY(-100%); }
|
|
|
|
|
|
100% { transform: translateX(100%) translateY(100%); }
|
2025-06-17 22:37:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-12 18:30:34 +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>
|