fix: 前端页面架构重构优化
This commit is contained in:
@@ -3,7 +3,6 @@ import type { GoodsItem } from '@/api/pay';
|
||||
import { CircleCheck, Loading } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { createOrder, getOrderStatus } from '@/api';
|
||||
import { getGoodsList, GoodsCategoryType } from '@/api/pay';
|
||||
import SupportModelList from '@/components/userPersonalCenter/components/SupportModelList.vue';
|
||||
@@ -305,8 +304,6 @@ async function checkPaymentStatus(outTradeNo: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function toggleDetails() {
|
||||
showDetails.value = !showDetails.value;
|
||||
}
|
||||
@@ -322,7 +319,8 @@ function onClose() {
|
||||
|
||||
function goToActivation() {
|
||||
close();
|
||||
userStore.openUserCenter('activationCode');
|
||||
// 使用 window.location 进行跳转,避免 router 注入问题
|
||||
window.location.href = '/console/activation';
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import type { DailyTaskItem, DailyTaskStatusOutput } from '@/api/dailyTask/types';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getTodayTaskStatus, claimTaskReward } from '@/api/dailyTask';
|
||||
import type { DailyTaskStatusOutput, DailyTaskItem } from '@/api/dailyTask/types';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { claimTaskReward, getTodayTaskStatus } from '@/api/dailyTask';
|
||||
|
||||
const taskData = ref<DailyTaskStatusOutput | null>(null);
|
||||
const loading = ref(false);
|
||||
@@ -17,15 +17,18 @@ async function fetchTaskStatus() {
|
||||
try {
|
||||
const res = await getTodayTaskStatus();
|
||||
taskData.value = res.data;
|
||||
} catch (error: any) {
|
||||
}
|
||||
catch (error: any) {
|
||||
ElMessage.error(error?.message || '获取任务状态失败');
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleClaim(task: DailyTaskItem) {
|
||||
if (task.status !== 1) return;
|
||||
if (task.status !== 1)
|
||||
return;
|
||||
|
||||
claiming.value[task.level] = true;
|
||||
try {
|
||||
@@ -34,9 +37,11 @@ async function handleClaim(task: DailyTaskItem) {
|
||||
|
||||
// 刷新任务状态
|
||||
await fetchTaskStatus();
|
||||
} catch (error: any) {
|
||||
}
|
||||
catch (error: any) {
|
||||
ElMessage.error(error?.message || '领取奖励失败');
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
claiming.value[task.level] = false;
|
||||
}
|
||||
}
|
||||
@@ -76,8 +81,10 @@ function getButtonClass(task: DailyTaskItem): string {
|
||||
|
||||
// 获取进度条颜色
|
||||
function getProgressColor(task: DailyTaskItem): string {
|
||||
if (task.status === 2) return '#FFD700'; // 已完成:金色
|
||||
if (task.status === 1) return '#67C23A'; // 可领取:绿色
|
||||
if (task.status === 2)
|
||||
return '#FFD700'; // 已完成:金色
|
||||
if (task.status === 1)
|
||||
return '#67C23A'; // 可领取:绿色
|
||||
return '#409EFF'; // 进行中:蓝色
|
||||
}
|
||||
</script>
|
||||
@@ -86,15 +93,21 @@ function getProgressColor(task: DailyTaskItem): string {
|
||||
<div v-loading="loading" class="daily-task-container">
|
||||
<div class="task-header">
|
||||
<h2>每日任务</h2>
|
||||
<p class="task-desc">完成每日任务,领取额外尊享包 Token 奖励,可累加重复</p>
|
||||
<p class="task-desc">
|
||||
完成每日任务,领取额外尊享包 Token 奖励,可累加重复
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="taskData" class="task-content">
|
||||
<!-- 今日消耗统计 -->
|
||||
<div class="consumption-card">
|
||||
<div class="consumption-icon">🔥</div>
|
||||
<div class="consumption-icon">
|
||||
🔥
|
||||
</div>
|
||||
<div class="consumption-info">
|
||||
<div class="consumption-label">今日尊享包消耗</div>
|
||||
<div class="consumption-label">
|
||||
今日尊享包消耗
|
||||
</div>
|
||||
<div class="consumption-value">
|
||||
{{ formatTokenDisplay(taskData.todayConsumedTokens) }} Tokens
|
||||
</div>
|
||||
@@ -109,7 +122,7 @@ function getProgressColor(task: DailyTaskItem): string {
|
||||
class="task-item"
|
||||
:class="{
|
||||
'task-completed': task.status === 2,
|
||||
'task-claimable': task.status === 1
|
||||
'task-claimable': task.status === 1,
|
||||
}"
|
||||
>
|
||||
<div class="task-icon">
|
||||
@@ -187,7 +200,6 @@ function getProgressColor(task: DailyTaskItem): string {
|
||||
|
||||
<style scoped>
|
||||
.daily-task-container {
|
||||
padding: 20px;
|
||||
min-height: 400px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -83,10 +83,9 @@ onMounted(() => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.premium-service {
|
||||
padding: 24px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);
|
||||
//background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);
|
||||
|
||||
// 美化滚动条
|
||||
&::-webkit-scrollbar {
|
||||
@@ -127,7 +126,6 @@ onMounted(() => {
|
||||
/* 响应式布局 */
|
||||
@media (max-width: 768px) {
|
||||
.premium-service {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.usage-list-wrapper {
|
||||
|
||||
@@ -599,18 +599,12 @@ onBeforeUnmount(() => {
|
||||
|
||||
<style scoped>
|
||||
.usage-statistics {
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.usage-statistics:hover {
|
||||
box-shadow: 0 6px 30px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.usage-statistics.fullscreen-mode {
|
||||
@@ -620,7 +614,6 @@ onBeforeUnmount(() => {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 2000;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
padding: 30px;
|
||||
overflow-y: auto;
|
||||
border-radius: 0;
|
||||
@@ -632,7 +625,6 @@ onBeforeUnmount(() => {
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
@@ -656,7 +648,6 @@ onBeforeUnmount(() => {
|
||||
|
||||
.option-label {
|
||||
text-decoration: line-through;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -671,7 +662,6 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
&.disabled-icon {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,14 +700,10 @@ onBeforeUnmount(() => {
|
||||
.chart-card {
|
||||
margin-bottom: 30px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.chart-card:hover {
|
||||
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
|
||||
@@ -303,7 +303,6 @@ function bindWechat() {
|
||||
|
||||
<style scoped>
|
||||
.user-profile {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import Header from '@/layouts/components/Header/index.vue';
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--color-gray-100);
|
||||
background: var(--color-white);
|
||||
.layout-header {
|
||||
padding: 0;
|
||||
border-bottom: var(--header-border) ;
|
||||
|
||||
@@ -153,7 +153,6 @@ function toggleSidebar() {
|
||||
<div
|
||||
v-else
|
||||
class="header-content-collapsed flex items-center justify-center hover:cursor-pointer"
|
||||
@click="handleLogoClick"
|
||||
>
|
||||
<el-icon size="20">
|
||||
<ChatLineSquare />
|
||||
@@ -188,7 +187,7 @@ function toggleSidebar() {
|
||||
class="creat-chat-btn"
|
||||
:class="{
|
||||
'creat-chat-btn-collapsed': isCollapsed,
|
||||
'is-disabled': isNewChatState
|
||||
'is-disabled': isNewChatState,
|
||||
}"
|
||||
@click="!isNewChatState && handleCreatChat()"
|
||||
>
|
||||
@@ -306,7 +305,7 @@ function toggleSidebar() {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
background-color: var(--sidebar-background-color, #f9fafb);
|
||||
//background-color: var(--sidebar-background-color, #f9fafb);
|
||||
|
||||
// 展开状态 - 240px
|
||||
&:not(.aside-collapsed) {
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useAnnouncementStore } from '@/stores';
|
||||
|
||||
const announcementStore = useAnnouncementStore();
|
||||
const { announcements } = storeToRefs(announcementStore);
|
||||
|
||||
// 计算未读公告数量(系统公告数量)
|
||||
const unreadCount = computed(() => {
|
||||
if (!Array.isArray(announcements.value))
|
||||
return 0;
|
||||
return announcements.value.filter(a => a.type === 'System').length;
|
||||
});
|
||||
|
||||
// 打开公告弹窗
|
||||
function openAnnouncement() {
|
||||
announcementStore.openDialog();
|
||||
}
|
||||
@@ -20,23 +10,17 @@ function openAnnouncement() {
|
||||
|
||||
<template>
|
||||
<div class="announcement-btn-container" data-tour="announcement-btn">
|
||||
<el-badge
|
||||
is-dot
|
||||
class="announcement-badge"
|
||||
<div
|
||||
class="announcement-btn"
|
||||
title="查看公告"
|
||||
@click="openAnnouncement"
|
||||
>
|
||||
<!-- :value="unreadCount" -->
|
||||
<!-- :hidden="unreadCount === 0" -->
|
||||
<!-- :max="99" -->
|
||||
<div
|
||||
class="announcement-btn"
|
||||
title="查看公告"
|
||||
@click="openAnnouncement"
|
||||
>
|
||||
<!-- PC端显示文字 -->
|
||||
<span class="pc-text">公告</span>
|
||||
<!-- 移动端显示图标 -->
|
||||
<!-- PC端显示文字 -->
|
||||
<span class="pc-text">公告</span>
|
||||
|
||||
<!-- 移动端显示图标 -->
|
||||
<div class="mobile-icon">
|
||||
<svg
|
||||
class="mobile-icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
@@ -51,62 +35,142 @@ function openAnnouncement() {
|
||||
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
|
||||
</svg>
|
||||
</div>
|
||||
</el-badge>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style scoped>
|
||||
.announcement-btn-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.announcement-badge {
|
||||
:deep(.el-badge__content) {
|
||||
background-color: #f56c6c;
|
||||
border: none;
|
||||
}
|
||||
.announcement-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.announcement-btn:hover {
|
||||
color: #66b1ff;
|
||||
transform: translateY(-1px);
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
/* PC端文字样式 */
|
||||
.pc-text {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
line-height: 1.2;
|
||||
padding: 2px 8px 2px 0;
|
||||
}
|
||||
|
||||
/* 红点样式 - 缩小到一半 */
|
||||
.pc-text::after,
|
||||
.mobile-icon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 6px; /* 缩小到6px */
|
||||
height: 6px; /* 缩小到6px */
|
||||
background-color: #f56c6c;
|
||||
border-radius: 50%;
|
||||
border: 1.5px solid #fff; /* 边框也相应缩小 */
|
||||
box-shadow: 0 1px 2px rgba(245, 108, 108, 0.3);
|
||||
animation: pulse 1.8s infinite;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* PC端红点位置 - 调整位置使红点正好与"告"字相交 */
|
||||
.pc-text::after {
|
||||
top: -4px; /* 向上移动,与文字相交 */
|
||||
right: -4px; /* 向右移动,与文字相交 */
|
||||
}
|
||||
|
||||
/* 为小红点添加微小的光晕效果 */
|
||||
.pc-text::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
width: 10px; /* 光晕也相应缩小 */
|
||||
height: 10px; /* 光晕也相应缩小 */
|
||||
background-color: rgba(245, 108, 108, 0.2);
|
||||
border-radius: 50%;
|
||||
animation: glow 2s infinite;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.3;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端图标样式 */
|
||||
.mobile-icon {
|
||||
display: none;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* 移动端图标内的红点位置 */
|
||||
.mobile-icon::after {
|
||||
top: -2px; /* 位置微调 */
|
||||
right: -2px; /* 位置微调 */
|
||||
width: 5px; /* 移动端红点更小一点 */
|
||||
height: 5px; /* 移动端红点更小一点 */
|
||||
}
|
||||
|
||||
/* 呼吸动画效果 - 调整为更微妙的动画 */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.9);
|
||||
box-shadow: 0 1px 2px rgba(245, 108, 108, 0.2);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 2px 4px rgba(245, 108, 108, 0.4);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.9);
|
||||
box-shadow: 0 1px 2px rgba(245, 108, 108, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端显示图标,隐藏文字 */
|
||||
@media (max-width: 768px) {
|
||||
.pc-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.announcement-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #66b1ff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
// PC端显示文字,隐藏图标
|
||||
.pc-text {
|
||||
display: inline;
|
||||
margin: 0 12px;
|
||||
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: none;
|
||||
}
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端显示图标,隐藏文字
|
||||
@media (max-width: 768px) {
|
||||
.announcement-btn-container {
|
||||
.announcement-btn {
|
||||
.pc-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
/* 响应式调整 */
|
||||
@media (min-width: 769px) {
|
||||
.mobile-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
<!-- 头像 -->
|
||||
<script setup lang="ts">
|
||||
import { ChatLineRound } from '@element-plus/icons-vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import Popover from '@/components/Popover/index.vue';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
import { useGuideTour } from '@/hooks/useGuideTour';
|
||||
import { useAnnouncementStore, useGuideTourStore, useUserStore } from '@/stores';
|
||||
import { useUserStore } from '@/stores';
|
||||
import { useSessionStore } from '@/stores/modules/session';
|
||||
import { getUserProfilePicture, isUserVip } from '@/utils/user';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const sessionStore = useSessionStore();
|
||||
const guideTourStore = useGuideTourStore();
|
||||
const announcementStore = useAnnouncementStore();
|
||||
const { startUserCenterTour } = useGuideTour();
|
||||
const { startHeaderTour } = useGuideTour();
|
||||
|
||||
/* 弹出面板 开始 */
|
||||
const popoverStyle = ref({
|
||||
@@ -29,41 +25,17 @@ const popoverRef = ref();
|
||||
|
||||
// 弹出面板内容
|
||||
const popoverList = ref([
|
||||
|
||||
// {
|
||||
// key: '5',
|
||||
// title: '控制台',
|
||||
// icon: 'settings-4-fill',
|
||||
// },
|
||||
// {
|
||||
// key: '3',
|
||||
// divider: true,
|
||||
// },
|
||||
// {
|
||||
// key: '7',
|
||||
// title: '公告',
|
||||
// icon: 'notification-fill',
|
||||
// },
|
||||
// {
|
||||
// key: '8',
|
||||
// title: '模型库',
|
||||
// icon: 'apps-fill',
|
||||
// },
|
||||
// {
|
||||
// key: '9',
|
||||
// title: '文档',
|
||||
// icon: 'book-fill',
|
||||
// },
|
||||
//
|
||||
{
|
||||
key: '1',
|
||||
title: '用户信息',
|
||||
icon: 'settings-4-fill',
|
||||
},
|
||||
// 待定
|
||||
// {
|
||||
// key: '6',
|
||||
// title: '新手引导',
|
||||
// icon: 'dashboard-fill',
|
||||
// },
|
||||
// {
|
||||
// key: '3',
|
||||
// divider: true,
|
||||
// },
|
||||
{
|
||||
key: '4',
|
||||
title: '退出登录',
|
||||
@@ -71,87 +43,20 @@ const popoverList = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
const rechargeLogRef = ref();
|
||||
const activeNav = ref('user');
|
||||
|
||||
// ============ 邀请码分享功能 ============
|
||||
/** 从 URL 获取的邀请码 */
|
||||
const externalInviteCode = ref<string>('');
|
||||
|
||||
const navItems = [
|
||||
{ name: 'user', label: '用户信息', icon: 'User' },
|
||||
// { name: 'role', label: '角色管理', icon: 'Avatar' },
|
||||
// { name: 'permission', label: '权限管理', icon: 'Key' },
|
||||
// { name: 'userInfo', label: '用户信息', icon: 'User' },
|
||||
{ name: 'apiKey', label: 'API密钥', icon: 'Key' },
|
||||
|
||||
{ name: 'rechargeLog', label: '充值记录', icon: 'Document' },
|
||||
{ name: 'usageStatistics', label: '用量统计', icon: 'Histogram' },
|
||||
{ name: 'premiumService', label: '尊享服务', icon: 'ColdDrink' },
|
||||
{ name: 'dailyTask', label: '每日任务(限时)', icon: 'Trophy' },
|
||||
{ name: 'cardFlip', label: '每周邀请(限时)', icon: 'Present' },
|
||||
// { name: 'usageStatistics2', label: '用量统计2', icon: 'Histogram' },
|
||||
{ name: 'activationCode', label: '激活码兑换', icon: 'MagicStick' },
|
||||
];
|
||||
function openDialog() {
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
function handleConfirm(activeNav: string) {
|
||||
ElMessage.success('操作成功');
|
||||
}
|
||||
|
||||
// 导航切换
|
||||
function handleNavChange(nav: string) {
|
||||
activeNav.value = nav;
|
||||
// 同步更新 store 中的 tab 状态,防止下次通过 store 打开同一 tab 时因值未变而不触发 watch
|
||||
if (userStore.userCenterActiveTab !== nav) {
|
||||
userStore.userCenterActiveTab = nav;
|
||||
}
|
||||
}
|
||||
|
||||
// 联系售后
|
||||
function handleContactSupport() {
|
||||
rechargeLogRef.value?.contactCustomerService();
|
||||
}
|
||||
const { startHeaderTour } = useGuideTour();
|
||||
|
||||
// 开始引导教程
|
||||
function handleStartTutorial() {
|
||||
startHeaderTour();
|
||||
}
|
||||
|
||||
// 点击
|
||||
function handleClick(item: any) {
|
||||
switch (item.key) {
|
||||
case '1':
|
||||
ElMessage.warning('暂未开放');
|
||||
break;
|
||||
case '2':
|
||||
ElMessage.warning('暂未开放');
|
||||
break;
|
||||
case '5':
|
||||
// 打开控制台
|
||||
popoverRef.value?.hide?.();
|
||||
router.push('/console');
|
||||
router.push('/console/user');
|
||||
break;
|
||||
case '6':
|
||||
handleStartTutorial();
|
||||
break;
|
||||
case '7':
|
||||
// 打开公告
|
||||
popoverRef.value?.hide?.();
|
||||
announcementStore.openDialog();
|
||||
break;
|
||||
case '8':
|
||||
// 打开模型库
|
||||
popoverRef.value?.hide?.();
|
||||
router.push('/model-library');
|
||||
break;
|
||||
case '9':
|
||||
// 打开文档
|
||||
popoverRef.value?.hide?.();
|
||||
window.open('https://ccnetcore.com/article/3a1bc4d1-6a7d-751d-91cc-2817eb2ddcde', '_blank');
|
||||
break;
|
||||
case '4':
|
||||
popoverRef.value?.hide?.();
|
||||
ElMessageBox.confirm('退出登录不会丢失任何数据,你仍可以登录此账号。', '确认退出登录?', {
|
||||
@@ -175,10 +80,7 @@ function handleClick(item: any) {
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// ElMessage({
|
||||
// type: 'info',
|
||||
// message: '取消',
|
||||
// });
|
||||
// 取消退出,不执行任何操作
|
||||
});
|
||||
break;
|
||||
default:
|
||||
@@ -222,124 +124,15 @@ function openVipGuide() {
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 点击右上角关闭或“关闭”按钮,不执行任何操作
|
||||
// 点击右上角关闭或"关闭"按钮,不执行任何操作
|
||||
});
|
||||
}
|
||||
|
||||
// ============ 监听对话框打开事件,切换到邀请码标签页 ============
|
||||
watch(dialogVisible, (newVal) => {
|
||||
if (newVal && externalInviteCode.value) {
|
||||
// 对话框打开后,切换标签页(已通过 :default-active 绑定,会自动响应)
|
||||
// console.log('[Avatar] watch: 对话框已打开,切换到 cardFlip 标签页');
|
||||
nextTick(() => {
|
||||
activeNav.value = 'cardFlip';
|
||||
// console.log('[Avatar] watch: 已设置 activeNav 为', activeNav.value);
|
||||
});
|
||||
}
|
||||
|
||||
// 对话框关闭时,清除邀请码状态和 URL 参数
|
||||
if (!newVal && externalInviteCode.value) {
|
||||
// console.log('[Avatar] watch: 对话框关闭,清除邀请码状态');
|
||||
externalInviteCode.value = '';
|
||||
|
||||
// 清除 URL 中的 inviteCode 参数
|
||||
const url = new URL(window.location.href);
|
||||
if (url.searchParams.has('inviteCode')) {
|
||||
url.searchParams.delete('inviteCode');
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
// console.log('[Avatar] watch: 已清除 URL 中的 inviteCode 参数');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ============ 监听 URL 参数,实现邀请码快捷分享 ============
|
||||
onMounted(() => {
|
||||
// 获取 URL 查询参数
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const inviteCode = urlParams.get('inviteCode');
|
||||
|
||||
if (inviteCode && inviteCode.trim()) {
|
||||
// console.log('[Avatar] onMounted: 检测到邀请码', inviteCode);
|
||||
|
||||
// 保存邀请码
|
||||
externalInviteCode.value = inviteCode.trim();
|
||||
|
||||
// 先设置标签页为 cardFlip
|
||||
activeNav.value = 'cardFlip';
|
||||
// console.log('[Avatar] onMounted: 设置 activeNav 为', activeNav.value);
|
||||
|
||||
// 延迟打开对话框,确保状态已更新
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
// console.log('[Avatar] onMounted: 打开用户中心对话框');
|
||||
dialogVisible.value = true;
|
||||
}, 200);
|
||||
});
|
||||
|
||||
// 注意:不立即清除 URL 参数,保留给登录后使用
|
||||
// URL 参数会在对话框关闭时清除
|
||||
}
|
||||
});
|
||||
|
||||
// ============ 监听引导状态,自动打开用户中心并开始引导 ============
|
||||
watch(() => guideTourStore.shouldStartUserCenterTour, (shouldStart) => {
|
||||
if (shouldStart) {
|
||||
// 清除触发标记
|
||||
guideTourStore.clearUserCenterTourTrigger();
|
||||
|
||||
// 注册导航切换回调
|
||||
guideTourStore.setUserCenterNavChangeCallback((nav: string) => {
|
||||
activeNav.value = nav;
|
||||
});
|
||||
|
||||
// 注册关闭弹窗回调
|
||||
guideTourStore.setUserCenterCloseCallback(() => {
|
||||
dialogVisible.value = false;
|
||||
});
|
||||
|
||||
// 打开用户中心弹窗
|
||||
nextTick(() => {
|
||||
dialogVisible.value = true;
|
||||
|
||||
// 等待弹窗打开后开始引导
|
||||
setTimeout(() => {
|
||||
startUserCenterTour();
|
||||
}, 600);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ============ 监听 Store 状态,控制用户中心弹窗 (新增) ============
|
||||
watch(() => userStore.isUserCenterVisible, (val) => {
|
||||
dialogVisible.value = val;
|
||||
if (val && userStore.userCenterActiveTab) {
|
||||
activeNav.value = userStore.userCenterActiveTab;
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => userStore.userCenterActiveTab, (val) => {
|
||||
if (val) {
|
||||
activeNav.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听本地 dialogVisible 变化,同步回 Store(可选,为了保持一致性)
|
||||
watch(dialogVisible, (val) => {
|
||||
if (!val) {
|
||||
userStore.closeUserCenter();
|
||||
}
|
||||
});
|
||||
|
||||
// ============ 暴露方法供外部调用 ============
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2 ">
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- 用户信息区域 -->
|
||||
<div class="user-info-display cursor-pointer flex flex-col text-right mr-2 leading-tight" @click="openDialog">
|
||||
<div class="user-info-display cursor-pointer flex flex-col text-right mr-2 leading-tight">
|
||||
<div class="text-sm font-semibold text-gray-800">
|
||||
{{ userStore.userInfo?.user.nick ?? '未登录用户' }}
|
||||
</div>
|
||||
@@ -356,6 +149,7 @@ defineExpose({
|
||||
<span
|
||||
v-else
|
||||
class="inline-block px-2 py-0.5 text-xs text-gray-600 bg-gray-100 rounded-full cursor-pointer hover:bg-yellow-50 hover:text-yellow-700 transition"
|
||||
@click="openVipGuide"
|
||||
>
|
||||
普通用户
|
||||
</span>
|
||||
@@ -363,7 +157,7 @@ defineExpose({
|
||||
</div>
|
||||
|
||||
<!-- 头像区域 -->
|
||||
<div class="avatar-container" data-tour="user-avatar">
|
||||
<div class="avatar-container">
|
||||
<Popover
|
||||
ref="popoverRef"
|
||||
placement="bottom-end"
|
||||
@@ -395,6 +189,7 @@ defineExpose({
|
||||
<span
|
||||
v-else
|
||||
class="inline-block px-2 py-0.5 text-xs text-gray-600 bg-gray-100 rounded-full cursor-pointer hover:bg-yellow-50 hover:text-yellow-700 transition"
|
||||
@click="openVipGuide"
|
||||
>
|
||||
普通用户
|
||||
</span>
|
||||
@@ -405,7 +200,6 @@ defineExpose({
|
||||
|
||||
<div v-for="item in popoverList" :key="item.key" class="popover-content-box-items h-full">
|
||||
<div
|
||||
v-if="!item.divider"
|
||||
class="popover-content-box-item flex items-center h-full gap-8px p-8px pl-10px pr-12px rounded-lg hover:cursor-pointer hover:bg-[rgba(0,0,0,.04)]"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
@@ -414,73 +208,10 @@ defineExpose({
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.divider" class="divder h-1px bg-gray-200 my-4px" />
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
<nav-dialog
|
||||
v-model="dialogVisible"
|
||||
title="控制台"
|
||||
:nav-items="navItems"
|
||||
:default-active="activeNav"
|
||||
@confirm="handleConfirm"
|
||||
@nav-change="handleNavChange"
|
||||
>
|
||||
<template #extra-actions>
|
||||
<el-tooltip v-if="isUserVip() && activeNav === 'rechargeLog'" content="联系售后" placement="bottom">
|
||||
<el-button circle plain size="small" @click="handleContactSupport">
|
||||
<el-icon color="#07c160">
|
||||
<ChatLineRound />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- 用户管理内容 -->
|
||||
<template #user>
|
||||
<user-management />
|
||||
</template>
|
||||
<!-- 用量统计 -->
|
||||
<template #usageStatistics>
|
||||
<usage-statistics />
|
||||
</template>
|
||||
<!-- 尊享服务 -->
|
||||
<template #premiumService>
|
||||
<premium-service />
|
||||
</template>
|
||||
<!-- 用量统计 -->
|
||||
<!-- <template #usageStatistics2> -->
|
||||
<!-- <usage-statistics2 /> -->
|
||||
<!-- </template> -->
|
||||
|
||||
<!-- 角色管理内容 -->
|
||||
<template #role>
|
||||
<!-- < /> -->
|
||||
</template>
|
||||
|
||||
<!-- 权限管理内容 -->
|
||||
<template #permission>
|
||||
<!-- <permission-management /> -->
|
||||
</template>
|
||||
|
||||
<template #apiKey>
|
||||
<APIKeyManagement />
|
||||
</template>
|
||||
<template #activationCode>
|
||||
<activation-code />
|
||||
</template>
|
||||
<template #dailyTask>
|
||||
<daily-task />
|
||||
</template>
|
||||
<template #cardFlip>
|
||||
<card-flip-activity :external-invite-code="externalInviteCode" />
|
||||
</template>
|
||||
<template #rechargeLog>
|
||||
<recharge-log ref="rechargeLogRef" />
|
||||
</template>
|
||||
</nav-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,131 +1,3 @@
|
||||
<!--
|
||||
<!– Header 头部 –>
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import logo from '@/assets/images/logo.png';
|
||||
import { useUserStore } from '@/stores';
|
||||
import AiTutorialBtn from './components/AiTutorialBtn.vue';
|
||||
import AnnouncementBtn from './components/AnnouncementBtn.vue';
|
||||
import Avatar from './components/Avatar.vue';
|
||||
import BuyBtn from './components/BuyBtn.vue';
|
||||
import ConsoleBtn from './components/ConsoleBtn.vue';
|
||||
import LoginBtn from './components/LoginBtn.vue';
|
||||
import ModelLibraryBtn from './components/ModelLibraryBtn.vue';
|
||||
import StartChatBtn from './components/StartChatBtn.vue';
|
||||
import ThemeBtn from './components/ThemeBtn.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 打开控制台
|
||||
function handleOpenConsole() {
|
||||
router.push('/console');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="header-container">
|
||||
<div class="header-box">
|
||||
<!– 左侧logo和品牌区域 –>
|
||||
<div class="left-section">
|
||||
<div class="brand-container">
|
||||
<el-image :src="logo" alt="logo" fit="contain" class="logo-img" />
|
||||
<span class="brand-text">意心AI</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!– 右侧功能按钮区域 –>
|
||||
<div class="right-section">
|
||||
<StartChatBtn />
|
||||
<AnnouncementBtn />
|
||||
<ModelLibraryBtn />
|
||||
<AiTutorialBtn />
|
||||
<ConsoleBtn @open-console="handleOpenConsole" />
|
||||
<BuyBtn v-show="userStore.userInfo" />
|
||||
<ThemeBtn />
|
||||
<LoginBtn v-show="!userStore.userInfo" />
|
||||
<Avatar v-show="userStore.userInfo" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.header-container {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
height: var(--header-container-default-height, 60px);
|
||||
|
||||
.header-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 16px;
|
||||
background: var(--header-bg-color, #ffffff);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
// 左侧品牌区域
|
||||
.left-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: fit-content;
|
||||
flex-shrink: 0;
|
||||
|
||||
.brand-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.logo-img {
|
||||
width: 36px; // 优化为更合适的大小
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.brand-text {
|
||||
font-size: 22px; // 减小字体大小
|
||||
font-weight: bold;
|
||||
color: var(--brand-color, #000000);
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.5px;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
//color: var(--brand-hover-color, #40a9ff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧功能区域
|
||||
.right-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px; // 优化按钮间距
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
|
||||
// 统一按钮样式
|
||||
:deep(.menu-button) {
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
-->
|
||||
<!-- Header 头部 -->
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
@@ -161,6 +33,18 @@ function handleSelect(key: string) {
|
||||
router.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 修改 AI 聊天菜单的点击事件
|
||||
function handleAIClick(e: MouseEvent) {
|
||||
e.stopPropagation(); // 阻止事件冒泡
|
||||
router.push('/chat/conversation');
|
||||
}
|
||||
|
||||
// 修改控制台菜单的点击事件
|
||||
function handleConsoleClick(e: MouseEvent) {
|
||||
e.stopPropagation(); // 阻止事件冒泡
|
||||
router.push('/console/user');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -186,7 +70,7 @@ function handleSelect(key: string) {
|
||||
<!-- AI聊天菜单 -->
|
||||
<el-sub-menu index="chat" class="chat-submenu" popper-class="custom-popover">
|
||||
<template #title>
|
||||
<span class="menu-title">AI聊天</span>
|
||||
<span class="menu-title" @click="handleAIClick">AI应用</span>
|
||||
</template>
|
||||
<el-menu-item index="/chat/conversation">
|
||||
AI对话
|
||||
@@ -217,7 +101,7 @@ function handleSelect(key: string) {
|
||||
<!-- 控制台菜单 -->
|
||||
<el-sub-menu index="console" class="console-submenu" popper-class="custom-popover">
|
||||
<template #title>
|
||||
<ConsoleBtn />
|
||||
<ConsoleBtn @click="handleConsoleClick" />
|
||||
</template>
|
||||
<el-menu-item index="/console/user">
|
||||
用户信息
|
||||
@@ -271,7 +155,7 @@ function handleSelect(key: string) {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.header-container {
|
||||
--menu-hover-bg: #f5f5f5;
|
||||
--menu-hover-bg: var(--color-white);
|
||||
--menu-active-color: var(--el-color-primary);
|
||||
--menu-transition: all 0.2s ease;
|
||||
|
||||
@@ -289,6 +173,8 @@ function handleSelect(key: string) {
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
border-bottom: none !important;
|
||||
//background: var(--color-white);
|
||||
|
||||
}
|
||||
|
||||
// 左侧品牌区域
|
||||
@@ -344,7 +230,7 @@ function handleSelect(key: string) {
|
||||
:deep(.el-sub-menu__title) {
|
||||
height: 100% !important;
|
||||
border-bottom: none !important;
|
||||
padding: 0 12px !important;
|
||||
padding: 0 4px !important;
|
||||
color: inherit !important;
|
||||
|
||||
&:hover {
|
||||
@@ -401,7 +287,7 @@ function handleSelect(key: string) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
padding: 0 4px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 控制侧边栏折叠状态
|
||||
const isCollapsed = ref(false);
|
||||
const isCollapsed = ref(true);
|
||||
|
||||
// 菜单项配置
|
||||
const navItems = [
|
||||
@@ -47,7 +47,7 @@ window.addEventListener('resize', checkIsMobile);
|
||||
<div class="console-page" :class="{ 'is-collapsed': isCollapsed }">
|
||||
<!-- 侧边栏导航 -->
|
||||
<div class="nav-sidebar" :class="{ 'is-collapsed': isCollapsed }">
|
||||
<div class="nav-header">
|
||||
<div v-if="false" class="nav-header">
|
||||
<h2 v-show="!isCollapsed" class="nav-title">
|
||||
AI聊天
|
||||
</h2>
|
||||
|
||||
@@ -208,7 +208,7 @@ watch(
|
||||
|
||||
.chat-header {
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
//max-width: 1000px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -607,7 +607,7 @@ function handleImagePreview(url: string) {
|
||||
|
||||
.chat-header {
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
//max-width: 1000px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -97,7 +97,7 @@ window.addEventListener('resize', checkIsMobile);
|
||||
<div class="content-main">
|
||||
<div v-if="isMobile" class="content-header">
|
||||
<div class="mobile-toggle" @click="isCollapsed = false">
|
||||
<el-icon><i-ep-expand /></el-icon>
|
||||
<el-icon><Expand /></el-icon>
|
||||
<span>菜单</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ export const layoutRouter: RouteRecordRaw[] = [
|
||||
name: 'chat',
|
||||
component: () => import('@/pages/chat/index.vue'),
|
||||
meta: {
|
||||
title: 'AI聊天',
|
||||
title: 'AI应用',
|
||||
icon: 'HomeFilled',
|
||||
},
|
||||
children: [
|
||||
|
||||
@@ -199,10 +199,10 @@
|
||||
|
||||
/* Element Plus 组件特定变量 */
|
||||
--el-menu-item-height: 48px;
|
||||
--el-menu-bg-color: var(--sidebar-background-color);
|
||||
--el-menu-text-color: var(--text-color-secondary);
|
||||
--el-menu-active-color: var(--color-primary);
|
||||
--el-menu-hover-bg-color: var(--color-gray-100);
|
||||
//--el-menu-bg-color: var(--sidebar-background-color);
|
||||
//--el-menu-text-color: var(--text-color-secondary);
|
||||
//--el-menu-active-color: var(--color-primary);
|
||||
//--el-menu-hover-bg-color: var(--color-gray-100);
|
||||
|
||||
/* 表单相关 */
|
||||
--el-form-label-font-size: var(--font-size-sm);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
|
||||
import ElementPlus from 'element-plus';
|
||||
import { createApp, h } from 'vue';
|
||||
import ProductPackage from '@/components/ProductPackage/index.vue';
|
||||
import router from '@/routers';
|
||||
import store from '@/stores';
|
||||
|
||||
export function showProductPackage() {
|
||||
const div = document.createElement('div');
|
||||
@@ -16,5 +20,16 @@ export function showProductPackage() {
|
||||
},
|
||||
});
|
||||
|
||||
// 关键:必须在 mount 之前按顺序注册所有依赖
|
||||
app.use(store); // 1. 先注册 store
|
||||
app.use(router); // 2. 再注册 router
|
||||
app.use(ElementPlus); // 3. 最后注册 ElementPlus
|
||||
|
||||
// 注册 Element Plus 图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
|
||||
// 最后才挂载应用
|
||||
app.mount(div);
|
||||
}
|
||||
|
||||
2
Yi.Ai.Vue3/types/components.d.ts
vendored
2
Yi.Ai.Vue3/types/components.d.ts
vendored
@@ -25,8 +25,6 @@ declare module 'vue' {
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
|
||||
Reference in New Issue
Block a user