feat:获取会员信息 并且展示title
This commit is contained in:
BIN
src/assets/titleIcon/PRO+.png
Normal file
BIN
src/assets/titleIcon/PRO+.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/assets/titleIcon/PRO-Try.png
Normal file
BIN
src/assets/titleIcon/PRO-Try.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 302 KiB |
BIN
src/assets/titleIcon/PRO.png
Normal file
BIN
src/assets/titleIcon/PRO.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/assets/titleIcon/free.png
Normal file
BIN
src/assets/titleIcon/free.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
@ -20,6 +20,37 @@ import { ChatHistoryManager } from "../utils/chatHistoryManager";
|
||||
import { MessageType } from "../types/chatHistory";
|
||||
import { getCachedUserInfo } from "../services/userService";
|
||||
|
||||
/**
|
||||
* 获取会员等级图标 URI
|
||||
*/
|
||||
function getTierIconUri(
|
||||
webview: vscode.Webview,
|
||||
context: vscode.ExtensionContext,
|
||||
tierCode?: string
|
||||
): string | undefined {
|
||||
if (!tierCode) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tierIconMap: Record<string, string> = {
|
||||
'BASIC': 'free.png',
|
||||
'TRIAL': 'PRO-Try.png',
|
||||
'ADVANCED': 'PRO.png',
|
||||
'PROFESSIONAL': 'PRO+.png'
|
||||
};
|
||||
|
||||
const iconFile = tierIconMap[tierCode];
|
||||
if (!iconFile) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const iconUri = webview.asWebviewUri(
|
||||
vscode.Uri.joinPath(context.extensionUri, 'src', 'assets', 'titleIcon', iconFile)
|
||||
);
|
||||
|
||||
return iconUri.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并显示 IC 助手面板
|
||||
*/
|
||||
@ -117,13 +148,15 @@ export async function showICHelperPanel(
|
||||
if (userInfo) {
|
||||
// 使用缓存的用户信息
|
||||
console.log('[ICHelperPanel] 使用缓存的用户信息:', userInfo);
|
||||
const tierIconUrl = getTierIconUri(panel.webview, context, userInfo.membership?.tierCode);
|
||||
panel.webview.postMessage({
|
||||
command: 'updateUserInfo',
|
||||
userInfo: {
|
||||
userId: userInfo.userId,
|
||||
nickname: userInfo.nickname,
|
||||
username: userInfo.username
|
||||
}
|
||||
},
|
||||
tierIconUrl: tierIconUrl
|
||||
});
|
||||
} else {
|
||||
// 如果没有缓存,从 session 中获取
|
||||
|
||||
@ -7,7 +7,7 @@ import * as http from 'http';
|
||||
import { URL } from 'url';
|
||||
import * as vscode from 'vscode';
|
||||
import { getStrangeLoopApiUrl, getConfig } from '../config/settings';
|
||||
import type { UserInfoResponse } from '../types/api';
|
||||
import type { UserInfoResponse, MembershipResponse, MultiMembershipVO, MembershipItemVO } from '../types/api';
|
||||
|
||||
/**
|
||||
* HTTP 请求选项
|
||||
@ -106,6 +106,14 @@ export interface UserInfo {
|
||||
permissions?: string[];
|
||||
createTime?: string;
|
||||
loginDate?: string;
|
||||
// 会员信息
|
||||
membership?: {
|
||||
tierCode: string;
|
||||
tierName: string;
|
||||
tierLevel: number;
|
||||
remainingDays?: number;
|
||||
monthlyCredits?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,14 +158,76 @@ export async function getUserInfo(token: string): Promise<UserInfo | null> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户会员信息
|
||||
* GET /strangeloop/api/membership/current
|
||||
*/
|
||||
export async function getMembershipInfo(token: string): Promise<MultiMembershipVO | null> {
|
||||
const apiPath = '/strangeloop/api/membership/current';
|
||||
const fullUrl = getStrangeLoopApiUrl(apiPath);
|
||||
console.log('[UserService] 获取会员信息');
|
||||
console.log('[UserService] 请求地址:', fullUrl);
|
||||
console.log('[UserService] Token:', token ? '已提供' : '未提供');
|
||||
|
||||
try {
|
||||
const response = await request<MembershipResponse>(apiPath, {
|
||||
method: 'GET',
|
||||
token
|
||||
});
|
||||
|
||||
// 处理响应数据 - 检查 code 是否为 200
|
||||
if (response.code === 200 && response.data) {
|
||||
console.log('[UserService] 会员信息获取成功:', response.data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
console.error('[UserService] 获取会员信息失败:', response);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('[UserService] 请求会员信息失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员等级映射
|
||||
*/
|
||||
const TIER_LEVEL_MAP: Record<string, number> = {
|
||||
'BASIC': 1,
|
||||
'TRIAL': 2,
|
||||
'ADVANCED': 3,
|
||||
'PROFESSIONAL': 4
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取最高等级的会员信息
|
||||
*/
|
||||
function getHighestTierMembership(allMemberships?: MembershipItemVO[]): MembershipItemVO | null {
|
||||
if (!allMemberships || allMemberships.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 按等级排序,获取最高等级
|
||||
return allMemberships.reduce((highest, current) => {
|
||||
const currentLevel = TIER_LEVEL_MAP[current.tierCode] || 0;
|
||||
const highestLevel = TIER_LEVEL_MAP[highest.tierCode] || 0;
|
||||
return currentLevel > highestLevel ? current : highest;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 当获取到 token 时自动调用此函数
|
||||
* 用于在登录成功后立即获取用户信息
|
||||
*/
|
||||
export async function onTokenReceived(token: string): Promise<UserInfo | null> {
|
||||
try {
|
||||
console.log('[UserService] Token 已获取,正在获取用户信息...');
|
||||
const userInfo = await getUserInfo(token);
|
||||
console.log('[UserService] Token 已获取,正在获取用户信息和会员信息...');
|
||||
|
||||
// 并行获取用户信息和会员信息
|
||||
const [userInfo, membershipInfo] = await Promise.all([
|
||||
getUserInfo(token),
|
||||
getMembershipInfo(token)
|
||||
]);
|
||||
|
||||
if (!userInfo) {
|
||||
console.warn('[UserService] 未能获取到用户信息');
|
||||
@ -192,6 +262,30 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
|
||||
if (userInfo.loginDate) {
|
||||
console.log(`最后登录: ${userInfo.loginDate}`);
|
||||
}
|
||||
|
||||
// 打印会员信息 - 从 allMemberships 中获取最高等级
|
||||
if (membershipInfo && membershipInfo.allMemberships) {
|
||||
const highestTier = getHighestTierMembership(membershipInfo.allMemberships);
|
||||
|
||||
if (highestTier) {
|
||||
console.log('');
|
||||
console.log('会员信息:');
|
||||
console.log(`会员等级: ${highestTier.tierName} (${highestTier.tierCode})`);
|
||||
console.log(`等级层级: ${highestTier.tierLevel}`);
|
||||
console.log(`剩余天数: ${highestTier.remainingDays === -1 ? '永久' : highestTier.remainingDays + '天'}`);
|
||||
console.log(`月度积分: ${highestTier.monthlyCredits}`);
|
||||
|
||||
// 将最高等级会员信息合并到用户信息中
|
||||
userInfo.membership = {
|
||||
tierCode: highestTier.tierCode,
|
||||
tierName: highestTier.tierName,
|
||||
tierLevel: highestTier.tierLevel,
|
||||
remainingDays: highestTier.remainingDays,
|
||||
monthlyCredits: highestTier.monthlyCredits
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// 保存到持久化存储
|
||||
|
||||
@ -345,6 +345,61 @@ export interface UserInfoResponse {
|
||||
};
|
||||
}
|
||||
|
||||
// ============== 会员信息 ==============
|
||||
|
||||
/**
|
||||
* 会员单条记录
|
||||
*/
|
||||
export interface MembershipItemVO {
|
||||
membershipId: number | null;
|
||||
tierCode: string;
|
||||
tierName: string;
|
||||
tierLevel: number;
|
||||
expireTime: string | null;
|
||||
remainingDays: number;
|
||||
permanent: boolean;
|
||||
nextGrantTime: string | null;
|
||||
lastGrantTime: string | null;
|
||||
grantCycle: number;
|
||||
totalGranted: number;
|
||||
monthlyCredits: number;
|
||||
teamSeat: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户会员信息
|
||||
*/
|
||||
export interface UserMembershipVO {
|
||||
userId: number;
|
||||
tierCode: string;
|
||||
tierName: string;
|
||||
tierLevel: number;
|
||||
allowedModelCombinations: string[];
|
||||
description?: string;
|
||||
createdTime?: string;
|
||||
updatedTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多会员信息响应
|
||||
*/
|
||||
export interface MultiMembershipVO extends UserMembershipVO {
|
||||
displayTier?: MembershipItemVO;
|
||||
allMemberships?: MembershipItemVO[];
|
||||
totalMonthlyCredits?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员信息响应
|
||||
* GET /strangeloop/api/membership/current
|
||||
*/
|
||||
export interface MembershipResponse {
|
||||
code: number;
|
||||
msg?: string;
|
||||
message?: string;
|
||||
data?: MultiMembershipVO;
|
||||
}
|
||||
|
||||
// ============== 辅助类型 ==============
|
||||
|
||||
/** 后端工具名称 */
|
||||
|
||||
@ -27,6 +27,8 @@ export function getConversationHistoryBarContent(): string {
|
||||
<span class="user-nickname" id="userNickname">加载中...</span>
|
||||
</div>
|
||||
|
||||
<img class="tier-icon" id="tierIcon" style="display: none;" />
|
||||
|
||||
<button class="new-conversation-button" onclick="createNewConversation()" title="新建对话">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" fill="currentColor"/>
|
||||
@ -88,6 +90,14 @@ export function getConversationHistoryBarStyles(): string {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tier-icon {
|
||||
width: 110px;
|
||||
height: 35px;
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.history-dropdown-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@ -590,10 +590,19 @@ export function getWebviewContent(
|
||||
console.log('[WebView] 收到用户信息:', message.userInfo);
|
||||
const userInfo = document.getElementById('userInfo');
|
||||
const userNickname = document.getElementById('userNickname');
|
||||
const tierIcon = document.getElementById('tierIcon');
|
||||
if (userInfo && userNickname && message.userInfo) {
|
||||
const displayName = message.userInfo.nickname || message.userInfo.username || '用户';
|
||||
console.log('[WebView] 显示用户名:', displayName);
|
||||
userNickname.textContent = displayName;
|
||||
|
||||
// 显示会员等级图标
|
||||
if (tierIcon && message.tierIconUrl) {
|
||||
tierIcon.src = message.tierIconUrl;
|
||||
tierIcon.style.display = 'block';
|
||||
console.log('[WebView] 显示会员图标:', message.tierIconUrl);
|
||||
}
|
||||
|
||||
userInfo.style.display = 'flex';
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user