feat:获取会员信息 并且展示title

This commit is contained in:
Roe-xin
2026-01-09 16:24:27 +08:00
parent 940584e1ea
commit c58e3603de
9 changed files with 205 additions and 4 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

@ -20,6 +20,37 @@ import { ChatHistoryManager } from "../utils/chatHistoryManager";
import { MessageType } from "../types/chatHistory"; import { MessageType } from "../types/chatHistory";
import { getCachedUserInfo } from "../services/userService"; 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 助手面板 * 创建并显示 IC 助手面板
*/ */
@ -117,13 +148,15 @@ export async function showICHelperPanel(
if (userInfo) { if (userInfo) {
// 使用缓存的用户信息 // 使用缓存的用户信息
console.log('[ICHelperPanel] 使用缓存的用户信息:', userInfo); console.log('[ICHelperPanel] 使用缓存的用户信息:', userInfo);
const tierIconUrl = getTierIconUri(panel.webview, context, userInfo.membership?.tierCode);
panel.webview.postMessage({ panel.webview.postMessage({
command: 'updateUserInfo', command: 'updateUserInfo',
userInfo: { userInfo: {
userId: userInfo.userId, userId: userInfo.userId,
nickname: userInfo.nickname, nickname: userInfo.nickname,
username: userInfo.username username: userInfo.username
} },
tierIconUrl: tierIconUrl
}); });
} else { } else {
// 如果没有缓存,从 session 中获取 // 如果没有缓存,从 session 中获取

View File

@ -7,7 +7,7 @@ import * as http from 'http';
import { URL } from 'url'; import { URL } from 'url';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { getStrangeLoopApiUrl, getConfig } from '../config/settings'; import { getStrangeLoopApiUrl, getConfig } from '../config/settings';
import type { UserInfoResponse } from '../types/api'; import type { UserInfoResponse, MembershipResponse, MultiMembershipVO, MembershipItemVO } from '../types/api';
/** /**
* HTTP 请求选项 * HTTP 请求选项
@ -106,6 +106,14 @@ export interface UserInfo {
permissions?: string[]; permissions?: string[];
createTime?: string; createTime?: string;
loginDate?: 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 时自动调用此函数 * 当获取到 token 时自动调用此函数
* 用于在登录成功后立即获取用户信息 * 用于在登录成功后立即获取用户信息
*/ */
export async function onTokenReceived(token: string): Promise<UserInfo | null> { export async function onTokenReceived(token: string): Promise<UserInfo | null> {
try { try {
console.log('[UserService] Token 已获取,正在获取用户信息...'); console.log('[UserService] Token 已获取,正在获取用户信息和会员信息...');
const userInfo = await getUserInfo(token);
// 并行获取用户信息和会员信息
const [userInfo, membershipInfo] = await Promise.all([
getUserInfo(token),
getMembershipInfo(token)
]);
if (!userInfo) { if (!userInfo) {
console.warn('[UserService] 未能获取到用户信息'); console.warn('[UserService] 未能获取到用户信息');
@ -192,6 +262,30 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
if (userInfo.loginDate) { if (userInfo.loginDate) {
console.log(`最后登录: ${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)); console.log('='.repeat(60));
// 保存到持久化存储 // 保存到持久化存储

View File

@ -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;
}
// ============== 辅助类型 ============== // ============== 辅助类型 ==============
/** 后端工具名称 */ /** 后端工具名称 */

View File

@ -27,6 +27,8 @@ export function getConversationHistoryBarContent(): string {
<span class="user-nickname" id="userNickname">加载中...</span> <span class="user-nickname" id="userNickname">加载中...</span>
</div> </div>
<img class="tier-icon" id="tierIcon" style="display: none;" />
<button class="new-conversation-button" onclick="createNewConversation()" title="新建对话"> <button class="new-conversation-button" onclick="createNewConversation()" title="新建对话">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" fill="currentColor"/> <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" fill="currentColor"/>
@ -88,6 +90,14 @@ export function getConversationHistoryBarStyles(): string {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.tier-icon {
width: 110px;
height: 35px;
flex-shrink: 0;
object-fit: contain;
border-radius: 4px;
}
.history-dropdown-button { .history-dropdown-button {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

View File

@ -590,10 +590,19 @@ export function getWebviewContent(
console.log('[WebView] 收到用户信息:', message.userInfo); console.log('[WebView] 收到用户信息:', message.userInfo);
const userInfo = document.getElementById('userInfo'); const userInfo = document.getElementById('userInfo');
const userNickname = document.getElementById('userNickname'); const userNickname = document.getElementById('userNickname');
const tierIcon = document.getElementById('tierIcon');
if (userInfo && userNickname && message.userInfo) { if (userInfo && userNickname && message.userInfo) {
const displayName = message.userInfo.nickname || message.userInfo.username || '用户'; const displayName = message.userInfo.nickname || message.userInfo.username || '用户';
console.log('[WebView] 显示用户名:', displayName); console.log('[WebView] 显示用户名:', displayName);
userNickname.textContent = 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'; userInfo.style.display = 'flex';
} }
break; break;