feat: 增加支付宝在线支付、套餐订购弹窗、会员权益、支持模型展示等
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
export * from './auth';
|
||||
export * from './chat';
|
||||
export * from './model';
|
||||
export * from './pay';
|
||||
export * from './session';
|
||||
export * from './user';
|
||||
|
||||
11
Yi.Ai.Vue3/src/api/pay/index.ts
Normal file
11
Yi.Ai.Vue3/src/api/pay/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { get, post } from '@/utils/request.ts';
|
||||
|
||||
// 创建订单并发起支付
|
||||
export function createOrder(params: any) {
|
||||
return post<any>(`/pay/Order`, params).json();
|
||||
}
|
||||
|
||||
// 查询订单状态
|
||||
export function getOrderStatus(OutTradeNo: any) {
|
||||
return get<any>(`/pay/OrderStatus?OutTradeNo=${OutTradeNo}`).json();
|
||||
}
|
||||
@@ -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>
|
||||
@@ -5,6 +5,7 @@ import Popover from '@/components/Popover/index.vue';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
import { useUserStore } from '@/stores';
|
||||
import { useSessionStore } from '@/stores/modules/session';
|
||||
import { showProductPackage } from '@/utils/product-package';
|
||||
import { getUserProfilePicture, isUserVip } from '@/utils/user';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -63,6 +64,7 @@ const navItems = [
|
||||
{ name: 'apiKey', label: 'API密钥', icon: 'Key' },
|
||||
{ name: 'rechargeLog', label: '充值记录', icon: 'Document' },
|
||||
{ name: 'usageStatistics', label: '用量统计', icon: 'Histogram' },
|
||||
// { name: 'usageStatistics2', label: '用量统计2', icon: 'Histogram' },
|
||||
];
|
||||
function openDialog() {
|
||||
dialogVisible.value = true;
|
||||
@@ -163,12 +165,30 @@ function openVipGuide() {
|
||||
}
|
||||
|
||||
/* 弹出面板 结束 */
|
||||
function onProductPackage() {
|
||||
showProductPackage();
|
||||
}
|
||||
// 直接调用
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<el-button
|
||||
class="buy-btn flex items-center gap-2 px-5 py-2 font-semibold shadow-lg"
|
||||
@click="onProductPackage"
|
||||
>
|
||||
<!-- <svg -->
|
||||
<!-- xmlns="http://www.w3.org/2000/svg" -->
|
||||
<!-- class="icon-rocket w-5 h-5 animate-bounce" -->
|
||||
<!-- viewBox="0 0 24 24" -->
|
||||
<!-- fill="currentColor" -->
|
||||
<!-- > -->
|
||||
<!-- <path d="M12 2C10 5 8 8 8 11l-5 5c0 3 3 3 5 5s5-3 8-5l5-5c-3-2-6-4-8-9z" /> -->
|
||||
<!-- </svg> -->
|
||||
<span>立即购买</span>
|
||||
</el-button>
|
||||
<!-- 用户信息区域 -->
|
||||
<div class=" cursor-pointer flex flex-col text-right mr-2 leading-tight" @click="openVipGuide">
|
||||
<div class=" cursor-pointer flex flex-col text-right mr-2 leading-tight" @click="onProductPackage">
|
||||
<div class="text-sm font-semibold text-gray-800">
|
||||
{{ userStore.userInfo?.user.nick ?? '未登录用户' }}
|
||||
</div>
|
||||
@@ -264,6 +284,10 @@ function openVipGuide() {
|
||||
<template #usageStatistics>
|
||||
<usage-statistics />
|
||||
</template>
|
||||
<!-- 用量统计 -->
|
||||
<!-- <template #usageStatistics2> -->
|
||||
<!-- <usage-statistics2 /> -->
|
||||
<!-- </template> -->
|
||||
|
||||
<!-- 角色管理内容 -->
|
||||
<template #role>
|
||||
@@ -297,4 +321,31 @@ function openVipGuide() {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
.buy-btn {
|
||||
background: linear-gradient(90deg, #FFD700, #FFC107);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(255, 215, 0, 0.5);
|
||||
background: linear-gradient(90deg, #FFC107, #FFD700);
|
||||
}
|
||||
|
||||
.icon-rocket {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.animate-bounce {
|
||||
animation: bounce 1.2s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-4px); }
|
||||
}
|
||||
</style>
|
||||
|
||||
201
Yi.Ai.Vue3/src/pages/payResult/index.vue
Normal file
201
Yi.Ai.Vue3/src/pages/payResult/index.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<script setup lang="ts">
|
||||
import { ElButton, ElDivider } from 'element-plus';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { getUserInfo } from '@/api';
|
||||
import {
|
||||
SSO_CLIENT_LOGIN_AGAIN,
|
||||
SSO_SEVER_URL,
|
||||
} from '@/config/sso.ts';
|
||||
|
||||
import { useUserStore } from '@/stores';
|
||||
import { useSessionStore } from '@/stores/modules/session.ts';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
const sessionStore = useSessionStore();
|
||||
|
||||
interface PayResult {
|
||||
out_trade_no: string;
|
||||
trade_no: string;
|
||||
total_amount: string;
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
const payResult = ref<PayResult>({
|
||||
out_trade_no: '',
|
||||
trade_no: '',
|
||||
total_amount: '',
|
||||
});
|
||||
|
||||
function parseUrlParams() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const result: PayResult = {
|
||||
out_trade_no: params.get('out_trade_no') || '',
|
||||
trade_no: params.get('trade_no') || '',
|
||||
total_amount: params.get('total_amount') || '',
|
||||
};
|
||||
params.forEach((value, key) => {
|
||||
if (!(key in result))
|
||||
result[key] = value;
|
||||
});
|
||||
payResult.value = result;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
parseUrlParams();
|
||||
});
|
||||
function handleThirdPartyLogin(type: any) {
|
||||
const redirectUri = encodeURIComponent(`${window.location.origin}/chat`);
|
||||
console.log('cccc', type);
|
||||
const popup = window.open(
|
||||
`${SSO_SEVER_URL}/login?client_id=${type}&redirect_uri=${redirectUri}`,
|
||||
'SSOLogin',
|
||||
'width=1000,height=800',
|
||||
);
|
||||
|
||||
// 使用标志位防止重复执行
|
||||
let isHandled = false;
|
||||
|
||||
const messageHandler = async (event: any) => {
|
||||
if (event.origin === new URL(SSO_SEVER_URL).origin
|
||||
&& event.data.type === 'SSO_LOGIN_SUCCESS'
|
||||
&& !isHandled) {
|
||||
isHandled = true;
|
||||
try {
|
||||
// 清理监听
|
||||
window.removeEventListener('message', messageHandler);
|
||||
const { token, refreshToken } = event.data;
|
||||
userStore.setToken(token, refreshToken);
|
||||
const resUserInfo = await getUserInfo();
|
||||
userStore.setUserInfo(resUserInfo.data);
|
||||
// 关闭弹窗
|
||||
if (popup && !popup.closed) {
|
||||
popup.close();
|
||||
}
|
||||
|
||||
// 后续逻辑
|
||||
ElMessage.success('登录成功');
|
||||
userStore.closeLoginDialog();
|
||||
await sessionStore.requestSessionList(1, true);
|
||||
await router.replace('/');
|
||||
}
|
||||
catch (error) {
|
||||
console.error('登录处理失败:', error);
|
||||
ElMessage.error('登录失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 先移除旧监听,再添加新监听
|
||||
window.removeEventListener('message', messageHandler);
|
||||
window.addEventListener('message', messageHandler);
|
||||
|
||||
// 超时自动清理
|
||||
// setTimeout(() => {
|
||||
// if (!isHandled) {
|
||||
// window.removeEventListener('message', messageHandler);
|
||||
// if (popup && !popup.closed)
|
||||
// popup.close();
|
||||
// ElMessage.warning('登录超时');
|
||||
// }
|
||||
// }, 60 * 1000); // 60分钟超时关闭
|
||||
}
|
||||
|
||||
function toHome() {
|
||||
router.replace('/');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pay-result-container flex flex-col items-center justify-center p-6 min-h-screen bg-gray-50">
|
||||
<div class="card bg-white rounded-xl shadow-lg p-8 max-w-md w-full text-center">
|
||||
<!-- 成功提示 -->
|
||||
<h1 class="text-4xl font-extrabold mb-4 text-orange-500">
|
||||
🎉 恭喜!
|
||||
</h1>
|
||||
<p class="text-xl font-semibold mb-6">
|
||||
您已成为尊贵的 <span class="text-orange-500">YixinAI VIP</span>
|
||||
</p>
|
||||
|
||||
<!-- 订单信息卡片 -->
|
||||
<div class="order-info bg-gray-100 p-4 rounded-lg mb-4 text-left">
|
||||
<p class="mb-2">
|
||||
<strong>商户订单号:</strong>{{ payResult.out_trade_no }}
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
<strong>支付交易号:</strong>{{ payResult.trade_no }}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
<strong>支付金额:</strong><span class="text-red-500 font-bold">¥{{ payResult.total_amount }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 更多信息提示 -->
|
||||
<div class="mb-6 text-gray-600 text-sm">
|
||||
更多订单信息和会员详情<br>请前往 <strong>用户中心 → 充值记录</strong> 查看。<br>
|
||||
用户中心在首页右上角个人头像点击下拉菜单。
|
||||
</div>
|
||||
|
||||
<!-- 重新登录提示 -->
|
||||
<div class="mb-4 text-gray-600">
|
||||
开通 VIP 后,需要重新登录意社区生效
|
||||
</div>
|
||||
|
||||
<ElDivider content-position="center">
|
||||
<span class="text-gray-400 text-sm">操作</span>
|
||||
</ElDivider>
|
||||
|
||||
<!-- 按钮区域 -->
|
||||
<div class="flex flex-col gap-3 mt-4">
|
||||
<ElButton
|
||||
class="w-full py-3 text-lg font-medium"
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="handleThirdPartyLogin(SSO_CLIENT_LOGIN_AGAIN)"
|
||||
>
|
||||
意社区重新登录
|
||||
</ElButton>
|
||||
|
||||
<ElButton
|
||||
class="w-full py-3 text-lg font-medium"
|
||||
type="default"
|
||||
size="large"
|
||||
@click="toHome()"
|
||||
>
|
||||
返回首页
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pay-result-container {
|
||||
.card {
|
||||
transition: transform 0.2s;
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
.order-info p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.el-button {
|
||||
border-radius: 8px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.card {
|
||||
padding: 6vw;
|
||||
}
|
||||
.order-info p {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.el-button {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import { CircleCheck } from '@element-plus/icons-vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import SupportModelProducts from '@/components/SupportModelProducts/indexl.vue';
|
||||
|
||||
@@ -193,46 +192,124 @@ function openContact() {
|
||||
</p>
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-6 flex justify-center">
|
||||
<el-card
|
||||
style="width: 400px;"
|
||||
v-for="(plan, index) in pricing"
|
||||
:key="index"
|
||||
class="rounded-2xl shadow hover:shadow-lg transition-all" :class="[plan.isPopular ? 'border-2 border-blue-500' : '']"
|
||||
>
|
||||
<div class="flex flex-col items-center text-center">
|
||||
<h2 class="text-2xl font-semibold mb-2">
|
||||
{{ plan.name }}
|
||||
</h2>
|
||||
<p class="text-3xl font-bold text-blue-600 mb-2" style="color: #E8CB96">
|
||||
<span>优惠后:</span>¥{{ plan.newPrice }} <del style="color: #889F9F;font-size: 16px">¥{{ plan.price }}</del>
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 mb-4">
|
||||
{{ plan.period }}
|
||||
</p>
|
||||
<el-divider />
|
||||
<ul class="text-left space-y-2 w-full min-h-200px">
|
||||
<li v-for="(feature, i) in plan.features" :key="i" class="flex items-start gap-2">
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
<span>{{ feature }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<el-button
|
||||
class="mt-6 w-full"
|
||||
type="primary"
|
||||
size="large"
|
||||
style="background: #D7BD8D;color: white;border: #191919"
|
||||
@click="openContact"
|
||||
<div class="text-center relative">
|
||||
<h3 class="text-lg font-bold mb-3">
|
||||
请扫码加入微信交流群<br>
|
||||
获取专属客服支持
|
||||
</h3>
|
||||
<div class="mb-4 flex items-center justify-center space-x-2">
|
||||
<!-- <span class="font-semibold">站长微信账号:</span> -->
|
||||
<!-- <span id="wechat-id" class="text-blue-600 font-mono select-text">chengzilaoge520</span> -->
|
||||
<span
|
||||
v-if="false"
|
||||
class="cursor-pointer" onclick="navigator.clipboard.writeText('chengzilaoge520').then(() => { window.parent.ElMessage({
|
||||
message: '微信号已复制到剪贴板',
|
||||
type: 'success',
|
||||
duration: 2000,
|
||||
});})"
|
||||
title="点击复制"
|
||||
>
|
||||
立即订阅
|
||||
</el-button>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 opacity-70 hover:opacity-100" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v16h14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 18H8V7h11v16z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-divider class="my-16">
|
||||
<div class="flex justify-center mb-4">
|
||||
<div v-if="false">
|
||||
<h4>
|
||||
站长微信
|
||||
</h4>
|
||||
<img
|
||||
:src="wxSrc"
|
||||
class="w-50 h-70 border border-gray-200 rounded-lg shadow-md cursor-pointer hover:shadow-lg transition-transform hover:scale-105"
|
||||
onclick="document.getElementById('wechat-qrcode-fullscreen').style.display = 'flex'"
|
||||
alt="微信二维码"
|
||||
>
|
||||
</div><div>
|
||||
<h4>
|
||||
微信交流群
|
||||
</h4>
|
||||
<img
|
||||
:src="wxGroupQD"
|
||||
|
||||
class="w-50 h-70 border border-gray-200 rounded-lg shadow-md cursor-pointer hover:shadow-lg transition-transform hover:scale-105"
|
||||
onclick="document.getElementById('wx-group-qrcode-fullscreen').style.display = 'flex'"
|
||||
alt="微信二维码"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="false" class="text-sm text-gray-600">
|
||||
<p class="mb-1">
|
||||
请备注 <span class="inline-block bg-yellow-100 text-yellow-800 px-2 py-0.5 rounded text-xs">ai</span> 快速通过验证
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 全屏放大二维码 -->
|
||||
<div
|
||||
id="wechat-qrcode-fullscreen"
|
||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.8); z-index:9999; justify-content:center; align-items:center;"
|
||||
onclick="this.style.display='none'"
|
||||
>
|
||||
<img
|
||||
:src="wxSrc"
|
||||
style="max-width:90%; max-height:90%; border:8px solid white; border-radius:16px; box-shadow:0 0 40px rgba(255,255,255,0.2);"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
id="wx-group-qrcode-fullscreen"
|
||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.8); z-index:9999; justify-content:center; align-items:center;"
|
||||
onclick="this.style.display='none'"
|
||||
>
|
||||
<img
|
||||
:src="wxGroupQD"
|
||||
|
||||
style="max-width:90%; max-height:90%; border:8px solid white; border-radius:16px; box-shadow:0 0 40px rgba(255,255,255,0.2);"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <el-card -->
|
||||
<!-- style="width: 400px;" -->
|
||||
<!-- v-for="(plan, index) in pricing" -->
|
||||
<!-- :key="index" -->
|
||||
<!-- class="rounded-2xl shadow hover:shadow-lg transition-all" :class="[plan.isPopular ? 'border-2 border-blue-500' : '']" -->
|
||||
<!-- > -->
|
||||
<!-- <div class="flex flex-col items-center text-center"> -->
|
||||
<!-- <h2 class="text-2xl font-semibold mb-2"> -->
|
||||
<!-- {{ plan.name }} -->
|
||||
<!-- </h2> -->
|
||||
<!-- <p class="text-3xl font-bold text-blue-600 mb-2" style="color: #E8CB96"> -->
|
||||
<!-- <span>优惠后:</span>¥{{ plan.newPrice }} <del style="color: #889F9F;font-size: 16px">¥{{ plan.price }}</del> -->
|
||||
<!-- </p> -->
|
||||
<!-- <p class="text-sm text-gray-500 mb-4"> -->
|
||||
<!-- {{ plan.period }} -->
|
||||
<!-- </p> -->
|
||||
<!-- <el-divider /> -->
|
||||
<!-- <ul class="text-left space-y-2 w-full min-h-200px"> -->
|
||||
<!-- <li v-for="(feature, i) in plan.features" :key="i" class="flex items-start gap-2"> -->
|
||||
<!-- <el-icon><CircleCheck /></el-icon> -->
|
||||
<!-- <span>{{ feature }}</span> -->
|
||||
<!-- </li> -->
|
||||
<!-- </ul> -->
|
||||
<!-- <el-button -->
|
||||
<!-- class="mt-6 w-full" -->
|
||||
<!-- type="primary" -->
|
||||
<!-- size="large" -->
|
||||
<!-- style="background: #D7BD8D;color: white;border: #191919" -->
|
||||
<!-- @click="openContact" -->
|
||||
<!-- > -->
|
||||
<!-- 立即订阅 -->
|
||||
<!-- </el-button> -->
|
||||
<!-- </div> -->
|
||||
<!-- </el-card> -->
|
||||
</div>
|
||||
<!-- <SupportModelList /> -->
|
||||
<el-divider v-if="false" class="my-16">
|
||||
充值流程说明
|
||||
</el-divider>
|
||||
<el-card class="max-w-2xl mx-auto shadow-md rounded-2xl">
|
||||
<el-card v-if="false" class="max-w-2xl mx-auto shadow-md rounded-2xl">
|
||||
<h3 class="text-xl font-semibold mb-4">
|
||||
如何充值 VIP
|
||||
</h3>
|
||||
@@ -270,7 +347,7 @@ function openContact() {
|
||||
<el-divider class="my-16">
|
||||
加入群聊
|
||||
</el-divider>
|
||||
<el-collapse class="max-w-3xl mx-auto flex justify-center" accordion >
|
||||
<el-collapse class="max-w-3xl mx-auto flex justify-center" accordion>
|
||||
<el-image
|
||||
style="width: 500px; height: auto;"
|
||||
:src="wxGroupQD"
|
||||
|
||||
@@ -41,6 +41,19 @@ export const layoutRouter: RouteRecordRaw[] = [
|
||||
isDefaultChat: false, // 根据实际情况设置
|
||||
layout: 'blankPage', // 如果需要自定义布局
|
||||
},
|
||||
|
||||
},
|
||||
{
|
||||
path: '/pay-result',
|
||||
name: 'payResult',
|
||||
component: () => import('@/pages/payResult/index.vue'),
|
||||
meta: {
|
||||
title: '支付结果',
|
||||
keepAlive: true, // 如果需要缓存
|
||||
isDefaultChat: false, // 根据实际情况设置
|
||||
layout: 'blankPage', // 如果需要自定义布局
|
||||
},
|
||||
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
20
Yi.Ai.Vue3/src/utils/product-package.ts
Normal file
20
Yi.Ai.Vue3/src/utils/product-package.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createApp, h } from 'vue';
|
||||
import ProductPackage from '@/components/ProductPackage/index.vue';
|
||||
|
||||
export function showProductPackage() {
|
||||
const div = document.createElement('div');
|
||||
document.body.appendChild(div);
|
||||
|
||||
const app = createApp({
|
||||
render() {
|
||||
return h(ProductPackage, {
|
||||
onClose: () => {
|
||||
app.unmount();
|
||||
div.remove();
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
app.mount(div);
|
||||
}
|
||||
2
Yi.Ai.Vue3/types/components.d.ts
vendored
2
Yi.Ai.Vue3/types/components.d.ts
vendored
@@ -44,11 +44,13 @@ declare module 'vue' {
|
||||
ModelSelect: typeof import('./../src/components/ModelSelect/index.vue')['default']
|
||||
NavDialog: typeof import('./../src/components/userPersonalCenter/NavDialog.vue')['default']
|
||||
Popover: typeof import('./../src/components/Popover/index.vue')['default']
|
||||
ProductPackage: typeof import('./../src/components/ProductPackage/index.vue')['default']
|
||||
QrCodeLogin: typeof import('./../src/components/LoginDialog/components/QrCodeLogin/index.vue')['default']
|
||||
RechargeLog: typeof import('./../src/components/userPersonalCenter/components/RechargeLog.vue')['default']
|
||||
RegistrationForm: typeof import('./../src/components/LoginDialog/components/FormLogin/RegistrationForm.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SupportModelList: typeof import('./../src/components/userPersonalCenter/components/SupportModelList.vue')['default']
|
||||
SvgIcon: typeof import('./../src/components/SvgIcon/index.vue')['default']
|
||||
UsageStatistics: typeof import('./../src/components/userPersonalCenter/components/UsageStatistics.vue')['default']
|
||||
UserManagement: typeof import('./../src/components/userPersonalCenter/components/UserManagement.vue')['default']
|
||||
|
||||
Reference in New Issue
Block a user