feat: 个人中心新增尊享服务、模型列表区分
This commit is contained in:
@@ -19,3 +19,8 @@ export function getQrCodeResult(data: any) {
|
|||||||
export function getWechatAuth(data: any) {
|
export function getWechatAuth(data: any) {
|
||||||
return post<any>('/fuwuhao/register', data).json();
|
return post<any>('/fuwuhao/register', data).json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取尊享服务Token包额度
|
||||||
|
export function getPremiumTokenPackage() {
|
||||||
|
return get<any>('/account/premium/token-package').json();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
<!-- 切换模型 -->
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { GetSessionListVO } from '@/api/model/types';
|
import type { GetSessionListVO } from '@/api/model/types';
|
||||||
import { Lock } from '@element-plus/icons-vue';
|
import { Lock } from '@element-plus/icons-vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import Popover from '@/components/Popover/index.vue';
|
import Popover from '@/components/Popover/index.vue';
|
||||||
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||||
import { useUserStore } from '@/stores';
|
import { useUserStore } from '@/stores';
|
||||||
import { useModelStore } from '@/stores/modules/model';
|
import { useModelStore } from '@/stores/modules/model';
|
||||||
@@ -12,7 +10,6 @@ import { showProductPackage } from '@/utils/product-package.ts';
|
|||||||
import { isUserVip } from '@/utils/user';
|
import { isUserVip } from '@/utils/user';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
// 检查模型是否可用
|
// 检查模型是否可用
|
||||||
@@ -22,21 +19,14 @@ function isModelAvailable(item: GetSessionListVO) {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await modelStore.requestModelList();
|
await modelStore.requestModelList();
|
||||||
// 设置默认模型
|
if (modelStore.modelList.length > 0 && !modelStore.currentModelInfo?.modelId) {
|
||||||
if (
|
|
||||||
modelStore.modelList.length > 0
|
|
||||||
&& (!modelStore.currentModelInfo || !modelStore.currentModelInfo.modelId)
|
|
||||||
) {
|
|
||||||
modelStore.setCurrentModelInfo(modelStore.modelList[0]);
|
modelStore.setCurrentModelInfo(modelStore.modelList[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentModelName = computed(
|
const currentModelName = computed(() => modelStore.currentModelInfo?.modelName);
|
||||||
() => modelStore.currentModelInfo && modelStore.currentModelInfo.modelName,
|
|
||||||
);
|
|
||||||
const popoverList = computed(() => modelStore.modelList);
|
const popoverList = computed(() => modelStore.modelList);
|
||||||
|
|
||||||
/* 弹出面板 开始 */
|
|
||||||
const popoverStyle = ref({
|
const popoverStyle = ref({
|
||||||
width: '200px',
|
width: '200px',
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
@@ -44,39 +34,33 @@ const popoverStyle = ref({
|
|||||||
background: 'var(--el-bg-color, #fff)',
|
background: 'var(--el-bg-color, #fff)',
|
||||||
border: '1px solid var(--el-border-color-light)',
|
border: '1px solid var(--el-border-color-light)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
boxShadow: '0 2px 12px 0 rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 2px 12px rgba(0,0,0,0.1)',
|
||||||
});
|
});
|
||||||
const popoverRef = ref();
|
const popoverRef = ref();
|
||||||
|
|
||||||
// 显示
|
|
||||||
async function showPopover() {
|
async function showPopover() {
|
||||||
// 获取最新的模型列表
|
|
||||||
await modelStore.requestModelList();
|
await modelStore.requestModelList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击
|
/* -------------------------------
|
||||||
// 处理模型点击
|
模型点击逻辑
|
||||||
|
-------------------------------- */
|
||||||
function handleModelClick(item: GetSessionListVO) {
|
function handleModelClick(item: GetSessionListVO) {
|
||||||
console.log('modelStore.modelList', modelStore.modelList);
|
|
||||||
if (!isModelAvailable(item)) {
|
if (!isModelAvailable(item)) {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
`
|
`
|
||||||
<div class="text-center leading-relaxed">
|
<div class="text-center leading-relaxed">
|
||||||
<h3 class="text-lg font-bold mb-3">${isUserVip() ? 'YiXinAI-VIP 会员' : '成为 YiXinAI-VIP'}</h3>
|
<h3 class="text-lg font-bold mb-3">${isUserVip() ? 'YiXinAI-VIP 会员' : '成为 YiXinAI-VIP'}</h3>
|
||||||
<p class="mb-2">
|
<p class="mb-2">
|
||||||
${
|
${isUserVip()
|
||||||
isUserVip()
|
|
||||||
? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!'
|
? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!'
|
||||||
: '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。'
|
: '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。'}
|
||||||
}
|
</p>
|
||||||
</p>
|
<p class="text-sm text-gray-500">
|
||||||
${
|
${isUserVip() ? '您可随时访问产品页面查看更多特权内容。' : '请点击右上角登录按钮,登录后进行购买!'}
|
||||||
isUserVip()
|
</p>
|
||||||
? '<p class="text-sm text-gray-500">您可随时访问产品页面查看更多特权内容。</p>'
|
</div>
|
||||||
: '<p class="text-sm text-gray-500">请点击右上角登录按钮,登录后进行购买!</p>'
|
`,
|
||||||
}
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
isUserVip() ? '会员状态' : '会员尊享',
|
isUserVip() ? '会员状态' : '会员尊享',
|
||||||
{
|
{
|
||||||
confirmButtonText: '产品查看',
|
confirmButtonText: '产品查看',
|
||||||
@@ -86,23 +70,63 @@ function handleModelClick(item: GetSessionListVO) {
|
|||||||
center: true,
|
center: true,
|
||||||
roundButton: true,
|
roundButton: true,
|
||||||
},
|
},
|
||||||
)
|
).then(() => showProductPackage());
|
||||||
.then(() => {
|
|
||||||
showProductPackage();
|
|
||||||
|
|
||||||
// router.push({
|
|
||||||
// name: 'products', // 使用命名路由
|
|
||||||
// query: { from: isUserVip() ? 'vip' : 'user' }, // 可选:添加来源标识
|
|
||||||
// });
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// 点击右上角关闭或“关闭”按钮,不执行任何操作
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
modelStore.setCurrentModelInfo(item);
|
modelStore.setCurrentModelInfo(item);
|
||||||
popoverRef.value?.hide?.();
|
popoverRef.value?.hide?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------
|
||||||
|
模型样式规则
|
||||||
|
规则1:普通灰色(免费模型)
|
||||||
|
规则2:金色光泽(VIP/付费)
|
||||||
|
规则3:彩色流光(尊享/高级)
|
||||||
|
-------------------------------- */
|
||||||
|
function getModelStyleClass(item: GetSessionListVO) {
|
||||||
|
const name = item.modelName.toLowerCase();
|
||||||
|
|
||||||
|
// 规则3:彩色流光
|
||||||
|
if (name.includes('claude') || name.includes('platinum')) {
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -111,108 +135,68 @@ function handleModelClick(item: GetSessionListVO) {
|
|||||||
ref="popoverRef"
|
ref="popoverRef"
|
||||||
placement="top-start"
|
placement="top-start"
|
||||||
:offset="[4, 0]"
|
:offset="[4, 0]"
|
||||||
popover-class="popover-content"
|
|
||||||
:popover-style="popoverStyle"
|
:popover-style="popoverStyle"
|
||||||
trigger="clickTarget"
|
trigger="clickTarget"
|
||||||
@show="showPopover"
|
@show="showPopover"
|
||||||
>
|
>
|
||||||
<!-- 触发元素插槽 -->
|
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div
|
<div
|
||||||
class="model-select-box select-none flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-12px border-[rgba()]"
|
class="flex items-center gap-1 p-2 rounded-md border border-blue-500 text-blue-600 cursor-pointer select-none"
|
||||||
>
|
>
|
||||||
<div class="model-select-box-icon">
|
<SvgIcon name="models" size="12" />
|
||||||
<SvgIcon name="models" size="12" />
|
<span class="text-sm font-medium">{{ currentModelName }}</span>
|
||||||
</div>
|
|
||||||
<div class="model-select-box-text font-size-12px">
|
|
||||||
{{ currentModelName }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="popover-content-box">
|
<div class="flex flex-col gap-1 max-h-52 overflow-y-auto p-1">
|
||||||
<div
|
<div
|
||||||
v-for="item in popoverList"
|
v-for="item in popoverList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="popover-content-box-items w-full rounded-8px select-none transition-all transition-duration-300 flex items-center hover:cursor-pointer hover:bg-[rgba(0,0,0,.04)]"
|
:class="getWrapperClass(item)"
|
||||||
|
@click="handleModelClick(item)"
|
||||||
>
|
>
|
||||||
<Popover
|
<span :class="getModelStyleClass(item)">
|
||||||
trigger-class="popover-trigger-item-text"
|
{{ item.modelName }}
|
||||||
popover-class="rounded-tooltip"
|
</span>
|
||||||
placement="right"
|
|
||||||
trigger="hover"
|
<el-icon
|
||||||
:offset="[12, 0]"
|
v-if="!isModelAvailable(item)"
|
||||||
|
class="absolute right-1 top-1/2 -translate-y-1/2 text-gray-400"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<Lock />
|
||||||
<div
|
</el-icon>
|
||||||
class="popover-content-box-item p-4px font-size-12px text-overflow line-height-16px relative"
|
|
||||||
:class="[
|
|
||||||
{ 'bg-[rgba(0,0,0,.04)] is-select': item.modelName === currentModelName },
|
|
||||||
{ 'cursor-not-allowed opacity-60': !isModelAvailable(item) },
|
|
||||||
]"
|
|
||||||
@click="handleModelClick(item)"
|
|
||||||
>
|
|
||||||
{{ item.modelName }}
|
|
||||||
<!-- VIP锁定图标 -->
|
|
||||||
<el-icon
|
|
||||||
v-if="!isModelAvailable(item)"
|
|
||||||
class="absolute right-1 top-1/2 transform -translate-y-1/2"
|
|
||||||
>
|
|
||||||
<Lock />
|
|
||||||
</el-icon>
|
|
||||||
</div>
|
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped>
|
||||||
.model-select-box {
|
/* 彩色流光动画 */
|
||||||
color: var(--el-color-primary, #409eff);
|
@keyframes gradientFlow {
|
||||||
background: var(--el-color-primary-light-9, rgb(235.9 245.3 255));
|
0%, 100% { background-position: 0 50%; }
|
||||||
border: 1px solid var(--el-color-primary, #409eff);
|
50% { background-position: 100% 50%; }
|
||||||
border-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.popover-content-box-item.is-select {
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--el-color-primary, #409eff);
|
|
||||||
}
|
|
||||||
.popover-content-box {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
height: 200px;
|
|
||||||
overflow: hidden auto;
|
|
||||||
.popover-content-box-items {
|
|
||||||
:deep() {
|
|
||||||
.popover-trigger-item-text {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.popover-content-box-item-text {
|
|
||||||
color: white;
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 滚动条样式
|
/* 金色光泽动画 */
|
||||||
&::-webkit-scrollbar {
|
@keyframes goldShine {
|
||||||
width: 4px;
|
0% { transform: translateX(-100%) translateY(-100%); }
|
||||||
}
|
100% { transform: translateX(100%) translateY(100%); }
|
||||||
&::-webkit-scrollbar-track {
|
}
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
/* 柔光 hover 动效 */
|
||||||
&::-webkit-scrollbar-thumb {
|
@keyframes glowPulse {
|
||||||
background: #cccccc;
|
0%, 100% { box-shadow: 0 0 6px rgba(37,99,235,0.2); }
|
||||||
border-radius: 4px;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ function cleanupPayment() {
|
|||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ key: 'member', label: '会员套餐' },
|
{ key: 'member', label: '会员套餐' },
|
||||||
// { key: 'token', label: 'Token 套餐' },
|
{ key: 'token', label: '尊享服务' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const benefitsData = {
|
const benefitsData = {
|
||||||
|
|||||||
@@ -0,0 +1,457 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Clock, Coin, TrophyBase, WarningFilled } from '@element-plus/icons-vue';
|
||||||
|
import { getPremiumTokenPackage } from '@/api/user';
|
||||||
|
|
||||||
|
// 尊享服务数据
|
||||||
|
const loading = ref(false);
|
||||||
|
const packageData = ref<any>({
|
||||||
|
totalQuota: 0, // 购买总额度
|
||||||
|
usedQuota: 0, // 已使用额度
|
||||||
|
remainingQuota: 0, // 剩余额度
|
||||||
|
usagePercentage: 0, // 使用百分比
|
||||||
|
packageName: 'Token包', // 套餐名称
|
||||||
|
expireDate: '', // 过期时间
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const usagePercent = computed(() => {
|
||||||
|
if (packageData.value.totalQuota === 0)
|
||||||
|
return 0;
|
||||||
|
return Math.round((packageData.value.usedQuota / packageData.value.totalQuota) * 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
const remainingPercent = computed(() => {
|
||||||
|
return 100 - usagePercent.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取进度条颜色
|
||||||
|
const progressColor = computed(() => {
|
||||||
|
const percent = usagePercent.value;
|
||||||
|
if (percent >= 90)
|
||||||
|
return '#f56c6c'; // 红色
|
||||||
|
if (percent >= 70)
|
||||||
|
return '#e6a23c'; // 橙色
|
||||||
|
return '#67c23a'; // 绿色
|
||||||
|
});
|
||||||
|
|
||||||
|
// 格式化数字
|
||||||
|
function formatNumber(num: number): string {
|
||||||
|
return num.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取尊享服务Token包数据
|
||||||
|
async function fetchPremiumTokenPackage() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getPremiumTokenPackage();
|
||||||
|
if (res.success && res.data) {
|
||||||
|
packageData.value = {
|
||||||
|
totalQuota: res.data.totalQuota || 0,
|
||||||
|
usedQuota: res.data.usedQuota || 0,
|
||||||
|
remainingQuota: res.data.remainingQuota || 0,
|
||||||
|
usagePercentage: res.data.usagePercentage || 0,
|
||||||
|
packageName: res.data.packageName || 'Token包',
|
||||||
|
expireDate: res.data.expireDate || '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算剩余额度(如果接口没返回)
|
||||||
|
if (!packageData.value.remainingQuota) {
|
||||||
|
packageData.value.remainingQuota = packageData.value.totalQuota - packageData.value.usedQuota;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ElMessage.warning('暂无尊享服务数据');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('获取尊享服务数据失败:', error);
|
||||||
|
ElMessage.error('获取尊享服务数据失败');
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
function refreshData() {
|
||||||
|
fetchPremiumTokenPackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchPremiumTokenPackage();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="premium-service" v-loading="loading">
|
||||||
|
<div class="header">
|
||||||
|
<h2>
|
||||||
|
<el-icon><TrophyBase /></el-icon>
|
||||||
|
尊享服务
|
||||||
|
</h2>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="refreshData"
|
||||||
|
>
|
||||||
|
刷新数据
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 套餐信息卡片 -->
|
||||||
|
<el-card class="package-card" shadow="hover">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<el-icon class="header-icon"><Coin /></el-icon>
|
||||||
|
<span class="header-title">{{ packageData.packageName }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="package-content">
|
||||||
|
<!-- 统计数据 -->
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item total">
|
||||||
|
<div class="stat-label">购买总额度</div>
|
||||||
|
<div class="stat-value">{{ formatNumber(packageData.totalQuota) }}</div>
|
||||||
|
<div class="stat-unit">Tokens</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-item used">
|
||||||
|
<div class="stat-label">已用额度</div>
|
||||||
|
<div class="stat-value">{{ formatNumber(packageData.usedQuota) }}</div>
|
||||||
|
<div class="stat-unit">Tokens</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-item remaining">
|
||||||
|
<div class="stat-label">剩余额度</div>
|
||||||
|
<div class="stat-value">{{ formatNumber(packageData.remainingQuota) }}</div>
|
||||||
|
<div class="stat-unit">Tokens</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 进度条 -->
|
||||||
|
<div class="progress-section">
|
||||||
|
<div class="progress-header">
|
||||||
|
<span class="progress-label">使用进度</span>
|
||||||
|
<span class="progress-percent" :style="{ color: progressColor }">
|
||||||
|
{{ usagePercent }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-progress
|
||||||
|
:percentage="usagePercent"
|
||||||
|
:color="progressColor"
|
||||||
|
:stroke-width="20"
|
||||||
|
:show-text="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="progress-legend">
|
||||||
|
<div class="legend-item">
|
||||||
|
<span class="legend-dot used-dot"></span>
|
||||||
|
<span class="legend-text">已使用: {{ usagePercent }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="legend-item">
|
||||||
|
<span class="legend-dot remaining-dot"></span>
|
||||||
|
<span class="legend-text">剩余: {{ remainingPercent }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 过期时间 -->
|
||||||
|
<div v-if="packageData.expireDate" class="expire-info">
|
||||||
|
<el-icon><Clock /></el-icon>
|
||||||
|
<span>有效期至: {{ packageData.expireDate }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 温馨提示 -->
|
||||||
|
<el-alert
|
||||||
|
class="tips-alert"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="tips-content">
|
||||||
|
<p>温馨提示:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Token额度根据不同模型消耗速率不同</li>
|
||||||
|
<li>建议合理使用,避免额度过快消耗</li>
|
||||||
|
<li>额度不足时请及时充值,避免影响使用</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 购买提示卡片(额度不足时显示) -->
|
||||||
|
<el-card
|
||||||
|
v-if="remainingPercent < 20"
|
||||||
|
class="warning-card"
|
||||||
|
shadow="hover"
|
||||||
|
>
|
||||||
|
<div class="warning-content">
|
||||||
|
<el-icon class="warning-icon" color="#e6a23c"><WarningFilled /></el-icon>
|
||||||
|
<div class="warning-text">
|
||||||
|
<h3>额度即将用完</h3>
|
||||||
|
<p>您的Token额度已使用{{ usagePercent }}%,剩余额度较少,建议及时充值。</p>
|
||||||
|
</div>
|
||||||
|
<el-button type="warning" @click="$router.push('/products')">
|
||||||
|
立即充值
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.premium-service {
|
||||||
|
padding: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h2 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .el-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 套餐卡片 */
|
||||||
|
.package-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统计数据网格 */
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item.total {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item.used {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item.remaining {
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-unit {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进度条部分 */
|
||||||
|
.progress-section {
|
||||||
|
padding: 20px;
|
||||||
|
background: #f7f8fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-label {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-percent {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-legend {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.used-dot {
|
||||||
|
background: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remaining-dot {
|
||||||
|
background: #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 过期信息 */
|
||||||
|
.expire-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #f0f9ff;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #0369a1;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 温馨提示 */
|
||||||
|
.tips-alert {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-content p {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-content ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-content li {
|
||||||
|
margin: 4px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 警告卡片 */
|
||||||
|
.warning-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px solid #e6a23c;
|
||||||
|
background: #fef3e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon {
|
||||||
|
font-size: 40px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-text h3 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: #e6a23c;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-text p {
|
||||||
|
margin: 0;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式布局 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.premium-service {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-legend {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-content {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -65,6 +65,7 @@ const navItems = [
|
|||||||
{ name: 'apiKey', label: 'API密钥', icon: 'Key' },
|
{ name: 'apiKey', label: 'API密钥', icon: 'Key' },
|
||||||
{ name: 'rechargeLog', label: '充值记录', icon: 'Document' },
|
{ name: 'rechargeLog', label: '充值记录', icon: 'Document' },
|
||||||
{ name: 'usageStatistics', label: '用量统计', icon: 'Histogram' },
|
{ name: 'usageStatistics', label: '用量统计', icon: 'Histogram' },
|
||||||
|
{ name: 'premiumService', label: '尊享服务', icon: 'ColdDrink' },
|
||||||
// { name: 'usageStatistics2', label: '用量统计2', icon: 'Histogram' },
|
// { name: 'usageStatistics2', label: '用量统计2', icon: 'Histogram' },
|
||||||
];
|
];
|
||||||
function openDialog() {
|
function openDialog() {
|
||||||
@@ -277,6 +278,10 @@ function onProductPackage() {
|
|||||||
<template #usageStatistics>
|
<template #usageStatistics>
|
||||||
<usage-statistics />
|
<usage-statistics />
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 尊享服务 -->
|
||||||
|
<template #premiumService>
|
||||||
|
<premium-service />
|
||||||
|
</template>
|
||||||
<!-- 用量统计 -->
|
<!-- 用量统计 -->
|
||||||
<!-- <template #usageStatistics2> -->
|
<!-- <template #usageStatistics2> -->
|
||||||
<!-- <usage-statistics2 /> -->
|
<!-- <usage-statistics2 /> -->
|
||||||
|
|||||||
2
Yi.Ai.Vue3/types/components.d.ts
vendored
2
Yi.Ai.Vue3/types/components.d.ts
vendored
@@ -34,6 +34,7 @@ declare module 'vue' {
|
|||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||||
|
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
@@ -46,6 +47,7 @@ declare module 'vue' {
|
|||||||
ModelSelect: typeof import('./../src/components/ModelSelect/index.vue')['default']
|
ModelSelect: typeof import('./../src/components/ModelSelect/index.vue')['default']
|
||||||
NavDialog: typeof import('./../src/components/userPersonalCenter/NavDialog.vue')['default']
|
NavDialog: typeof import('./../src/components/userPersonalCenter/NavDialog.vue')['default']
|
||||||
Popover: typeof import('./../src/components/Popover/index.vue')['default']
|
Popover: typeof import('./../src/components/Popover/index.vue')['default']
|
||||||
|
PremiumService: typeof import('./../src/components/userPersonalCenter/components/PremiumService.vue')['default']
|
||||||
ProductPackage: typeof import('./../src/components/ProductPackage/index.vue')['default']
|
ProductPackage: typeof import('./../src/components/ProductPackage/index.vue')['default']
|
||||||
QrCodeLogin: typeof import('./../src/components/LoginDialog/components/QrCodeLogin/index.vue')['default']
|
QrCodeLogin: typeof import('./../src/components/LoginDialog/components/QrCodeLogin/index.vue')['default']
|
||||||
RechargeLog: typeof import('./../src/components/userPersonalCenter/components/RechargeLog.vue')['default']
|
RechargeLog: typeof import('./../src/components/userPersonalCenter/components/RechargeLog.vue')['default']
|
||||||
|
|||||||
Reference in New Issue
Block a user