feat: 增加支付宝在线支付、套餐订购弹窗、会员权益、支持模型展示等
This commit is contained in:
@@ -56,6 +56,7 @@ async function showPopover() {
|
||||
// 点击
|
||||
// 处理模型点击
|
||||
function handleModelClick(item: GetSessionListVO) {
|
||||
console.log('modelStore.modelList', modelStore.modelList);
|
||||
if (!isModelAvailable(item)) {
|
||||
ElMessageBox.confirm(
|
||||
`
|
||||
|
||||
947
Yi.Ai.Vue3/src/components/ProductPackage/index.vue
Normal file
947
Yi.Ai.Vue3/src/components/ProductPackage/index.vue
Normal file
@@ -0,0 +1,947 @@
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { createOrder, getOrderStatus } from '@/api';
|
||||
import SupportModelList from '@/components/userPersonalCenter/components/SupportModelList.vue';
|
||||
import ProductPage from '@/pages/products/index.vue';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const visible = ref(true);
|
||||
const activeTab = ref('member');
|
||||
const selectedId = ref<number | null>(null);
|
||||
const selectedPrice = ref(0);
|
||||
const selectPackageObject = ref<any>(null);
|
||||
const showDetails = ref(false);
|
||||
const isMobile = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const paymentWindow = ref<Window | null>(null);
|
||||
const pollInterval = ref<NodeJS.Timeout | null>(null);
|
||||
|
||||
function checkMobile() {
|
||||
isMobile.value = window.innerWidth < 768;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
cleanupPayment();
|
||||
});
|
||||
|
||||
function cleanupPayment() {
|
||||
if (pollInterval.value) {
|
||||
clearInterval(pollInterval.value);
|
||||
pollInterval.value = null;
|
||||
}
|
||||
// if (paymentWindow.value && !paymentWindow.value.closed) {
|
||||
// paymentWindow.value.close();
|
||||
// }
|
||||
// 清除轮询定时器
|
||||
if (pollInterval.value) {
|
||||
clearInterval(pollInterval.value);
|
||||
pollInterval.value = null;
|
||||
}
|
||||
|
||||
// 关闭支付窗口(如果还在)
|
||||
// if (paymentWindow.value && !paymentWindow.value.closed) {
|
||||
// paymentWindow.value.close();
|
||||
// paymentWindow.value = null;
|
||||
// }
|
||||
|
||||
retryCount = 0; // 重置重试计数器
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ key: 'member', label: '会员套餐' },
|
||||
// { key: 'token', label: 'Token 套餐' },
|
||||
];
|
||||
|
||||
const packagesData = {
|
||||
member: [
|
||||
{ id: 1, name: '10个月', desc: '', price: 199, perMonth: 19.9, tag: '超高性价比', discount: '长期省心', key: 10 },
|
||||
{ id: 2, name: '6个月', desc: '', price: 143.9, perMonth: 23.9, tag: '年度热销', key: 6 },
|
||||
{ id: 3, name: '3个月', desc: '', price: 80.7, perMonth: 26.9, tag: '短期体验', discount: '', key: 3 },
|
||||
{ id: 4, name: '1个月', desc: '', price: 29.9, originalPrice: 49.9, tag: '灵活选择', discount: '', key: 1 },
|
||||
// { id: 5, name: '测试', desc: '', price: 0.01, originalPrice: 9.9, tag: '测试使用', discount: '', key: 0 },
|
||||
],
|
||||
token: [
|
||||
{ id: 6, name: '10M 输入Token', desc: '', price: 49.9, tag: '轻量用户', discount: '' },
|
||||
{ id: 7, name: '20M 输入Token', desc: '', price: 79.9, tag: '中等使用', discount: '' },
|
||||
{ id: 8, name: '30M 输入Token', desc: '', price: 99.9, tag: '量大管饱', discount: '' },
|
||||
{ id: 9, name: '联系站长', desc: '', price: 0, tag: '企业级需求', discount: '' },
|
||||
],
|
||||
};
|
||||
|
||||
const benefitsData = {
|
||||
member: [
|
||||
{ name: '基础+高级模型访问', value: '' },
|
||||
{ name: 'AI专线超级加速', value: '' },
|
||||
{ name: '售后微信群支持', value: '' },
|
||||
{ name: '专属Api服务', value: '' },
|
||||
],
|
||||
token: [
|
||||
{ name: 'Token 用途', value: '用于调用 API 或模型生成内容' },
|
||||
{ name: '灵活计费', value: '按调用量扣费,更加自由' },
|
||||
{ name: '支持多模型', value: '适配多种模型调用需求' },
|
||||
],
|
||||
};
|
||||
|
||||
const fullBenefitsData = {
|
||||
member: [
|
||||
{ category: 'YiXinAI AI Pro', free: '1项', vip: '5项', value: '价值68/月' },
|
||||
{ category: '基础对话', free: '3条/天', vip: '不限条款', value: 'DeepSeek-R1 32b蒸馏版、AI-4o mini' },
|
||||
{ category: '高级对话', free: '-', vip: '400条/月', value: 'DeepSeek-R1 671b满血版、4o、Cd3.5s、Code、文档对话' },
|
||||
{ category: 'AI基础绘画', free: '-', vip: '500条/月', value: '' },
|
||||
{ category: 'AI高级绘画', free: '-', vip: '50条/月', value: '' },
|
||||
{ category: 'AI-4.0联网搜索', free: '-', vip: '支持', value: '' },
|
||||
{ category: 'AI PPT', free: '1项', vip: '12项', value: '价值99/月' },
|
||||
{ category: 'AI 思维导图', free: '1项', vip: '8项', value: '价值99/月' },
|
||||
{ category: '文档对话', free: '0项', vip: '6项', value: '价值99/月' },
|
||||
{ category: 'AI 绘图', free: '0项', vip: '5项', value: '价值99/月' },
|
||||
{ category: 'AI 写作', free: '1项', vip: '6项', value: '价值99/月' },
|
||||
],
|
||||
};
|
||||
|
||||
const currentPackages = computed(() => packagesData[activeTab.value]);
|
||||
const currentBenefits = computed(() => benefitsData[activeTab.value]);
|
||||
|
||||
function selectPackage(pkg: any) {
|
||||
selectedId.value = pkg.id;
|
||||
selectedPrice.value = pkg.price;
|
||||
selectPackageObject.value = pkg;
|
||||
}
|
||||
|
||||
async function pay() {
|
||||
if (!selectedId.value) {
|
||||
ElMessage.warning('请选择一个套餐');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
goodsType: selectPackageObject.value?.key,
|
||||
};
|
||||
|
||||
const response = await createOrder(params);
|
||||
console.log('订单创建成功:', response);
|
||||
|
||||
if (response.data.paymentPageHtml) {
|
||||
handlePaymentPage(response.data.paymentPageHtml, response.data.orderId, response.data.outTradeNo);
|
||||
}
|
||||
else {
|
||||
throw new Error('未获取到支付页面');
|
||||
}
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('支付失败:', error);
|
||||
ElMessage.error(`支付失败: ${error.message || '未知错误'}`);
|
||||
}
|
||||
finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
function handlePaymentPage(html: string, orderId: string, outTradeNo: string) {
|
||||
// 关闭当前弹窗(如果有)
|
||||
close();
|
||||
|
||||
// 创建支付窗口
|
||||
paymentWindow.value = window.open('', '_blank');
|
||||
if (!paymentWindow.value) {
|
||||
ElMessage.error('无法打开支付窗口,请检查浏览器弹窗设置或允许弹窗');
|
||||
return;
|
||||
}
|
||||
|
||||
// 写入支付页面HTML并自动提交
|
||||
paymentWindow.value.document.open();
|
||||
paymentWindow.value.document.write(html);
|
||||
// paymentWindow.value.document.close();
|
||||
|
||||
// 3秒后开始轮询支付状态(给支付页面加载时间)
|
||||
setTimeout(() => {
|
||||
startPolling(outTradeNo);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function startPolling(outTradeNo: string) {
|
||||
// 先清理之前的轮询任务
|
||||
cleanupPayment();
|
||||
|
||||
// 立即检查一次状态(避免等待第一个间隔)
|
||||
checkPaymentStatus(outTradeNo);
|
||||
|
||||
// 设置轮询任务(每3秒检查一次)
|
||||
pollInterval.value = setInterval(() => {
|
||||
checkPaymentStatus(outTradeNo);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
let retryCount = 0; // 错误重试计数器
|
||||
async function checkPaymentStatus(outTradeNo: string) {
|
||||
try {
|
||||
const result = await getOrderStatus(outTradeNo);
|
||||
console.log('订单状态检查结果:', result);
|
||||
|
||||
if (result.data.tradeStatus === 'TRADE_SUCCESS') {
|
||||
// 支付成功处理
|
||||
cleanupPayment();
|
||||
ElMessage.success('支付成功!');
|
||||
close(); // 关闭弹窗
|
||||
|
||||
// 可以在这里添加跳转到成功页面的逻辑
|
||||
// window.location.href = '/pay/success?order=' + outTradeNo;
|
||||
}
|
||||
else if (result.data.tradeStatus === 'TRADE_CLOSED'
|
||||
|| result.data.tradeStatus === 'TRADE_FAILED') {
|
||||
// 支付失败处理
|
||||
cleanupPayment();
|
||||
ElMessage.warning(`支付失败: ${result.data.tradeStatusDesc || '未知原因'}`);
|
||||
}
|
||||
// 其他状态(如待支付)不做处理,继续轮询
|
||||
}
|
||||
catch (error) {
|
||||
console.error('检查订单状态失败:', error);
|
||||
// 网络错误等情况,可以重试几次后停止
|
||||
if (retryCount > 3) {
|
||||
cleanupPayment();
|
||||
ElMessage.error('检查支付状态失败,请手动刷新页面确认');
|
||||
}
|
||||
retryCount++;
|
||||
}
|
||||
}
|
||||
const router = useRouter();
|
||||
|
||||
function toggleDetails() {
|
||||
showDetails.value = !showDetails.value;
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible.value = false;
|
||||
emit('close');
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:width="isMobile ? '90%' : '980px'"
|
||||
:fullscreen="isMobile && showDetails"
|
||||
:show-close="false"
|
||||
destroy-on-close
|
||||
class="product-package-dialog"
|
||||
@close="onClose"
|
||||
>
|
||||
<!-- 详情页 -->
|
||||
<div v-if="showDetails" class="details-view">
|
||||
<!-- 顶部标题和返回按钮 -->
|
||||
<div class="flex items-center mb-6 sticky top-0 bg-white z-10 pt-2 pb-4">
|
||||
<el-button text circle size="small" class="mr-2" @click="toggleDetails">
|
||||
←
|
||||
</el-button>
|
||||
<div class="text-xl font-bold">
|
||||
YiXinAI会员详细权益
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProductPage />
|
||||
<!-- 权益详情表格 -->
|
||||
<div v-if="false" class="benefits-table">
|
||||
<div class="table-header">
|
||||
<div class="table-cell">
|
||||
服务项
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
免费用户
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
AI大会员
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="(item, index) in fullBenefitsData.member" :key="index" class="table-row">
|
||||
<div class="table-cell font-medium">
|
||||
{{ item.category }}
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
{{ item.free }}
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<div>{{ item.vip }}</div>
|
||||
<div v-if="item.value" class="text-gray-500 text-xs">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主页面 -->
|
||||
<div v-else>
|
||||
<!-- 顶部标题和关闭按钮 -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="text-xl font-bold">
|
||||
购买套餐
|
||||
</div>
|
||||
<el-button circle size="small" @click="close">
|
||||
✕
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Tab 切换 -->
|
||||
<div class="flex border-b mb-6 overflow-x-auto">
|
||||
<div
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="cursor-pointer px-5 py-2 -mb-px border-b-2 transition whitespace-nowrap"
|
||||
:class="activeTab === tab.key
|
||||
? 'border-orange-500 text-orange-500 font-semibold'
|
||||
: 'border-transparent text-gray-500 hover:text-orange-500'"
|
||||
@click="activeTab = tab.key"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 移动端布局 -->
|
||||
<div v-if="isMobile" class="mobile-layout">
|
||||
<!-- 套餐卡片列表 -->
|
||||
<div class="package-list">
|
||||
<div
|
||||
v-for="pkg in currentPackages"
|
||||
:key="pkg.id"
|
||||
class="package-card"
|
||||
:class="{ selected: pkg.id === selectedId }"
|
||||
@click="selectPackage(pkg)"
|
||||
>
|
||||
<!-- 标签 -->
|
||||
<div v-if="pkg.discount" class="discount-tag">
|
||||
{{ pkg.discount }}
|
||||
</div>
|
||||
<div v-if="pkg.tag" class="tag">
|
||||
{{ pkg.tag }}
|
||||
</div>
|
||||
|
||||
<!-- 套餐信息 -->
|
||||
<div class="package-info">
|
||||
<div class="package-name">
|
||||
{{ pkg.name }}
|
||||
</div>
|
||||
<div class="package-desc">
|
||||
{{ pkg.desc }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 价格 -->
|
||||
<div class="package-price">
|
||||
<span class="price">¥{{ pkg.price }}</span>
|
||||
<span v-if="pkg.perMonth" class="per-month">
|
||||
仅¥{{ pkg.perMonth }}/月
|
||||
</span>
|
||||
<div v-if="pkg.originalPrice" class="original-price">
|
||||
原价¥{{ pkg.originalPrice }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 权益预览 -->
|
||||
<div class="benefits-preview max-h-200px overflow-y-auto">
|
||||
<div class="section-title">
|
||||
专属权益
|
||||
</div>
|
||||
<ul class="benefits-list">
|
||||
<li
|
||||
v-for="(b, index) in currentBenefits"
|
||||
:key="index"
|
||||
class="benefit-item"
|
||||
>
|
||||
<span class="dot">•</span>
|
||||
<span>
|
||||
<span class="benefit-name">{{ b.name }}</span>
|
||||
<span v-if="b.value">:{{ b.value }}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<SupportModelList />
|
||||
</div>
|
||||
|
||||
<!-- 支付区域 -->
|
||||
<div class="payment-area">
|
||||
<div v-if="false" class="agreement-text">
|
||||
登录和注册都代表同意YiXinAI的会员协议
|
||||
</div>
|
||||
<div class="payment-info">
|
||||
<div class="actual-payment">
|
||||
<span>实际支付:</span>
|
||||
<span class="price">¥{{ selectedPrice || 0 }}</span>
|
||||
</div>
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
class="view-details-btn"
|
||||
@click="toggleDetails"
|
||||
>
|
||||
了解更多
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="!selectedId || isLoading"
|
||||
:loading="isLoading"
|
||||
class="pay-button"
|
||||
@click="pay"
|
||||
>
|
||||
立即支付
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="note-text">
|
||||
可叠加购买次数,过期时间以最后订单为准。<br>
|
||||
最终解释权归YiXinAI所有。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 桌面端布局 -->
|
||||
<div v-else class="flex gap-6 desktop-layout">
|
||||
<!-- 左栏 套餐卡片 + 支付 -->
|
||||
<div class="w-[60%] flex flex-col justify-between">
|
||||
<!-- 套餐卡片列表 -->
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div
|
||||
v-for="pkg in currentPackages"
|
||||
:key="pkg.id"
|
||||
class="package-card"
|
||||
:class="{ selected: pkg.id === selectedId }"
|
||||
@click="selectPackage(pkg)"
|
||||
>
|
||||
<!-- 标签 -->
|
||||
<div v-if="pkg.discount" class="discount-tag">
|
||||
{{ pkg.discount }}
|
||||
</div>
|
||||
<div v-if="pkg.tag" class="tag">
|
||||
{{ pkg.tag }}
|
||||
</div>
|
||||
|
||||
<!-- 套餐信息 -->
|
||||
<div class="package-info">
|
||||
<div class="package-name">
|
||||
{{ pkg.name }}
|
||||
</div>
|
||||
<div class="package-desc">
|
||||
{{ pkg.desc }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 价格 -->
|
||||
<div class="package-price">
|
||||
<span class="price">¥{{ pkg.price }}</span>
|
||||
<span v-if="pkg.perMonth" class="per-month">
|
||||
仅¥{{ pkg.perMonth }}/月
|
||||
</span>
|
||||
<div v-if="pkg.originalPrice" class="original-price">
|
||||
原价¥{{ pkg.originalPrice }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 支付按钮 -->
|
||||
<div class="payment-section">
|
||||
<div v-if="false" class="agreement-text">
|
||||
登录和注册都代表同意YiXinAI的会员协议
|
||||
</div>
|
||||
<div class="payment-action">
|
||||
<div class="actual-payment">
|
||||
<span>实际支付:</span>
|
||||
<span class="price">¥{{ selectedPrice || 0 }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-button class="pay-button" text type="primary" @click="toggleDetails">
|
||||
了解更多
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:disabled="!selectedId || isLoading"
|
||||
:loading="isLoading"
|
||||
class="pay-button"
|
||||
@click="pay"
|
||||
>
|
||||
立即支付
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note-text">
|
||||
可叠加购买次数,过期时间以最后订单为准。<br>
|
||||
最终解释权归YiXinAI所有。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右栏 套餐权益 + 详情 -->
|
||||
<div class="w-[40%] flex flex-col justify-between right-panel max-h-400px overflow-y-auto">
|
||||
<div>
|
||||
<div class="section-title">
|
||||
会员权益
|
||||
</div>
|
||||
<ul class="benefits-list ">
|
||||
<li
|
||||
v-for="(b, index) in currentBenefits"
|
||||
:key="index"
|
||||
class="benefit-item"
|
||||
>
|
||||
<span class="dot">•</span>
|
||||
<span>
|
||||
<span class="benefit-name">{{ b.name }}</span>
|
||||
<span v-if="b.value">:{{ b.value }}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- 额外描述 -->
|
||||
<div v-if="activeTab === 'member'" class="extra-description ">
|
||||
<SupportModelList />
|
||||
|
||||
<!-- <div class="description-card"> -->
|
||||
<!-- <div class="title"> -->
|
||||
<!-- 前沿模型AI对话 -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="subtext"> -->
|
||||
<!-- DP-RI深度思考、精准解答 -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="subtext"> -->
|
||||
<!-- AI写作、文档对话、AI思维导图等赋能职场 -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
|
||||
<!-- <div class="description-card"> -->
|
||||
<!-- <div class="title"> -->
|
||||
<!-- AI绘图与设计能力 -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="subtext"> -->
|
||||
<!-- 视觉吸睛赋能 -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="subtext"> -->
|
||||
<!-- "AI+办公!"解锁300+工具箱会员权益 -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查看详情 -->
|
||||
<div class="view-details">
|
||||
<!-- <el-button text type="primary" @click="toggleDetails"> -->
|
||||
<!-- 查看详情 -->
|
||||
<!-- </el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.product-package-dialog {
|
||||
.el-dialog__header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.details-view {
|
||||
height: 600px;
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
|
||||
.benefits-table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
.table-header, .table-row {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
display: table-cell;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
font-weight: bold;
|
||||
background-color: #f8f8f8;
|
||||
|
||||
.table-cell {
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端样式 */
|
||||
.mobile-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
|
||||
.package-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.package-card {
|
||||
position: relative;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.selected {
|
||||
border-color: #f97316;
|
||||
background-color: #fff7ed;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.discount-tag {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 8px;
|
||||
background-color: #ef4444;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: 8px;
|
||||
background-color: #f97316;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.package-info {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.package-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.package-desc {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
}
|
||||
|
||||
.package-price {
|
||||
.price {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
.per-month {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.original-price {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.benefits-preview {
|
||||
background-color: #f9fafb;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
|
||||
.section-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.benefits-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
|
||||
.dot {
|
||||
color: #f97316;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.benefit-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view-details-btn {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.payment-area {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
padding-top: 16px;
|
||||
|
||||
.agreement-text {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.payment-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.actual-payment {
|
||||
.price {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #f97316;
|
||||
}
|
||||
}
|
||||
|
||||
.pay-button {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.note-text {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 桌面端样式 */
|
||||
.desktop-layout {
|
||||
.package-card {
|
||||
position: relative;
|
||||
width: calc(50% - 0.5rem);
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
&.selected {
|
||||
border-color: #f97316;
|
||||
background-color: #fff7ed;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.discount-tag {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 8px;
|
||||
background-color: #ef4444;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: 8px;
|
||||
background-color: #f97316;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.package-info {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.package-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.package-desc {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
}
|
||||
|
||||
.package-price {
|
||||
.price {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
.per-month {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.original-price {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.payment-section {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
|
||||
.agreement-text {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.payment-action {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.actual-payment {
|
||||
.price {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #f97316;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.note-text {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
.section-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.benefits-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
|
||||
.dot {
|
||||
color: #f97316;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.benefit-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.extra-description {
|
||||
margin-top: 24px;
|
||||
|
||||
.description-card {
|
||||
background-color: #f9fafb;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.subtext {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view-details {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.el-dialog {
|
||||
margin-top: 20px !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
.details-view {
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
|
||||
.benefits-table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
|
||||
.table-header, .table-row {
|
||||
display: table;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,202 @@
|
||||
<script lang="ts" setup>
|
||||
import { useModelStore } from '@/stores/modules/model';
|
||||
|
||||
// interface Model {
|
||||
// id: string;
|
||||
// category: string;
|
||||
// modelId: string;
|
||||
// modelName: string;
|
||||
// modelDescribe: string;
|
||||
// modelPrice: number;
|
||||
// modelType: string;
|
||||
// modelShow: string;
|
||||
// systemPrompt: string | null;
|
||||
// apiHost: string | null;
|
||||
// apiKey: string | null;
|
||||
// remark: string;
|
||||
// }
|
||||
|
||||
// 从store获取模型列表
|
||||
/*
|
||||
const modelList = ref<Model[]>([
|
||||
{
|
||||
id: '077be103-1456-a4bb-409c-3a15f04e1ad9',
|
||||
category: 'chat',
|
||||
modelId: 'DeepSeek-R1-0528',
|
||||
modelName: 'DeepSeek-R1',
|
||||
modelDescribe: '国产开源,深度思索模式,不过幻读问题比较大,同时具备思考响应链,在开源模型中永远的神!',
|
||||
modelPrice: 0,
|
||||
modelType: '1',
|
||||
modelShow: '0',
|
||||
systemPrompt: null,
|
||||
apiHost: null,
|
||||
apiKey: null,
|
||||
remark: '国产开源,深度思索模式,不过幻读问题比较大,同时具备思考响应链,在开源模型中永远的神!',
|
||||
},
|
||||
{
|
||||
id: '077be103-1456-a4bb-409c-3a15f04e1ab7',
|
||||
category: 'chat',
|
||||
modelId: 'DeepSeek-V3-0324',
|
||||
modelName: 'DeepSeek-V3',
|
||||
modelDescribe: '国产开源,简单聊天模式,对于中文文章语义体验较好,但响应速度一般',
|
||||
modelPrice: 0,
|
||||
modelType: '1',
|
||||
modelShow: '0',
|
||||
systemPrompt: null,
|
||||
apiHost: null,
|
||||
apiKey: null,
|
||||
remark: '国产开源,简单聊天模式,对于中文文章语义体验较好,但响应速度一般',
|
||||
},
|
||||
]);
|
||||
*/
|
||||
|
||||
// 实际使用时,您可以从store中获取模型列表
|
||||
// import { useModelStore } from '@/stores/model';
|
||||
const modelStore = useModelStore();
|
||||
const modelList = computed(() => modelStore.modelList);
|
||||
console.log('modelList---', modelList);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-container">
|
||||
<div class="model-header">
|
||||
支持的模型
|
||||
</div>
|
||||
<div class="model-grid">
|
||||
<div v-for="model in modelList" :key="model.id" class="model-card">
|
||||
<div class="model-card-header">
|
||||
<h3 class="model-name">
|
||||
{{ model.modelName }}
|
||||
</h3>
|
||||
<div class="model-price">
|
||||
<template v-if="model.modelPrice === 0">
|
||||
<span class="free-tag">{{ model.modelId === 'DeepSeek-R1-0528' ? '免费' : 'Vip专享' }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="price">{{ model.modelPrice }}元/次</span>
|
||||
<span class="per-token">{{ model.modelPrice * 100 }}元/百万Token</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-description">
|
||||
{{ model.modelDescribe }}
|
||||
</div>
|
||||
<div class="model-footer">
|
||||
<span class="model-id">{{ model.modelId }}</span>
|
||||
<!-- <el-tag v-if="model.category === 'chat'" size="small" type="success"> -->
|
||||
<!-- 对话 -->
|
||||
<!-- </el-tag> -->
|
||||
<!-- <el-tag v-else size="small" type="info"> -->
|
||||
<!-- 其他 -->
|
||||
<!-- </el-tag> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.model-container {
|
||||
//padding: 10px;
|
||||
max-width: 300px;
|
||||
margin: 10px 0 ;
|
||||
}
|
||||
|
||||
.model-header {
|
||||
font-size: 14px;
|
||||
margin-bottom: 24px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.model-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.model-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 10px;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.model-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.model-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.model-price {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.free-tag {
|
||||
background: #f0f9eb;
|
||||
color: #67c23a;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.price {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.per-token {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.model-description {
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.model-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.model-id {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.model-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.model-card {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user