feat: 增加尊享产品
This commit is contained in:
@@ -1,5 +1,28 @@
|
|||||||
import { get, post } from '@/utils/request.ts';
|
import { get, post } from '@/utils/request.ts';
|
||||||
|
|
||||||
|
// 商品分类类型
|
||||||
|
export enum GoodsCategoryType {
|
||||||
|
Vip = 'Vip',
|
||||||
|
PremiumPackage = 'PremiumPackage',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品信息接口
|
||||||
|
export interface GoodsItem {
|
||||||
|
goodsName: string; // 商品名称
|
||||||
|
originalPrice: number; // 原价
|
||||||
|
referencePrice: number; // 参考价格(月均价)
|
||||||
|
goodsPrice: number; // 实际价格
|
||||||
|
discountAmount: number | null; // 折扣金额
|
||||||
|
goodsCategory: string; // 商品分类
|
||||||
|
remark: string | null; // 备注(标签)
|
||||||
|
discountDescription: string | null; // 折扣描述
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取商品列表
|
||||||
|
export function getGoodsList(categoryType: GoodsCategoryType) {
|
||||||
|
return get<GoodsItem[]>(`/pay/GoodsList?GoodsCategoryType=${categoryType}`).json();
|
||||||
|
}
|
||||||
|
|
||||||
// 创建订单并发起支付
|
// 创建订单并发起支付
|
||||||
export function createOrder(params: any) {
|
export function createOrder(params: any) {
|
||||||
return post<any>(`/pay/Order`, params).json();
|
return post<any>(`/pay/Order`, params).json();
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ export function getWechatAuth(data: any) {
|
|||||||
|
|
||||||
// 获取尊享服务Token包额度
|
// 获取尊享服务Token包额度
|
||||||
export function getPremiumTokenPackage() {
|
export function getPremiumTokenPackage() {
|
||||||
return get<any>('/account/premium/token-package').json();
|
return get<any>('/usage-statistics/premium-token-usage').json();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,13 +87,13 @@ function getModelStyleClass(item: GetSessionListVO) {
|
|||||||
const name = item.modelName.toLowerCase();
|
const name = item.modelName.toLowerCase();
|
||||||
|
|
||||||
// 规则3:彩色流光
|
// 规则3:彩色流光
|
||||||
if (name.includes('claude') || name.includes('platinum')) {
|
// if (name.includes('claude') || name.includes('platinum')) {
|
||||||
return `
|
// return `
|
||||||
text-transparent bg-clip-text
|
// text-transparent bg-clip-text
|
||||||
bg-[linear-gradient(45deg,#ff0000,#ff8000,#ffff00,#00ff00,#00ffff,#0000ff,#8000ff,#ff0080)]
|
// bg-[linear-gradient(45deg,#ff0000,#ff8000,#ffff00,#00ff00,#00ffff,#0000ff,#8000ff,#ff0080)]
|
||||||
bg-[length:400%_400%] animate-gradientFlow
|
// bg-[length:400%_400%] animate-gradientFlow
|
||||||
`;
|
// `;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 规则2:普通灰
|
// 规则2:普通灰
|
||||||
if (name.includes('deepseek-r1')) {
|
if (name.includes('deepseek-r1')) {
|
||||||
|
|||||||
@@ -1,40 +1,49 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CircleCheck } from '@element-plus/icons-vue';
|
import { CircleCheck, Loading } from '@element-plus/icons-vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { createOrder, getOrderStatus } from '@/api';
|
import { createOrder, getOrderStatus } from '@/api';
|
||||||
|
import { GoodsCategoryType, getGoodsList, type GoodsItem } from '@/api/pay';
|
||||||
import SupportModelList from '@/components/userPersonalCenter/components/SupportModelList.vue';
|
import SupportModelList from '@/components/userPersonalCenter/components/SupportModelList.vue';
|
||||||
import ProductPage from '@/pages/products/index.vue';
|
import ProductPage from '@/pages/products/index.vue';
|
||||||
import { useUserStore } from '@/stores';
|
import { useUserStore } from '@/stores';
|
||||||
|
|
||||||
const emit = defineEmits(['close']);
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
const packagesData = {
|
// 商品数据类型定义
|
||||||
member: [
|
interface PackageItem {
|
||||||
{ id: 1, name: '8个月(推荐)', desc: '', price: 183.2, perMonth: 22.9, tag: '超高性价比', discount: '限时活动', key: 8 },
|
id: number;
|
||||||
{ id: 2, name: '6个月', desc: '', price: 155.4, perMonth: 25.9, tag: '年度热销', key: 6 },
|
name: string;
|
||||||
{ id: 3, name: '3个月', desc: '', price: 83.7, perMonth: 27.9, tag: '短期体验', discount: '', key: 3 },
|
desc: string;
|
||||||
{ id: 4, name: '1个月', desc: '', price: 29.9, originalPrice: 49.9, tag: '灵活选择', discount: '', key: 1 },
|
price: number;
|
||||||
// { id: 5, name: '测试', desc: '', price: 0.01, originalPrice: 9.9, tag: '测试使用', discount: '', key: 0 },
|
originalPrice?: number;
|
||||||
],
|
perMonth?: number;
|
||||||
token: [
|
tag: string;
|
||||||
{ id: 6, name: '10M 输入Token', desc: '', price: 49.9, tag: '轻量用户', discount: '' },
|
discount: string;
|
||||||
{ id: 7, name: '20M 输入Token', desc: '', price: 79.9, tag: '中等使用', discount: '' },
|
goodsName: string; // 用于创建订单
|
||||||
{ id: 8, name: '30M 输入Token', desc: '', price: 99.9, tag: '量大管饱', discount: '' },
|
}
|
||||||
{ id: 9, name: '联系站长', desc: '', price: 0, tag: '企业级需求', discount: '' },
|
|
||||||
],
|
// 商品数据(从接口获取)
|
||||||
};
|
const packagesData = ref<{
|
||||||
|
member: PackageItem[];
|
||||||
|
token: PackageItem[];
|
||||||
|
}>({
|
||||||
|
member: [],
|
||||||
|
token: [],
|
||||||
|
});
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const visible = ref(true);
|
const visible = ref(true);
|
||||||
const activeTab = ref('member');
|
const activeTab = ref('member');
|
||||||
const selectedId = ref<number | null>(packagesData.member[3].id);
|
const selectedId = ref<number | null>(null);
|
||||||
const selectedPrice = ref(packagesData.member[3].price);
|
const selectedPrice = ref(0);
|
||||||
const selectPackageObject = ref<any>(packagesData.member[3]);
|
const selectPackageObject = ref<PackageItem | null>(null);
|
||||||
const showDetails = ref(false);
|
const showDetails = ref(false);
|
||||||
const isMobile = ref(false);
|
const isMobile = ref(false);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
const isLoadingGoods = ref(false); // 商品加载状态
|
||||||
const paymentWindow = ref<Window | null>(null);
|
const paymentWindow = ref<Window | null>(null);
|
||||||
const pollInterval = ref<NodeJS.Timeout | null>(null);
|
const pollInterval = ref<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
@@ -42,9 +51,63 @@ function checkMobile() {
|
|||||||
isMobile.value = window.innerWidth < 768;
|
isMobile.value = window.innerWidth < 768;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取商品列表
|
||||||
|
async function fetchGoodsList() {
|
||||||
|
isLoadingGoods.value = true;
|
||||||
|
try {
|
||||||
|
// 并行获取会员和Token商品列表
|
||||||
|
const [vipRes, tokenRes] = await Promise.all([
|
||||||
|
getGoodsList(GoodsCategoryType.Vip),
|
||||||
|
getGoodsList(GoodsCategoryType.PremiumPackage),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 转换VIP商品数据
|
||||||
|
packagesData.value.member = (vipRes.data || []).map((item: GoodsItem, index: number) => ({
|
||||||
|
id: index + 1,
|
||||||
|
name: item.goodsName,
|
||||||
|
desc: '',
|
||||||
|
price: item.goodsPrice,
|
||||||
|
originalPrice: item.originalPrice !== item.goodsPrice ? item.originalPrice : undefined,
|
||||||
|
perMonth: item.referencePrice,
|
||||||
|
tag: item.remark || '',
|
||||||
|
discount: item.discountDescription || '',
|
||||||
|
goodsName: item.goodsName, // 保存原始商品名称用于创建订单
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 转换Token商品数据
|
||||||
|
packagesData.value.token = (tokenRes.data || []).map((item: GoodsItem, index: number) => ({
|
||||||
|
id: index + 100, // 避免ID冲突
|
||||||
|
name: item.goodsName,
|
||||||
|
desc: '',
|
||||||
|
price: item.goodsPrice,
|
||||||
|
originalPrice: item.originalPrice !== item.goodsPrice ? item.originalPrice : undefined,
|
||||||
|
perMonth: item.referencePrice,
|
||||||
|
tag: item.remark || '',
|
||||||
|
discount: item.discountDescription || '',
|
||||||
|
goodsName: item.goodsName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 设置默认选中第一个会员套餐
|
||||||
|
if (packagesData.value.member.length > 0) {
|
||||||
|
const defaultPackage = packagesData.value.member[0];
|
||||||
|
selectedId.value = defaultPackage.id;
|
||||||
|
selectedPrice.value = defaultPackage.price;
|
||||||
|
selectPackageObject.value = defaultPackage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('获取商品列表失败:', error);
|
||||||
|
ElMessage.error('获取商品列表失败,请刷新重试');
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
isLoadingGoods.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkMobile();
|
checkMobile();
|
||||||
window.addEventListener('resize', checkMobile);
|
window.addEventListener('resize', checkMobile);
|
||||||
|
fetchGoodsList(); // 获取商品列表
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@@ -116,7 +179,7 @@ const fullBenefitsData = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentPackages = computed(() => packagesData[activeTab.value]);
|
const currentPackages = computed(() => packagesData.value[activeTab.value as 'member' | 'token']);
|
||||||
const currentBenefits = computed(() => benefitsData[activeTab.value]);
|
const currentBenefits = computed(() => benefitsData[activeTab.value]);
|
||||||
|
|
||||||
function selectPackage(pkg: any) {
|
function selectPackage(pkg: any) {
|
||||||
@@ -130,18 +193,17 @@ function handleClickLogin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function pay() {
|
async function pay() {
|
||||||
if (!selectedId.value) {
|
if (!selectedId.value || !selectPackageObject.value) {
|
||||||
ElMessage.warning('请选择一个套餐');
|
ElMessage.warning('请选择一个套餐');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
// const returnUrl = `https://ai.ccnetcore.com/pay-result`;
|
|
||||||
const returnUrl = `${window.location.origin}/pay-result`;
|
const returnUrl = `${window.location.origin}/pay-result`;
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
goodsType: selectPackageObject.value?.key,
|
goodsName: selectPackageObject.value.goodsName, // 使用商品名称
|
||||||
ReturnUrl: returnUrl,
|
ReturnUrl: returnUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -322,8 +384,16 @@ function onClose() {
|
|||||||
|
|
||||||
<!-- 移动端布局 -->
|
<!-- 移动端布局 -->
|
||||||
<div v-if="isMobile" class="mobile-layout">
|
<div v-if="isMobile" class="mobile-layout">
|
||||||
|
<!-- 商品加载状态 -->
|
||||||
|
<div v-if="isLoadingGoods" class="loading-container">
|
||||||
|
<el-icon class="is-loading" :size="40">
|
||||||
|
<Loading />
|
||||||
|
</el-icon>
|
||||||
|
<p>加载商品中...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 套餐卡片列表 -->
|
<!-- 套餐卡片列表 -->
|
||||||
<div class="package-list">
|
<div v-else-if="currentPackages.length > 0" class="package-list">
|
||||||
<div
|
<div
|
||||||
v-for="pkg in currentPackages" :key="pkg.id" class="package-card"
|
v-for="pkg in currentPackages" :key="pkg.id" class="package-card"
|
||||||
:class="{ selected: pkg.id === selectedId }" @click="selectPackage(pkg)"
|
:class="{ selected: pkg.id === selectedId }" @click="selectPackage(pkg)"
|
||||||
@@ -348,12 +418,32 @@ function onClose() {
|
|||||||
|
|
||||||
<!-- 价格 -->
|
<!-- 价格 -->
|
||||||
<div class="package-price">
|
<div class="package-price">
|
||||||
<span class="price">¥{{ pkg.price }}</span>
|
<!-- 官方参考价(仅Token套餐显示) -->
|
||||||
<span v-if="pkg.perMonth" class="per-month">
|
<div v-if="activeTab === 'token' && pkg.perMonth" class="reference-price-section">
|
||||||
仅¥{{ pkg.perMonth }}/月
|
<div class="reference-label">官方参考价</div>
|
||||||
</span>
|
<div class="reference-value">¥{{ pkg.perMonth }}</div>
|
||||||
<div v-if="pkg.originalPrice" class="original-price">
|
</div>
|
||||||
原价¥{{ pkg.originalPrice }}
|
|
||||||
|
<!-- 当前价格 -->
|
||||||
|
<div class="current-price-section">
|
||||||
|
<span class="price-label">{{ activeTab === 'member' ? '会员价' : '我们的价格' }}</span>
|
||||||
|
<div class="price-row">
|
||||||
|
<span class="price">¥{{ pkg.price }}</span>
|
||||||
|
<span v-if="activeTab === 'member' && pkg.perMonth" class="per-month">
|
||||||
|
仅¥{{ pkg.perMonth }}/月
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 优惠说明 -->
|
||||||
|
<div v-if="pkg.discount" class="discount-info">
|
||||||
|
<el-icon class="discount-icon"><CircleCheck /></el-icon>
|
||||||
|
<span class="discount-text">{{ pkg.discount }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 节省金额(如果有官方参考价) -->
|
||||||
|
<div v-if="activeTab === 'token' && pkg.perMonth && pkg.perMonth > pkg.price" class="save-amount">
|
||||||
|
立省 ¥{{ (pkg.perMonth - pkg.price).toFixed(2) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -429,8 +519,16 @@ function onClose() {
|
|||||||
|
|
||||||
<!-- 桌面端布局 -->
|
<!-- 桌面端布局 -->
|
||||||
<div v-else class="flex gap-6 desktop-layout">
|
<div v-else class="flex gap-6 desktop-layout">
|
||||||
|
<!-- 商品加载状态 -->
|
||||||
|
<div v-if="isLoadingGoods" class="loading-container-desktop">
|
||||||
|
<el-icon class="is-loading" :size="50">
|
||||||
|
<Loading />
|
||||||
|
</el-icon>
|
||||||
|
<p>加载商品中...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 左栏 套餐卡片 + 支付 -->
|
<!-- 左栏 套餐卡片 + 支付 -->
|
||||||
<div class="w-[60%] flex flex-col justify-between">
|
<div v-else class="w-[60%] flex flex-col justify-between">
|
||||||
<!-- 套餐卡片列表 -->
|
<!-- 套餐卡片列表 -->
|
||||||
<div class="flex flex-wrap gap-4">
|
<div class="flex flex-wrap gap-4">
|
||||||
<div
|
<div
|
||||||
@@ -457,12 +555,32 @@ function onClose() {
|
|||||||
|
|
||||||
<!-- 价格 -->
|
<!-- 价格 -->
|
||||||
<div class="package-price">
|
<div class="package-price">
|
||||||
<span class="price">¥{{ pkg.price }}</span>
|
<!-- 官方参考价(仅Token套餐显示) -->
|
||||||
<span v-if="pkg.perMonth" class="per-month">
|
<div v-if="activeTab === 'token' && pkg.perMonth" class="reference-price-section">
|
||||||
仅¥{{ pkg.perMonth }}/月
|
<div class="reference-label">官方参考价</div>
|
||||||
</span>
|
<div class="reference-value">¥{{ pkg.perMonth }}</div>
|
||||||
<div v-if="pkg.originalPrice" class="original-price">
|
</div>
|
||||||
原价¥{{ pkg.originalPrice }}
|
|
||||||
|
<!-- 当前价格 -->
|
||||||
|
<div class="current-price-section">
|
||||||
|
<span class="price-label">{{ activeTab === 'member' ? '会员价' : '我们的价格' }}</span>
|
||||||
|
<div class="price-row">
|
||||||
|
<span class="price">¥{{ pkg.price }}</span>
|
||||||
|
<span v-if="activeTab === 'member' && pkg.perMonth" class="per-month">
|
||||||
|
仅¥{{ pkg.perMonth }}/月
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 优惠说明 -->
|
||||||
|
<div v-if="pkg.discount" class="discount-info">
|
||||||
|
<el-icon class="discount-icon"><CircleCheck /></el-icon>
|
||||||
|
<span class="discount-text">{{ pkg.discount }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 节省金额(如果有官方参考价) -->
|
||||||
|
<div v-if="activeTab === 'token' && pkg.perMonth && pkg.perMonth > pkg.price" class="save-amount">
|
||||||
|
立省 ¥{{ (pkg.perMonth - pkg.price).toFixed(2) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -686,22 +804,91 @@ function onClose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.package-price {
|
.package-price {
|
||||||
.price {
|
display: flex;
|
||||||
font-size: 20px;
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
// 官方参考价
|
||||||
|
.reference-price-section {
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px dashed #f87171;
|
||||||
|
|
||||||
|
.reference-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #991b1b;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reference-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #dc2626;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前价格
|
||||||
|
.current-price-section {
|
||||||
|
.price-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #f97316;
|
||||||
|
}
|
||||||
|
|
||||||
|
.per-month {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优惠说明
|
||||||
|
.discount-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #1e40af;
|
||||||
|
line-height: 1.4;
|
||||||
|
|
||||||
|
.discount-icon {
|
||||||
|
color: #3b82f6;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discount-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节省金额
|
||||||
|
.save-amount {
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #f97316;
|
color: #15803d;
|
||||||
}
|
text-align: center;
|
||||||
|
box-shadow: 0 2px 4px rgba(34, 197, 94, 0.2);
|
||||||
.per-month {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.original-price {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #9ca3af;
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,22 +1035,94 @@ function onClose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.package-price {
|
.package-price {
|
||||||
.price {
|
display: flex;
|
||||||
font-size: 20px;
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
// 官方参考价
|
||||||
|
.reference-price-section {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px dashed #f87171;
|
||||||
|
|
||||||
|
.reference-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #991b1b;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reference-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #dc2626;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前价格
|
||||||
|
.current-price-section {
|
||||||
|
.price-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #f97316;
|
||||||
|
}
|
||||||
|
|
||||||
|
.per-month {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优惠说明
|
||||||
|
.discount-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #1e40af;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
.discount-icon {
|
||||||
|
color: #3b82f6;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discount-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节省金额
|
||||||
|
.save-amount {
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #f97316;
|
color: #15803d;
|
||||||
}
|
text-align: center;
|
||||||
|
box-shadow: 0 2px 6px rgba(34, 197, 94, 0.25);
|
||||||
.per-month {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.original-price {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #9ca3af;
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -993,5 +1252,35 @@ function onClose() {
|
|||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 加载状态样式 */
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
color: #909399;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container-desktop {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 400px;
|
||||||
|
color: #909399;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -34,8 +34,17 @@ const progressColor = computed(() => {
|
|||||||
return '#67c23a'; // 绿色
|
return '#67c23a'; // 绿色
|
||||||
});
|
});
|
||||||
|
|
||||||
// 格式化数字
|
// 格式化数字 - 转换为万为单位
|
||||||
function formatNumber(num: number): string {
|
function formatNumber(num: number): string {
|
||||||
|
if (num === 0)
|
||||||
|
return '0';
|
||||||
|
const wan = num / 10000;
|
||||||
|
// 保留2位小数,去掉末尾的0
|
||||||
|
return wan.toFixed(2).replace(/\.?0+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化原始数字(带千分位)
|
||||||
|
function formatRawNumber(num: number): string {
|
||||||
return num.toLocaleString();
|
return num.toLocaleString();
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@@ -61,18 +70,21 @@ async function fetchPremiumTokenPackage() {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await getPremiumTokenPackage();
|
const res = await getPremiumTokenPackage();
|
||||||
if (res.success && res.data) {
|
console.log('尊享服务Token包数据:', res);
|
||||||
|
if (res.data) {
|
||||||
|
// 适配新的接口字段名
|
||||||
|
const data = res.data;
|
||||||
packageData.value = {
|
packageData.value = {
|
||||||
totalQuota: res.data.totalQuota || 0,
|
totalQuota: data.premiumTotalTokens || 0, // 尊享包总token
|
||||||
usedQuota: res.data.usedQuota || 0,
|
usedQuota: data.premiumUsedTokens || 0, // 尊享包已使用token
|
||||||
remainingQuota: res.data.remainingQuota || 0,
|
remainingQuota: data.premiumRemainingTokens || 0, // 尊享包剩余token
|
||||||
usagePercentage: res.data.usagePercentage || 0,
|
usagePercentage: data.usagePercentage || 0,
|
||||||
packageName: res.data.packageName || 'Token包',
|
packageName: data.packageName || '尊享Token包',
|
||||||
expireDate: res.data.expireDate || '',
|
expireDate: data.expireDate || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算剩余额度(如果接口没返回)
|
// 计算剩余额度(如果接口没返回)
|
||||||
if (!packageData.value.remainingQuota) {
|
if (packageData.value.remainingQuota === 0 && packageData.value.totalQuota > 0) {
|
||||||
packageData.value.remainingQuota = packageData.value.totalQuota - packageData.value.usedQuota;
|
packageData.value.remainingQuota = packageData.value.totalQuota - packageData.value.usedQuota;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,7 +149,10 @@ onMounted(() => {
|
|||||||
{{ formatNumber(packageData.totalQuota) }}
|
{{ formatNumber(packageData.totalQuota) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-unit">
|
<div class="stat-unit">
|
||||||
Tokens
|
万 Tokens
|
||||||
|
</div>
|
||||||
|
<div class="stat-raw" :title="`原始值: ${formatRawNumber(packageData.totalQuota)} Tokens`">
|
||||||
|
({{ formatRawNumber(packageData.totalQuota) }})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -149,7 +164,10 @@ onMounted(() => {
|
|||||||
{{ formatNumber(packageData.usedQuota) }}
|
{{ formatNumber(packageData.usedQuota) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-unit">
|
<div class="stat-unit">
|
||||||
Tokens
|
万 Tokens
|
||||||
|
</div>
|
||||||
|
<div class="stat-raw" :title="`原始值: ${formatRawNumber(packageData.usedQuota)} Tokens`">
|
||||||
|
({{ formatRawNumber(packageData.usedQuota) }})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -161,7 +179,10 @@ onMounted(() => {
|
|||||||
{{ formatNumber(packageData.remainingQuota) }}
|
{{ formatNumber(packageData.remainingQuota) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-unit">
|
<div class="stat-unit">
|
||||||
Tokens
|
万 Tokens
|
||||||
|
</div>
|
||||||
|
<div class="stat-raw" :title="`原始值: ${formatRawNumber(packageData.remainingQuota)} Tokens`">
|
||||||
|
({{ formatRawNumber(packageData.remainingQuota) }})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -345,6 +366,18 @@ onMounted(() => {
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat-raw {
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-top: 4px;
|
||||||
|
cursor: help;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-raw:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* 进度条部分 */
|
/* 进度条部分 */
|
||||||
.progress-section {
|
.progress-section {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ const totalTokens = ref(0);
|
|||||||
const usageData = ref<any[]>([]);
|
const usageData = ref<any[]>([]);
|
||||||
const modelUsageData = ref<any[]>([]);
|
const modelUsageData = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 计算属性:是否有模型数据
|
||||||
|
const hasModelData = computed(() => modelUsageData.value.length > 0);
|
||||||
|
|
||||||
// 获取用量数据
|
// 获取用量数据
|
||||||
async function fetchUsageData() {
|
async function fetchUsageData() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -156,9 +159,67 @@ function updateLineChart() {
|
|||||||
|
|
||||||
// 更新饼图 - 动态适应数据量
|
// 更新饼图 - 动态适应数据量
|
||||||
function updatePieChart() {
|
function updatePieChart() {
|
||||||
if (!pieChartInstance || modelUsageData.value.length === 0)
|
if (!pieChartInstance)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// 空数据状态
|
||||||
|
if (modelUsageData.value.length === 0) {
|
||||||
|
const emptyOption = {
|
||||||
|
graphic: [
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
left: 'center',
|
||||||
|
top: 'center',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'circle',
|
||||||
|
shape: {
|
||||||
|
r: 80,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fill: '#f5f7fa',
|
||||||
|
stroke: '#e9ecef',
|
||||||
|
lineWidth: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
style: {
|
||||||
|
text: '📊',
|
||||||
|
fontSize: 48,
|
||||||
|
x: -24,
|
||||||
|
y: -40,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
style: {
|
||||||
|
text: '暂无数据',
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fill: '#909399',
|
||||||
|
x: -36,
|
||||||
|
y: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
style: {
|
||||||
|
text: '还没有模型使用记录',
|
||||||
|
fontSize: 14,
|
||||||
|
fill: '#c0c4cc',
|
||||||
|
x: -70,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
pieChartInstance.setOption(emptyOption, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = modelUsageData.value.map(item => ({
|
const data = modelUsageData.value.map(item => ({
|
||||||
name: item.model,
|
name: item.model,
|
||||||
value: item.tokens,
|
value: item.tokens,
|
||||||
@@ -168,6 +229,7 @@ function updatePieChart() {
|
|||||||
const isSmallContainer = pieContainerSize.width.value < 600;
|
const isSmallContainer = pieContainerSize.width.value < 600;
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
|
graphic: [], // 清空graphic配置
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
formatter: '{a} <br/>{b}: {c} tokens ({d}%)',
|
formatter: '{a} <br/>{b}: {c} tokens ({d}%)',
|
||||||
@@ -222,14 +284,76 @@ function updatePieChart() {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
pieChartInstance.setOption(option);
|
pieChartInstance.setOption(option, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新柱状图 - 动态适应数据量
|
// 更新柱状图 - 动态适应数据量
|
||||||
function updateBarChart() {
|
function updateBarChart() {
|
||||||
if (!barChartInstance || modelUsageData.value.length === 0)
|
if (!barChartInstance)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// 空数据状态
|
||||||
|
if (modelUsageData.value.length === 0) {
|
||||||
|
const emptyOption = {
|
||||||
|
graphic: [
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
left: 'center',
|
||||||
|
top: 'center',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'rect',
|
||||||
|
shape: {
|
||||||
|
width: 160,
|
||||||
|
height: 160,
|
||||||
|
r: 12,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fill: '#f5f7fa',
|
||||||
|
stroke: '#e9ecef',
|
||||||
|
lineWidth: 2,
|
||||||
|
},
|
||||||
|
left: -80,
|
||||||
|
top: -80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
style: {
|
||||||
|
text: '📈',
|
||||||
|
fontSize: 48,
|
||||||
|
x: -24,
|
||||||
|
y: -40,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
style: {
|
||||||
|
text: '暂无数据',
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fill: '#909399',
|
||||||
|
x: -36,
|
||||||
|
y: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
style: {
|
||||||
|
text: '还没有模型使用记录',
|
||||||
|
fontSize: 14,
|
||||||
|
fill: '#c0c4cc',
|
||||||
|
x: -70,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
barChartInstance.setOption(emptyOption, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const models = modelUsageData.value.map(item => item.model);
|
const models = modelUsageData.value.map(item => item.model);
|
||||||
const tokens = modelUsageData.value.map(item => item.tokens);
|
const tokens = modelUsageData.value.map(item => item.tokens);
|
||||||
|
|
||||||
@@ -237,6 +361,7 @@ function updateBarChart() {
|
|||||||
const isSmallContainer = barContainerSize.width.value < 600;
|
const isSmallContainer = barContainerSize.width.value < 600;
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
|
graphic: [], // 清空graphic配置
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
@@ -302,7 +427,7 @@ function updateBarChart() {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
barChartInstance.setOption(option);
|
barChartInstance.setOption(option, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调整图表大小
|
// 调整图表大小
|
||||||
|
|||||||
1
Yi.Ai.Vue3/types/import_meta.d.ts
vendored
1
Yi.Ai.Vue3/types/import_meta.d.ts
vendored
@@ -6,7 +6,6 @@ interface ImportMetaEnv {
|
|||||||
readonly VITE_WEB_ENV: string;
|
readonly VITE_WEB_ENV: string;
|
||||||
readonly VITE_WEB_BASE_API: string;
|
readonly VITE_WEB_BASE_API: string;
|
||||||
readonly VITE_API_URL: string;
|
readonly VITE_API_URL: string;
|
||||||
readonly VITE_BUILD_COMPRESS: string;
|
|
||||||
readonly VITE_SSO_SEVER_URL: string;
|
readonly VITE_SSO_SEVER_URL: string;
|
||||||
readonly VITE_APP_VERSION: string;
|
readonly VITE_APP_VERSION: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user