fix:增加用户中心,完成Apikey功能页,增加角色工具方法
This commit is contained in:
@@ -21,6 +21,6 @@ VITE_SSO_SEVER_URL='https://ccnetcore.com'
|
|||||||
# SSO单点登录项目标识
|
# SSO单点登录项目标识
|
||||||
VITE_SSO_CLIENT_ID='YiXin-Ai';
|
VITE_SSO_CLIENT_ID='YiXin-Ai';
|
||||||
# 版本号
|
# 版本号
|
||||||
VITE_APP_VERSION='1.0.0';
|
VITE_APP_VERSION='1.0.3';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import type { GetSessionListVO } from './types';
|
import type { GetSessionListVO } from './types';
|
||||||
import { get } from '@/utils/request';
|
import { get, post } from '@/utils/request';
|
||||||
|
|
||||||
// 获取当前用户的模型列表
|
// 获取当前用户的模型列表
|
||||||
export function getModelList() {
|
export function getModelList() {
|
||||||
// return get<GetSessionListVO[]>('/system/model/modelList');
|
// return get<GetSessionListVO[]>('/system/model/modelList');
|
||||||
return get<GetSessionListVO[]>('/ai-chat/model').json();
|
return get<GetSessionListVO[]>('/ai-chat/model').json();
|
||||||
}
|
}
|
||||||
|
// 申请ApiKey
|
||||||
|
export function applyApiKey() {
|
||||||
|
return post<any>('/token').json();
|
||||||
|
}
|
||||||
|
// 获取ApiKey
|
||||||
|
export function getApiKey() {
|
||||||
|
return get<any>('/token').json();
|
||||||
|
}
|
||||||
|
|||||||
132
Yi.Ai.Vue3/src/components/userPersonalCenter/NavDialog.vue
Normal file
132
Yi.Ai.Vue3/src/components/userPersonalCenter/NavDialog.vue
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
interface NavItem {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean;
|
||||||
|
title?: string;
|
||||||
|
width?: string;
|
||||||
|
navItems: NavItem[];
|
||||||
|
defaultActive?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
title: '弹窗标题',
|
||||||
|
width: '800px',
|
||||||
|
defaultActive: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'confirm', 'close', 'nav-change']);
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const activeNav = ref(props.defaultActive || (props.navItems.length > 0 ? props.navItems[0].name : ''));
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (val) => {
|
||||||
|
visible.value = val;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.defaultActive, (val) => {
|
||||||
|
if (val) {
|
||||||
|
activeNav.value = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleNavSelect(index: string) {
|
||||||
|
activeNav.value = index;
|
||||||
|
emit('nav-change', index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
visible.value = false;
|
||||||
|
emit('update:modelValue', false);
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConfirm() {
|
||||||
|
emit('confirm', activeNav.value);
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
:title="title"
|
||||||
|
:width="width"
|
||||||
|
:before-close="handleClose"
|
||||||
|
class="nav-dialog"
|
||||||
|
>
|
||||||
|
<div class="dialog-container">
|
||||||
|
<!-- 左侧导航 -->
|
||||||
|
<div class="nav-side">
|
||||||
|
<el-menu
|
||||||
|
:default-active="activeNav"
|
||||||
|
class="nav-menu"
|
||||||
|
@select="handleNavSelect"
|
||||||
|
>
|
||||||
|
<el-menu-item
|
||||||
|
v-for="item in navItems"
|
||||||
|
:key="item.name"
|
||||||
|
:index="item.name"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<el-icon v-if="item.icon">
|
||||||
|
<component :is="item.icon" />
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧内容 -->
|
||||||
|
<div class="content-main">
|
||||||
|
<slot :name="activeNav" />
|
||||||
|
<div v-if="!$slots[activeNav]" class="empty-content">
|
||||||
|
<el-empty description="暂无内容" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dialog-container {
|
||||||
|
display: flex;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-side {
|
||||||
|
width: 200px;
|
||||||
|
border-right: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-main {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 20px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,562 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { CircleCheck } from '@element-plus/icons-vue';
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { applyApiKey, getApiKey } from '@/api/model/index.ts';
|
||||||
|
import { isUserVip } from '@/utils/user';
|
||||||
|
|
||||||
|
const apiKey = ref('');
|
||||||
|
const showKey = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const displayedKey = ref('');
|
||||||
|
const showSuccessDialog = ref(false);
|
||||||
|
|
||||||
|
const isOpening = ref(false);
|
||||||
|
const confettis = ref<any[]>([]);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 获取API密钥
|
||||||
|
async function fetchApiKey() {
|
||||||
|
try {
|
||||||
|
const res = await getApiKey();
|
||||||
|
if (res.data?.apiKey) {
|
||||||
|
apiKey.value = res.data.apiKey;
|
||||||
|
displayedKey.value = res.data.apiKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('获取API密钥失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 领取密钥
|
||||||
|
async function handleClaim() {
|
||||||
|
if (!isUserVip) {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`
|
||||||
|
<div class="text-center leading-relaxed">
|
||||||
|
<h3 class="text-lg font-bold mb-3">成为 YiXinAI-VIP</h3>
|
||||||
|
<p class="mb-2">
|
||||||
|
解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-gray-500">点击下方按钮,立即升级为 VIP 会员!</p>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
'会员尊享',
|
||||||
|
{
|
||||||
|
confirmButtonText: '前往产品页面',
|
||||||
|
cancelButtonText: '关闭',
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
type: 'info',
|
||||||
|
center: true,
|
||||||
|
roundButton: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
router.push({
|
||||||
|
name: 'products', // 使用命名路由
|
||||||
|
query: { from: 'user' }, // 可选:添加来源标识
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 点击右上角关闭或“关闭”按钮,不执行任何操作
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isOpening.value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
isOpening.value = true;
|
||||||
|
generateConfetti();
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
await applyApiKey();
|
||||||
|
await fetchApiKey();
|
||||||
|
|
||||||
|
showSuccessDialog.value = true;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('领取失败:', error);
|
||||||
|
ElMessage.error('领取失败,请稍后重试');
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false;
|
||||||
|
setTimeout(() => isOpening.value = false, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置密钥
|
||||||
|
async function handleReset() {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定要重置API密钥吗?原密钥将立即失效', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
loading.value = true;
|
||||||
|
await applyApiKey(); // 使用相同的申请接口重置
|
||||||
|
await fetchApiKey();
|
||||||
|
ElMessage.success('API密钥已重置');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
console.error('重置失败:', error);
|
||||||
|
ElMessage.error('重置失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换密钥显示状态
|
||||||
|
function toggleKeyVisibility() {
|
||||||
|
showKey.value = !showKey.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制API密钥
|
||||||
|
function copyApiKey() {
|
||||||
|
navigator.clipboard.writeText(apiKey.value || '')
|
||||||
|
.then(() => ElMessage.success('已复制到剪贴板'))
|
||||||
|
.catch(() => ElMessage.error('复制失败'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成随机彩带
|
||||||
|
function generateConfetti() {
|
||||||
|
const colors = ['#ff9a9e', '#fad0c4', '#fbc2eb', '#a6c1ee', '#a18cd1', '#fdcbf1'];
|
||||||
|
const newConfettis = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
newConfettis.push({
|
||||||
|
style: {
|
||||||
|
left: `${50 + (Math.random() - 0.5) * 30}%`,
|
||||||
|
backgroundColor: colors[Math.floor(Math.random() * colors.length)],
|
||||||
|
width: `${6 + Math.random() * 6}px`,
|
||||||
|
height: `${6 + Math.random() * 6}px`,
|
||||||
|
transform: `rotate(${Math.random() * 360}deg)`,
|
||||||
|
animationDuration: `${1 + Math.random() * 2}s`,
|
||||||
|
animationDelay: `${Math.random() * 0.5}s`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
confettis.value = newConfettis;
|
||||||
|
setTimeout(() => confettis.value = [], 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchApiKey();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="api-key-management">
|
||||||
|
<!-- 未领取状态 -->
|
||||||
|
<div v-if="!apiKey " class="unclaimed-state">
|
||||||
|
<div class="gift-container" @click="handleClaim">
|
||||||
|
<div class="gift-box" :class="{ opening: isOpening }">
|
||||||
|
<div class="gift-lid" />
|
||||||
|
<div class="gift-body">
|
||||||
|
<div class="ribbon-horizontal" />
|
||||||
|
<div class="ribbon-vertical" />
|
||||||
|
<div class="ribbon-bow">
|
||||||
|
<div class="bow-left" />
|
||||||
|
<div class="bow-right" />
|
||||||
|
<div class="bow-center" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(confetti, index) in confettis" :key="index" class="confetti"
|
||||||
|
:style="confetti.style"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="claim-text">
|
||||||
|
<h3>🎁 尊享会员专属福利 🎁</h3>
|
||||||
|
<h3>
|
||||||
|
YiXinAi重磅推出专属Api接入服务
|
||||||
|
</h3>
|
||||||
|
<h3>YiXinAI-Vip 限时免费领取</h3>
|
||||||
|
<p>点击礼盒领取您的专属API密钥</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 已领取状态 -->
|
||||||
|
<div v-else-if="apiKey" class="claimed-state">
|
||||||
|
<h3 class="key-title">
|
||||||
|
🎉 恭喜您已获得专属API密钥 🎉
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="key-display">
|
||||||
|
<el-input
|
||||||
|
v-model="displayedKey"
|
||||||
|
:type="showKey ? 'text' : 'password'"
|
||||||
|
readonly
|
||||||
|
class="key-input"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button
|
||||||
|
:icon="showKey ? 'Hide' : 'View'"
|
||||||
|
@click="toggleKeyVisibility"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
icon="DocumentCopy"
|
||||||
|
@click="copyApiKey"
|
||||||
|
/>
|
||||||
|
</el-button-group>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="key-actions">
|
||||||
|
<el-button type="warning" :loading="loading" @click="handleReset">
|
||||||
|
重置密钥
|
||||||
|
</el-button>
|
||||||
|
<p class="key-hint">
|
||||||
|
重置后原密钥将立即失效
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 使用说明 -->
|
||||||
|
<div class="usage-guide">
|
||||||
|
<el-divider />
|
||||||
|
<h3>使用说明</h3>
|
||||||
|
<div class="guide-content">
|
||||||
|
<p><strong>API地址:</strong>https://ai.ccnetcore.com</p>
|
||||||
|
<p><strong>密钥:</strong>上面申请的token</p>
|
||||||
|
<p><strong>模型:</strong>聊天界面显示的模型名称</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="guide-images">
|
||||||
|
<el-image
|
||||||
|
style="max-width: 100%; margin: 10px 0;"
|
||||||
|
src="/images/api_usage_instructions.png"
|
||||||
|
fit="contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 领取成功弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="showSuccessDialog"
|
||||||
|
title="领取成功"
|
||||||
|
width="400px"
|
||||||
|
:show-close="false"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<div class="success-dialog">
|
||||||
|
<el-icon color="#67C23A" :size="60">
|
||||||
|
<CircleCheck />
|
||||||
|
</el-icon>
|
||||||
|
<p class="success-message">
|
||||||
|
恭喜您成功领取API密钥!
|
||||||
|
</p>
|
||||||
|
<p class="success-tip">
|
||||||
|
请妥善保管您的密钥
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="showSuccessDialog = false">
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.api-key-management {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 未领取状态样式 */
|
||||||
|
.unclaimed-state {
|
||||||
|
text-align: center;
|
||||||
|
margin: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-box {
|
||||||
|
position: relative;
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
margin: 0 auto 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-box:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-lid {
|
||||||
|
position: absolute;
|
||||||
|
width: 180px;
|
||||||
|
height: 40px;
|
||||||
|
background: #E74C3C;
|
||||||
|
top: -20px;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-body {
|
||||||
|
position: absolute;
|
||||||
|
width: 180px;
|
||||||
|
height: 140px;
|
||||||
|
background: #C0392B;
|
||||||
|
top: 20px;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ribbon {
|
||||||
|
position: absolute;
|
||||||
|
width: 40px;
|
||||||
|
height: 100%;
|
||||||
|
background: #F1C40F;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-label {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -30px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #E74C3C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.claim-hint {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #E74C3C;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 已领取状态样式 */
|
||||||
|
.claimed-state {
|
||||||
|
margin: 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-title {
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-display {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-input {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加按钮间距 */
|
||||||
|
.action-btn {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 或者直接设置最后一个按钮的左边距 */
|
||||||
|
.key-input :deep(.el-input-group__append .el-button + .el-button) {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
.key-actions {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-hint {
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 使用说明样式 */
|
||||||
|
.usage-guide {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-content {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 成功弹窗样式 */
|
||||||
|
.success-dialog {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #333;
|
||||||
|
margin: 15px 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-tip {
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 未领取状态样式 */
|
||||||
|
.unclaimed-state {
|
||||||
|
text-align: center;
|
||||||
|
margin: 30px 0;
|
||||||
|
perspective: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-container {
|
||||||
|
position: relative;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
margin: 0 auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-box {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: all 0.3s;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-box:hover:not(.opening) {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-box.opening .gift-lid {
|
||||||
|
transform: rotateX(180deg) translateY(-30px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-lid {
|
||||||
|
position: absolute;
|
||||||
|
width: 150px;
|
||||||
|
height: 30px;
|
||||||
|
background: linear-gradient(135deg, #ff7676, #e74c3c);
|
||||||
|
top: -15px;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
transform-origin: bottom;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-body {
|
||||||
|
position: absolute;
|
||||||
|
width: 150px;
|
||||||
|
height: 120px;
|
||||||
|
background: linear-gradient(135deg, #e74c3c, #c0392b);
|
||||||
|
top: 15px;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ribbon-horizontal {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 12px;
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ribbon-vertical {
|
||||||
|
position: absolute;
|
||||||
|
width: 12px;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ribbon-bow {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bow-left, .bow-right {
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: linear-gradient(135deg, #f1c40f, #f39c12);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bow-left {
|
||||||
|
left: -25px;
|
||||||
|
clip-path: polygon(0 0, 100% 0, 100% 50%, 0 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bow-right {
|
||||||
|
left: 5px;
|
||||||
|
clip-path: polygon(0 0, 100% 0, 100% 50%, 0 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bow-center {
|
||||||
|
position: absolute;
|
||||||
|
width: 12px;
|
||||||
|
height: 30px;
|
||||||
|
background: linear-gradient(135deg, #f1c40f, #f39c12);
|
||||||
|
left: -6px;
|
||||||
|
top: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confetti {
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #f1c40f;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
opacity: 0;
|
||||||
|
animation: confetti-fall 2s ease-out forwards;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes confetti-fall {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0) rotate(0deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(var(--tx), 200px) rotate(360deg);
|
||||||
|
opacity: 0;
|
||||||
|
--tx: calc((var(--random) - 0.5) * 200px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.claim-text {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.claim-text h3 {
|
||||||
|
color: #e74c3c;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.claim-text p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
interface User {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const users: User[] = [
|
||||||
|
{ name: '张三', email: 'zhangsan@example.com', role: '管理员' },
|
||||||
|
{ name: '李四', email: 'lisi@example.com', role: '编辑' },
|
||||||
|
{ name: '王五', email: 'wangwu@example.com', role: '查看者' },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="user-management">
|
||||||
|
<h3>用户管理</h3>
|
||||||
|
<el-table :data="users" style="width: 100%">
|
||||||
|
<el-table-column prop="name" label="姓名" />
|
||||||
|
<el-table-column prop="email" label="邮箱" />
|
||||||
|
<el-table-column prop="role" label="角色" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -32,14 +32,19 @@ const popoverRef = ref();
|
|||||||
|
|
||||||
// 弹出面板内容
|
// 弹出面板内容
|
||||||
const popoverList = ref([
|
const popoverList = ref([
|
||||||
|
// {
|
||||||
|
// key: '1',
|
||||||
|
// title: '收藏夹',
|
||||||
|
// icon: 'book-mark-fill',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// key: '2',
|
||||||
|
// title: '设置',
|
||||||
|
// icon: 'settings-4-fill',
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '5',
|
||||||
title: '收藏夹',
|
title: '用户中心',
|
||||||
icon: 'book-mark-fill',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
title: '设置',
|
|
||||||
icon: 'settings-4-fill',
|
icon: 'settings-4-fill',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -53,6 +58,25 @@ const popoverList = ref([
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const navItems = [
|
||||||
|
// { name: 'user', label: '用户管理', icon: 'User' },
|
||||||
|
// { name: 'role', label: '角色管理', icon: 'Avatar' },
|
||||||
|
// { name: 'permission', label: '权限管理', icon: 'Key' },
|
||||||
|
{ name: 'apiKey', label: 'API密钥', icon: 'Key' },
|
||||||
|
];
|
||||||
|
function openDialog() {
|
||||||
|
dialogVisible.value = true;
|
||||||
|
}
|
||||||
|
function handleConfirm(activeNav: string) {
|
||||||
|
console.log('确认操作,当前导航:', activeNav);
|
||||||
|
ElMessage.success('操作成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNavChange(nav: string) {
|
||||||
|
console.log('导航切换:', nav);
|
||||||
|
}
|
||||||
|
|
||||||
// 点击
|
// 点击
|
||||||
function handleClick(item: any) {
|
function handleClick(item: any) {
|
||||||
switch (item.key) {
|
switch (item.key) {
|
||||||
@@ -64,6 +88,10 @@ function handleClick(item: any) {
|
|||||||
ElMessage.warning('暂未开放');
|
ElMessage.warning('暂未开放');
|
||||||
console.log('点击了设置');
|
console.log('点击了设置');
|
||||||
break;
|
break;
|
||||||
|
case '5':
|
||||||
|
console.log('点击了用户中心');
|
||||||
|
openDialog();
|
||||||
|
break;
|
||||||
case '4':
|
case '4':
|
||||||
popoverRef.value?.hide?.();
|
popoverRef.value?.hide?.();
|
||||||
ElMessageBox.confirm('退出登录不会丢失任何数据,你仍可以登录此账号。', '确认退出登录?', {
|
ElMessageBox.confirm('退出登录不会丢失任何数据,你仍可以登录此账号。', '确认退出登录?', {
|
||||||
@@ -226,6 +254,32 @@ function openVipGuide() {
|
|||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
<nav-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
title="用户中心"
|
||||||
|
:nav-items="navItems"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
@nav-change="handleNavChange"
|
||||||
|
>
|
||||||
|
<!-- 用户管理内容 -->
|
||||||
|
<template #user>
|
||||||
|
<user-management />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 角色管理内容 -->
|
||||||
|
<template #role>
|
||||||
|
<!-- <role-management /> -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 权限管理内容 -->
|
||||||
|
<template #permission>
|
||||||
|
<!-- <permission-management /> -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #apiKey>
|
||||||
|
<APIKeyManagement />
|
||||||
|
</template>
|
||||||
|
</nav-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const pricing = [
|
|||||||
'无限制使用',
|
'无限制使用',
|
||||||
'售后微信群支持',
|
'售后微信群支持',
|
||||||
'可用优惠券,使用后29.9元',
|
'可用优惠券,使用后29.9元',
|
||||||
|
'专属Api服务',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -30,6 +31,7 @@ const pricing = [
|
|||||||
'AI超级加速',
|
'AI超级加速',
|
||||||
'无限制使用',
|
'无限制使用',
|
||||||
'售后微信群支持',
|
'售后微信群支持',
|
||||||
|
'专属Api服务',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -44,6 +46,8 @@ const pricing = [
|
|||||||
'无限制使用',
|
'无限制使用',
|
||||||
'售后微信群优先处理',
|
'售后微信群优先处理',
|
||||||
'可用优惠券,使用后49.9元',
|
'可用优惠券,使用后49.9元',
|
||||||
|
'专属Api服务',
|
||||||
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
10
Yi.Ai.Vue3/src/utils/user.js
Normal file
10
Yi.Ai.Vue3/src/utils/user.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { useUserStore } from '@/stores/index.js';
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
// 获取用户角色信息
|
||||||
|
const userRoles = userStore.userInfo?.roles ?? [];
|
||||||
|
const isUserVip = userRoles.some(role => role.roleCode === 'YiXinAi-Vip');
|
||||||
|
export {
|
||||||
|
isUserVip,
|
||||||
|
};
|
||||||
11
Yi.Ai.Vue3/types/components.d.ts
vendored
11
Yi.Ai.Vue3/types/components.d.ts
vendored
@@ -9,14 +9,17 @@ export {}
|
|||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AccountPassword: typeof import('./../src/components/LoginDialog/components/FormLogin/AccountPassword.vue')['default']
|
AccountPassword: typeof import('./../src/components/LoginDialog/components/FormLogin/AccountPassword.vue')['default']
|
||||||
|
APIKeyManagement: typeof import('./../src/components/userPersonalCenter/components/APIKeyManagement.vue')['default']
|
||||||
DeepThinking: typeof import('./../src/components/DeepThinking/index.vue')['default']
|
DeepThinking: typeof import('./../src/components/DeepThinking/index.vue')['default']
|
||||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import('element-plus/es')['ElForm']
|
||||||
@@ -26,6 +29,12 @@ declare module 'vue' {
|
|||||||
ElImage: typeof import('element-plus/es')['ElImage']
|
ElImage: typeof import('element-plus/es')['ElImage']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElMain: typeof import('element-plus/es')['ElMain']
|
ElMain: typeof import('element-plus/es')['ElMain']
|
||||||
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
|
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
FilesSelect: typeof import('./../src/components/FilesSelect/index.vue')['default']
|
FilesSelect: typeof import('./../src/components/FilesSelect/index.vue')['default']
|
||||||
IconSelect: typeof import('./../src/components/IconSelect/index.vue')['default']
|
IconSelect: typeof import('./../src/components/IconSelect/index.vue')['default']
|
||||||
@@ -33,12 +42,14 @@ declare module 'vue' {
|
|||||||
LoginDialog: typeof import('./../src/components/LoginDialog/index.vue')['default']
|
LoginDialog: typeof import('./../src/components/LoginDialog/index.vue')['default']
|
||||||
ModeList: typeof import('./../src/components/modeList/index.vue')['default']
|
ModeList: typeof import('./../src/components/modeList/index.vue')['default']
|
||||||
ModelSelect: typeof import('./../src/components/ModelSelect/index.vue')['default']
|
ModelSelect: typeof import('./../src/components/ModelSelect/index.vue')['default']
|
||||||
|
NavDialog: typeof import('./../src/components/userPersonalCenter/NavDialog.vue')['default']
|
||||||
Popover: typeof import('./../src/components/Popover/index.vue')['default']
|
Popover: typeof import('./../src/components/Popover/index.vue')['default']
|
||||||
QrCodeLogin: typeof import('./../src/components/LoginDialog/components/QrCodeLogin/index.vue')['default']
|
QrCodeLogin: typeof import('./../src/components/LoginDialog/components/QrCodeLogin/index.vue')['default']
|
||||||
RegistrationForm: typeof import('./../src/components/LoginDialog/components/FormLogin/RegistrationForm.vue')['default']
|
RegistrationForm: typeof import('./../src/components/LoginDialog/components/FormLogin/RegistrationForm.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SvgIcon: typeof import('./../src/components/SvgIcon/index.vue')['default']
|
SvgIcon: typeof import('./../src/components/SvgIcon/index.vue')['default']
|
||||||
|
UserManagement: typeof import('./../src/components/userPersonalCenter/components/UserManagement.vue')['default']
|
||||||
VerificationCode: typeof import('./../src/components/LoginDialog/components/FormLogin/VerificationCode.vue')['default']
|
VerificationCode: typeof import('./../src/components/LoginDialog/components/FormLogin/VerificationCode.vue')['default']
|
||||||
WelecomeText: typeof import('./../src/components/WelecomeText/index.vue')['default']
|
WelecomeText: typeof import('./../src/components/WelecomeText/index.vue')['default']
|
||||||
}
|
}
|
||||||
|
|||||||
1
Yi.Ai.Vue3/types/import_meta.d.ts
vendored
1
Yi.Ai.Vue3/types/import_meta.d.ts
vendored
@@ -6,7 +6,6 @@ interface ImportMetaEnv {
|
|||||||
readonly VITE_WEB_ENV: string;
|
readonly VITE_WEB_ENV: string;
|
||||||
readonly VITE_WEB_BASE_API: string;
|
readonly VITE_WEB_BASE_API: string;
|
||||||
readonly VITE_API_URL: string;
|
readonly VITE_API_URL: string;
|
||||||
readonly VITE_BUILD_COMPRESS: string;
|
|
||||||
readonly VITE_SSO_SEVER_URL: string;
|
readonly VITE_SSO_SEVER_URL: string;
|
||||||
readonly VITE_SSO_CLIENT_ID: string;
|
readonly VITE_SSO_CLIENT_ID: string;
|
||||||
readonly VITE_APP_VERSION: string;
|
readonly VITE_APP_VERSION: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user