feat: 实现邀请码验证功能

## 功能概述
   - 用户首次使用需验证邀请码才能发起对话
   - 在输入框聚焦和点击示例时触发验证检查
   - 使用弹窗形式展示邀请码输入界面,包含企业端用户提示和微信二维码

   ## 主要变更

   ### 新增文件
   - `services/invitationService.ts`: 邀请码验证服务,处理验证逻辑和状态管理
   - `views/invitationModal.ts`: 邀请码验证弹窗组件(HTML/CSS/JS)
   - `docs/invitation-code-design.md`: 邀请码功能设计文档

   ### 修改文件
   - `extension.ts`: 添加更换邀请码命令,退出登录时清除验证状态
   - `panels/ICHelperPanel.ts`: 添加邀请码验证状态检查和验证消息处理
   - `services/apiClient.ts`: 添加邀请码验证接口调用
   - `types/api.ts`: 添加邀请码相关类型定义
   - `views/inputArea.ts`: 输入框聚焦时触发邀请码验证检查
   - `views/exampleShowcase.ts`: 点击示例时先检查邀请码验证状态
   - `views/webviewContent.ts`: 集成邀请码弹窗到主界面

   ## 技术实现
   - 验证状态保存在 ExtensionContext.globalState 中
   - 使用后端接口 POST /api/invitation/verify 进行验证
   - 弹窗样式适配 VS Code 主题
   - 支持回车键提交验证
This commit is contained in:
Roe-xin
2026-01-27 14:40:31 +08:00
parent 885e2cef75
commit 032dd1b215
10 changed files with 1290 additions and 4 deletions

View File

@ -7,7 +7,7 @@ import * as https from 'https';
import * as http from 'http';
import { URL } from 'url';
import { getApiUrl, getConfig } from '../config/settings';
import type { ToolCallResult, AnswerRequest, ToolResultResponse, AnswerResponse, ToolConfirmResponse, UserInfoResponse } from '../types/api';
import type { ToolCallResult, AnswerRequest, ToolResultResponse, AnswerResponse, ToolConfirmResponse, UserInfoResponse, InvitationVerifyRequest, InvitationVerifyResponse, InvitationStatusResponse } from '../types/api';
/**
* HTTP 请求选项
@ -260,3 +260,27 @@ export async function getCreditBalance(userId: string): Promise<CreditBalanceRes
timeout: 5000
});
}
/**
* 验证邀请码
* POST /api/invitation/verify
*/
export async function verifyInvitationCode(code: string): Promise<InvitationVerifyResponse> {
console.log('[API] 验证邀请码');
const body: InvitationVerifyRequest = { code };
return request<InvitationVerifyResponse>('/api/invitation/verify', {
method: 'POST',
body
});
}
/**
* 查询邀请码验证状态
* GET /api/invitation/status
*/
export async function checkInvitationStatus(): Promise<InvitationStatusResponse> {
console.log('[API] 查询邀请码验证状态');
return request<InvitationStatusResponse>('/api/invitation/status', {
method: 'GET'
});
}

View File

@ -0,0 +1,91 @@
/**
* 邀请码验证服务
*/
import * as vscode from 'vscode';
import { verifyInvitationCode, checkInvitationStatus } from './apiClient';
/**
* 邀请码验证服务类
*/
export class InvitationService {
/**
* 检查用户是否已验证邀请码
*/
static async isVerified(context: vscode.ExtensionContext): Promise<boolean> {
// 【临时】使用本地验证,不调用后端
const localVerified = context.globalState.get<boolean>('invitationCodeVerified');
return localVerified || false;
}
/**
* 验证邀请码
*/
static async verifyCode(code: string): Promise<{ success: boolean; message: string }> {
try {
console.log('[InvitationService] 验证邀请码:', code);
const response = await verifyInvitationCode(code);
if (response.code === 200 && response.data?.verified) {
return {
success: true,
message: response.msg || '验证成功'
};
} else {
return {
success: false,
message: response.msg || '验证失败'
};
}
} catch (error: any) {
console.error('[InvitationService] 验证邀请码失败:', error);
return {
success: false,
message: error.message || '网络连接失败,请检查网络后重试'
};
}
}
/**
* 保存验证状态到本地
*/
static async saveVerificationStatus(
context: vscode.ExtensionContext,
code: string,
verifiedTime?: string
): Promise<void> {
await context.globalState.update('invitationCodeVerified', true);
await context.globalState.update('invitationCode', code);
await context.globalState.update('invitationVerifiedTime', verifiedTime || new Date().toISOString());
}
/**
* 清除验证状态(用于退出登录或更换邀请码)
*/
static async clearVerificationStatus(context: vscode.ExtensionContext): Promise<void> {
await context.globalState.update('invitationCodeVerified', undefined);
await context.globalState.update('invitationCode', undefined);
await context.globalState.update('invitationVerifiedTime', undefined);
}
/**
* 显示邀请码输入弹窗
*/
static async showInputDialog(): Promise<string | undefined> {
const code = await vscode.window.showInputBox({
prompt: '请输入邀请码以继续使用 IC Coder',
placeHolder: '例如INVITE2024ABC',
ignoreFocusOut: true,
validateInput: (value) => {
if (!value || value.trim().length === 0) {
return '邀请码不能为空';
}
if (value.trim().length < 6) {
return '邀请码格式不正确';
}
return null;
}
});
return code?.trim();
}
}