fix:增加用户中心,完成Apikey功能页,增加角色工具方法
This commit is contained in:
@@ -21,6 +21,6 @@ VITE_SSO_SEVER_URL='https://ccnetcore.com'
|
||||
# SSO单点登录项目标识
|
||||
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 { get } from '@/utils/request';
|
||||
import { get, post } from '@/utils/request';
|
||||
|
||||
// 获取当前用户的模型列表
|
||||
export function getModelList() {
|
||||
// return get<GetSessionListVO[]>('/system/model/modelList');
|
||||
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([
|
||||
// {
|
||||
// key: '1',
|
||||
// title: '收藏夹',
|
||||
// icon: 'book-mark-fill',
|
||||
// },
|
||||
// {
|
||||
// key: '2',
|
||||
// title: '设置',
|
||||
// icon: 'settings-4-fill',
|
||||
// },
|
||||
{
|
||||
key: '1',
|
||||
title: '收藏夹',
|
||||
icon: 'book-mark-fill',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
title: '设置',
|
||||
key: '5',
|
||||
title: '用户中心',
|
||||
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) {
|
||||
switch (item.key) {
|
||||
@@ -64,6 +88,10 @@ function handleClick(item: any) {
|
||||
ElMessage.warning('暂未开放');
|
||||
console.log('点击了设置');
|
||||
break;
|
||||
case '5':
|
||||
console.log('点击了用户中心');
|
||||
openDialog();
|
||||
break;
|
||||
case '4':
|
||||
popoverRef.value?.hide?.();
|
||||
ElMessageBox.confirm('退出登录不会丢失任何数据,你仍可以登录此账号。', '确认退出登录?', {
|
||||
@@ -226,6 +254,32 @@ function openVipGuide() {
|
||||
</div>
|
||||
</Popover>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ const pricing = [
|
||||
'无限制使用',
|
||||
'售后微信群支持',
|
||||
'可用优惠券,使用后29.9元',
|
||||
'专属Api服务',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -30,6 +31,7 @@ const pricing = [
|
||||
'AI超级加速',
|
||||
'无限制使用',
|
||||
'售后微信群支持',
|
||||
'专属Api服务',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -44,6 +46,8 @@ const pricing = [
|
||||
'无限制使用',
|
||||
'售后微信群优先处理',
|
||||
'可用优惠券,使用后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' {
|
||||
export interface GlobalComponents {
|
||||
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']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
@@ -26,6 +29,12 @@ declare module 'vue' {
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
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']
|
||||
FilesSelect: typeof import('./../src/components/FilesSelect/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']
|
||||
ModeList: typeof import('./../src/components/modeList/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']
|
||||
QrCodeLogin: typeof import('./../src/components/LoginDialog/components/QrCodeLogin/index.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']
|
||||
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']
|
||||
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_BASE_API: string;
|
||||
readonly VITE_API_URL: string;
|
||||
readonly VITE_BUILD_COMPRESS: string;
|
||||
readonly VITE_SSO_SEVER_URL: string;
|
||||
readonly VITE_SSO_CLIENT_ID: string;
|
||||
readonly VITE_APP_VERSION: string;
|
||||
|
||||
Reference in New Issue
Block a user