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

@ -0,0 +1,321 @@
/**
* 邀请码验证弹窗
*/
/**
* 获取邀请码弹窗的 HTML 内容
*/
export function getInvitationModalContent(qrCodeUri?: string): string {
return `
<!-- 邀请码验证弹窗 -->
<div id="invitationModal" class="invitation-modal" style="display: none;">
<div class="invitation-modal-overlay"></div>
<div class="invitation-modal-content">
<div class="invitation-modal-header">
<h2>验证邀请码</h2>
<p class="invitation-modal-subtitle">仅供企业端用户和内部人员使用</p>
</div>
<div class="invitation-modal-body">
<div class="invitation-qrcode-section">
<p class="invitation-qrcode-text">欢迎企业端用户扫码添加微信获取邀请码</p>
<img src="${qrCodeUri}" alt="微信二维码" class="invitation-qrcode-image" />
</div>
<div class="invitation-input-section">
<input
type="text"
id="invitationCodeInput"
class="invitation-code-input"
placeholder="请输入邀请码"
maxlength="20"
/>
<div id="invitationError" class="invitation-error" style="display: none;"></div>
</div>
</div>
<div class="invitation-modal-footer">
<button id="invitationSubmitBtn" class="invitation-btn invitation-btn-primary">
验证
</button>
</div>
</div>
</div>
`;
}
/**
* 获取邀请码弹窗的 CSS 样式
*/
export function getInvitationModalStyles(): string {
return `
/* 邀请码弹窗样式 */
.invitation-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
}
.invitation-modal-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
}
.invitation-modal-content {
position: relative;
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
width: 90%;
max-width: 400px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
animation: modalSlideIn 0.3s ease-out;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.invitation-modal-header {
padding: 24px 24px 16px;
border-bottom: 1px solid var(--vscode-panel-border);
text-align: center;
}
.invitation-modal-header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--vscode-foreground);
}
.invitation-modal-subtitle {
margin: 8px 0 0;
font-size: 13px;
color: var(--vscode-descriptionForeground);
}
.invitation-modal-body {
padding: 24px;
}
.invitation-qrcode-section {
text-align: center;
margin-bottom: 24px;
}
.invitation-qrcode-text {
margin: 0 0 16px;
font-size: 13px;
color: var(--vscode-foreground);
line-height: 1.5;
}
.invitation-qrcode-image {
width: 200px;
height: 200px;
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
background: #fff;
}
.invitation-input-section {
margin-top: 24px;
}
.invitation-code-input {
width: 100%;
padding: 10px 12px;
font-size: 14px;
border: 1px solid var(--vscode-input-border);
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border-radius: 4px;
outline: none;
transition: border-color 0.2s;
box-sizing: border-box;
}
.invitation-code-input:focus {
border-color: var(--vscode-focusBorder);
}
.invitation-code-input::placeholder {
color: var(--vscode-input-placeholderForeground);
}
.invitation-error {
margin-top: 12px;
padding: 8px 12px;
font-size: 13px;
color: var(--vscode-errorForeground);
background: var(--vscode-inputValidation-errorBackground);
border: 1px solid var(--vscode-inputValidation-errorBorder);
border-radius: 4px;
}
.invitation-modal-footer {
padding: 16px 24px;
border-top: 1px solid var(--vscode-panel-border);
display: flex;
justify-content: flex-end;
}
.invitation-btn {
padding: 8px 20px;
font-size: 13px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
outline: none;
}
.invitation-btn-primary {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
.invitation-btn-primary:hover {
background: var(--vscode-button-hoverBackground);
}
.invitation-btn-primary:active {
transform: scale(0.98);
}
.invitation-btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
}
/**
* 获取邀请码弹窗的 JavaScript 逻辑
*/
export function getInvitationModalScript(): string {
return `
// 邀请码弹窗逻辑
(function() {
const modal = document.getElementById('invitationModal');
const input = document.getElementById('invitationCodeInput');
const submitBtn = document.getElementById('invitationSubmitBtn');
const errorDiv = document.getElementById('invitationError');
// 显示邀请码弹窗
window.showInvitationModal = function() {
modal.style.display = 'flex';
setTimeout(() => {
input.focus();
}, 100);
};
// 隐藏邀请码弹窗
window.hideInvitationModal = function() {
modal.style.display = 'none';
input.value = '';
errorDiv.style.display = 'none';
errorDiv.textContent = '';
submitBtn.disabled = false;
};
// 显示错误信息
window.showInvitationError = function(message) {
errorDiv.textContent = message;
errorDiv.style.display = 'block';
submitBtn.disabled = false;
};
// 提交邀请码
function submitInvitationCode() {
const code = input.value.trim();
if (!code) {
showInvitationError('邀请码不能为空');
return;
}
if (code.length < 6) {
showInvitationError('邀请码格式不正确');
return;
}
// 禁用按钮,防止重复提交
submitBtn.disabled = true;
errorDiv.style.display = 'none';
// 发送验证请求到后端
vscode.postMessage({
command: 'verifyInvitationCode',
code: code
});
}
// 点击提交按钮
submitBtn.addEventListener('click', submitInvitationCode);
// 回车键提交
input.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
submitInvitationCode();
}
});
// 阻止点击弹窗内容时关闭
document.querySelector('.invitation-modal-content').addEventListener('click', function(e) {
e.stopPropagation();
});
// 监听来自后端的消息
window.addEventListener('message', function(event) {
const message = event.data;
// 处理邀请码验证状态
if (message.command === 'invitationCodeStatus') {
if (!message.verified) {
// 未验证,显示弹窗
showInvitationModal();
} else {
// 已验证,继续执行待处理的操作
if (typeof pendingExampleIndex !== 'undefined' && pendingExampleIndex >= 0) {
// 如果有待发送的示例,先检查工作区
vscode.postMessage({ command: 'checkWorkspace' });
}
}
}
// 处理邀请码验证结果
if (message.command === 'invitationCodeVerified') {
if (message.success) {
// 验证成功,隐藏弹窗
hideInvitationModal();
// 继续执行待处理的操作
if (typeof pendingExampleIndex !== 'undefined' && pendingExampleIndex >= 0) {
// 如果有待发送的示例,先检查工作区
vscode.postMessage({ command: 'checkWorkspace' });
}
} else {
// 验证失败,显示错误信息
showInvitationError(message.message || '验证失败,请重试');
}
}
});
})();
`;
}