feat: 实现试用用户欢迎引导和过期检测功能
- 新增试用用户首次登录欢迎弹窗,展示使用教程 - 新增试用期过期检测服务和过期提醒弹窗 - 从 JWT token 中提取 ispluginTrial 标识判断用户类型 - 试用用户跳过邀请码验证流程 - 在消息发送前检查试用期是否过期 - 新增 ExpiredPanel 和 WelcomePanel 面板组件 - 新增 expiredModal 和 welcomeModal 视图组件 - 优化用户登录流程,根据用户类型显示不同引导
This commit is contained in:
@ -310,6 +310,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
logoutCommand,
|
logoutCommand,
|
||||||
changeInvitationCodeCommand,
|
changeInvitationCodeCommand,
|
||||||
testNotificationCommand,
|
testNotificationCommand,
|
||||||
|
testTrialUserCommand,
|
||||||
|
testExpiredUserCommand,
|
||||||
// TODO: 等待重新实现这些命令
|
// TODO: 等待重新实现这些命令
|
||||||
// viewHistoryCommand,
|
// viewHistoryCommand,
|
||||||
// newSessionCommand,
|
// newSessionCommand,
|
||||||
|
|||||||
78
src/panels/ExpiredPanel.ts
Normal file
78
src/panels/ExpiredPanel.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* 试用期到期提醒面板
|
||||||
|
* 功能:试用期到期时显示续费提示
|
||||||
|
* 依赖:vscode
|
||||||
|
* 使用场景:试用用户到期时显示
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
export class ExpiredPanel {
|
||||||
|
public static render() {
|
||||||
|
const panel = vscode.window.createWebviewPanel(
|
||||||
|
'icCoderExpired',
|
||||||
|
'试用期已到期',
|
||||||
|
vscode.ViewColumn.One,
|
||||||
|
{ enableScripts: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
panel.webview.html = this.getHtmlContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getHtmlContent(): string {
|
||||||
|
return `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 60px 40px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
background-color: var(--vscode-editor-background);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: var(--vscode-errorForeground);
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 15px 0;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
padding: 12px 30px;
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>⏰ 您的试用期已到期</h1>
|
||||||
|
<p>感谢您使用 IC Coder!您的 15 天试用期已结束。</p>
|
||||||
|
<p>如需继续使用,请联系我们获取正式版本。</p>
|
||||||
|
|
||||||
|
<button class="button" onclick="contact()">联系我们</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function contact() {
|
||||||
|
window.open('https://iccoder.com/contact', '_blank');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -426,6 +426,19 @@ export async function showICHelperPanel(
|
|||||||
case "checkInvitationCode":
|
case "checkInvitationCode":
|
||||||
// 检查邀请码验证状态
|
// 检查邀请码验证状态
|
||||||
{
|
{
|
||||||
|
// 先检查是否是试用用户
|
||||||
|
const { getCachedUserInfo } = require("../services/userService");
|
||||||
|
const userInfo = getCachedUserInfo();
|
||||||
|
|
||||||
|
if (userInfo?.isPluginTrial === true) {
|
||||||
|
// 试用用户,跳过邀请码验证,直接返回已验证
|
||||||
|
console.log('[ICHelperPanel] 试用用户,跳过邀请码验证');
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "invitationCodeStatus",
|
||||||
|
verified: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 正式用户,检查邀请码
|
||||||
const { InvitationService } = require("../services/invitationService");
|
const { InvitationService } = require("../services/invitationService");
|
||||||
const isVerified = await InvitationService.isVerified(context);
|
const isVerified = await InvitationService.isVerified(context);
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
@ -433,6 +446,36 @@ export async function showICHelperPanel(
|
|||||||
verified: isVerified
|
verified: isVerified
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "checkWelcomeModal":
|
||||||
|
// 检查是否需要显示欢迎弹窗
|
||||||
|
{
|
||||||
|
console.log('[ICHelperPanel] 收到 checkWelcomeModal 消息');
|
||||||
|
const showWelcome = context.globalState.get('showWelcomeModal');
|
||||||
|
console.log('[ICHelperPanel] showWelcomeModal 标记值:', showWelcome);
|
||||||
|
|
||||||
|
if (showWelcome) {
|
||||||
|
// 清除标记并显示欢迎弹窗
|
||||||
|
await context.globalState.update('showWelcomeModal', undefined);
|
||||||
|
console.log('[ICHelperPanel] ✅ 发送 showWelcomeModal 命令到前端');
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "showWelcomeModal"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('[ICHelperPanel] showWelcomeModal 标记为 false,不显示弹窗');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "checkTrialExpiration":
|
||||||
|
// 检查试用期是否过期
|
||||||
|
{
|
||||||
|
console.log('[ICHelperPanel] 收到 checkTrialExpiration 消息');
|
||||||
|
const { TrialExpirationService } = require("../services/trialExpirationService");
|
||||||
|
const trialService = new TrialExpirationService(context, panel);
|
||||||
|
const isExpired = await trialService.checkExpiration();
|
||||||
|
console.log('[ICHelperPanel] 试用期过期状态:', isExpired);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "verifyInvitationCode":
|
case "verifyInvitationCode":
|
||||||
// 验证邀请码
|
// 验证邀请码
|
||||||
|
|||||||
153
src/panels/WelcomePanel.ts
Normal file
153
src/panels/WelcomePanel.ts
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* 欢迎引导面板
|
||||||
|
* 功能:插件试用用户首次登录显示使用教程
|
||||||
|
* 依赖:vscode
|
||||||
|
* 使用场景:试用用户首次登录时显示
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
export class WelcomePanel {
|
||||||
|
public static currentPanel: WelcomePanel | undefined;
|
||||||
|
private readonly _panel: vscode.WebviewPanel;
|
||||||
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
|
private constructor(panel: vscode.WebviewPanel) {
|
||||||
|
this._panel = panel;
|
||||||
|
this._panel.webview.html = this.getHtmlContent();
|
||||||
|
|
||||||
|
// 监听来自 webview 的消息
|
||||||
|
this._panel.webview.onDidReceiveMessage(
|
||||||
|
(message) => {
|
||||||
|
if (message.command === 'close') {
|
||||||
|
this._panel.dispose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
this._disposables
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听关闭事件
|
||||||
|
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static render(context: vscode.ExtensionContext) {
|
||||||
|
// 避免重复显示
|
||||||
|
if (WelcomePanel.currentPanel) {
|
||||||
|
WelcomePanel.currentPanel._panel.reveal(vscode.ViewColumn.One);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const panel = vscode.window.createWebviewPanel(
|
||||||
|
'icCoderWelcome',
|
||||||
|
'欢迎使用 IC Coder',
|
||||||
|
vscode.ViewColumn.One,
|
||||||
|
{
|
||||||
|
enableScripts: true,
|
||||||
|
retainContextWhenHidden: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
WelcomePanel.currentPanel = new WelcomePanel(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHtmlContent(): string {
|
||||||
|
return `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>欢迎使用 IC Coder</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 40px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
background-color: var(--vscode-editor-background);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: var(--vscode-textLink-foreground);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.welcome-message {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
}
|
||||||
|
.step {
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 20px;
|
||||||
|
background: var(--vscode-editor-inactiveSelectionBackground);
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 4px solid var(--vscode-textLink-foreground);
|
||||||
|
}
|
||||||
|
.step h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: var(--vscode-textLink-foreground);
|
||||||
|
}
|
||||||
|
.step p {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🎉 欢迎使用 IC Coder!</h1>
|
||||||
|
<p class="welcome-message">
|
||||||
|
您已成功激活 15 天试用期,让我们开始探索 IC Coder 的强大功能吧!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>📝 步骤 1:打开聊天面板</h3>
|
||||||
|
<p>点击侧边栏的 IC Coder 图标,或使用命令面板搜索 "IC Coder: Open Chat"</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>💬 步骤 2:输入您的需求</h3>
|
||||||
|
<p>描述您想要生成的 Verilog 代码或需要帮助的问题,AI 将为您提供专业的解决方案</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>🔬 步骤 3:运行仿真</h3>
|
||||||
|
<p>使用 "生成 VCD" 命令运行 iverilog 仿真,并通过波形查看器查看仿真结果</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="button" onclick="closePanel()">开始使用</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const vscode = acquireVsCodeApi();
|
||||||
|
|
||||||
|
function closePanel() {
|
||||||
|
vscode.postMessage({ command: 'close' });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
WelcomePanel.currentPanel = undefined;
|
||||||
|
this._panel.dispose();
|
||||||
|
while (this._disposables.length) {
|
||||||
|
const disposable = this._disposables.pop();
|
||||||
|
if (disposable) {
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/services/trialExpirationService.ts
Normal file
62
src/services/trialExpirationService.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* 试用期过期检测服务
|
||||||
|
* 功能:检查插件试用用户是否过期
|
||||||
|
* 依赖:vscode, userService
|
||||||
|
* 使用场景:用户使用功能前检查是否过期
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { getCachedUserInfo } from './userService';
|
||||||
|
|
||||||
|
export class TrialExpirationService {
|
||||||
|
private context: vscode.ExtensionContext;
|
||||||
|
private panel?: vscode.WebviewPanel;
|
||||||
|
|
||||||
|
constructor(context: vscode.ExtensionContext, panel?: vscode.WebviewPanel) {
|
||||||
|
this.context = context;
|
||||||
|
this.panel = panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否过期
|
||||||
|
* @returns true=已过期,false=未过期
|
||||||
|
*/
|
||||||
|
public async checkExpiration(): Promise<boolean> {
|
||||||
|
const userInfo = getCachedUserInfo();
|
||||||
|
|
||||||
|
// 不是插件试用用户,不需要检查
|
||||||
|
if (!userInfo?.isPluginTrial) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有过期时间,不检查
|
||||||
|
if (!userInfo.pluginTrialExpiresAt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否过期
|
||||||
|
const now = Date.now();
|
||||||
|
if (now >= userInfo.pluginTrialExpiresAt) {
|
||||||
|
// 已过期
|
||||||
|
await this.handleExpired();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理过期逻辑
|
||||||
|
*/
|
||||||
|
private async handleExpired(): Promise<void> {
|
||||||
|
// 通知前端显示过期弹窗
|
||||||
|
if (this.panel) {
|
||||||
|
this.panel.webview.postMessage({
|
||||||
|
command: 'showExpiredModal'
|
||||||
|
});
|
||||||
|
console.log('[TrialExpirationService] 已通知前端显示过期弹窗');
|
||||||
|
} else {
|
||||||
|
console.warn('[TrialExpirationService] panel 未提供,无法显示过期弹窗');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,8 @@ import * as vscode from 'vscode';
|
|||||||
import { getStrangeLoopApiUrl, getConfig } from '../config/settings';
|
import { getStrangeLoopApiUrl, getConfig } from '../config/settings';
|
||||||
import type { UserInfoResponse, MembershipResponse, MultiMembershipVO, MembershipItemVO } from '../types/api';
|
import type { UserInfoResponse, MembershipResponse, MultiMembershipVO, MembershipItemVO } from '../types/api';
|
||||||
import { fetchBalanceWithToken, getCachedBalance } from './creditsService';
|
import { fetchBalanceWithToken, getCachedBalance } from './creditsService';
|
||||||
|
import { getIsPluginTrialFromToken } from '../utils/jwtUtils';
|
||||||
|
// 移除 WelcomePanel 导入,改用消息通知方式
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP 请求选项
|
* HTTP 请求选项
|
||||||
@ -117,6 +119,10 @@ export interface UserInfo {
|
|||||||
};
|
};
|
||||||
// Credits 余额
|
// Credits 余额
|
||||||
credits?: number;
|
credits?: number;
|
||||||
|
// 插件试用用户标识(从 JWT token 中提取)
|
||||||
|
isPluginTrial?: boolean;
|
||||||
|
// 试用到期时间(毫秒时间戳)
|
||||||
|
pluginTrialExpiresAt?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -226,6 +232,10 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
|
|||||||
try {
|
try {
|
||||||
console.log('[UserService] Token 已获取,正在获取用户信息、会员信息和余额...');
|
console.log('[UserService] Token 已获取,正在获取用户信息、会员信息和余额...');
|
||||||
|
|
||||||
|
// 从 token 中提取 ispluginTrial 标识
|
||||||
|
const isPluginTrial = getIsPluginTrialFromToken(token);
|
||||||
|
console.log('[UserService] 从 token 中提取 ispluginTrial:', isPluginTrial);
|
||||||
|
|
||||||
// 并行获取用户信息、会员信息和余额
|
// 并行获取用户信息、会员信息和余额
|
||||||
const [userInfo, membershipInfo, credits] = await Promise.all([
|
const [userInfo, membershipInfo, credits] = await Promise.all([
|
||||||
getUserInfo(token),
|
getUserInfo(token),
|
||||||
@ -238,6 +248,12 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将 token 中的 ispluginTrial 标识添加到用户信息
|
||||||
|
if (isPluginTrial !== null) {
|
||||||
|
userInfo.isPluginTrial = isPluginTrial;
|
||||||
|
console.log('[UserService] 已将 ispluginTrial 添加到用户信息:', isPluginTrial);
|
||||||
|
}
|
||||||
|
|
||||||
// 添加 Credits 余额到用户信息
|
// 添加 Credits 余额到用户信息
|
||||||
console.log('[UserService] 获取到的 Credits 余额:', credits);
|
console.log('[UserService] 获取到的 Credits 余额:', credits);
|
||||||
if (credits !== null) {
|
if (credits !== null) {
|
||||||
@ -313,6 +329,34 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
|
|||||||
// 保存到持久化存储
|
// 保存到持久化存储
|
||||||
await saveUserInfo(userInfo);
|
await saveUserInfo(userInfo);
|
||||||
|
|
||||||
|
// 判断是否是插件试用用户
|
||||||
|
console.log('[UserService] 检查用户类型,isPluginTrial:', userInfo.isPluginTrial);
|
||||||
|
console.log('[UserService] extensionContext 是否存在:', !!extensionContext);
|
||||||
|
|
||||||
|
if (userInfo.isPluginTrial === true) {
|
||||||
|
// 插件试用用户:标记需要显示欢迎弹窗
|
||||||
|
const hasWelcomed = extensionContext?.globalState.get('pluginTrialWelcomed');
|
||||||
|
console.log('[UserService] 是否已显示过欢迎弹窗:', hasWelcomed);
|
||||||
|
|
||||||
|
if (!hasWelcomed && extensionContext) {
|
||||||
|
// 设置标记,让聊天面板显示欢迎弹窗
|
||||||
|
await extensionContext.globalState.update('showWelcomeModal', true);
|
||||||
|
await extensionContext.globalState.update('pluginTrialWelcomed', true);
|
||||||
|
console.log('[UserService] ✅ 已设置欢迎弹窗标记 showWelcomeModal=true');
|
||||||
|
|
||||||
|
// 验证标记是否设置成功
|
||||||
|
const checkMark = extensionContext.globalState.get('showWelcomeModal');
|
||||||
|
console.log('[UserService] 验证标记:', checkMark);
|
||||||
|
} else if (!extensionContext) {
|
||||||
|
console.error('[UserService] ❌ extensionContext 为 null,无法设置标记');
|
||||||
|
} else {
|
||||||
|
console.log('[UserService] 已经显示过欢迎弹窗,跳过');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 正式用户:显示邀请码弹窗(现有逻辑)
|
||||||
|
console.log('[UserService] 正式用户登录,将在面板中检查邀请码');
|
||||||
|
}
|
||||||
|
|
||||||
return userInfo;
|
return userInfo;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[UserService] 获取用户信息失败:', error);
|
console.error('[UserService] 获取用户信息失败:', error);
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export interface JwtPayload {
|
|||||||
user_id?: number; // 用户ID (下划线命名)
|
user_id?: number; // 用户ID (下划线命名)
|
||||||
exp?: number; // 过期时间
|
exp?: number; // 过期时间
|
||||||
iat?: number; // 签发时间
|
iat?: number; // 签发时间
|
||||||
|
ispluginTrial?: boolean; // 是否是插件试用用户
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,3 +103,24 @@ export function isTokenExpired(
|
|||||||
|
|
||||||
return isExpired;
|
return isExpired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 JWT token 中获取 ispluginTrial 标识
|
||||||
|
* @param token JWT token
|
||||||
|
* @returns true=插件试用用户,false=正式用户,null=无法判断
|
||||||
|
*/
|
||||||
|
export function getIsPluginTrialFromToken(token: string): boolean | null {
|
||||||
|
const payload = parseJwtPayload(token);
|
||||||
|
if (!payload) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 ispluginTrial 字段
|
||||||
|
if (payload.ispluginTrial !== undefined) {
|
||||||
|
console.log("[JWT] 从 token 中获取到 ispluginTrial:", payload.ispluginTrial);
|
||||||
|
return payload.ispluginTrial === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[JWT] token 中没有 ispluginTrial 字段,判定为正式用户");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import {
|
|||||||
} from "../services/creditsService";
|
} from "../services/creditsService";
|
||||||
import { optimizePrompt } from "../services/promptOptimizeService";
|
import { optimizePrompt } from "../services/promptOptimizeService";
|
||||||
import { NotificationService } from "../services/notificationService";
|
import { NotificationService } from "../services/notificationService";
|
||||||
|
import { TrialExpirationService } from "../services/trialExpirationService";
|
||||||
|
|
||||||
import type { RunMode, ServiceTier } from "../types/api";
|
import type { RunMode, ServiceTier } from "../types/api";
|
||||||
|
|
||||||
@ -124,6 +125,21 @@ export async function handleUserMessage(
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查试用期是否过期
|
||||||
|
const trialService = new TrialExpirationService(context, panel);
|
||||||
|
const isExpired = await trialService.checkExpiration();
|
||||||
|
if (isExpired) {
|
||||||
|
console.warn("[MessageHandler] 试用期已过期,阻止发送");
|
||||||
|
|
||||||
|
// 恢复输入状态
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "updateSegments",
|
||||||
|
segments: [],
|
||||||
|
isComplete: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录用户消息到历史(允许失败,不阻塞主流程)
|
// 记录用户消息到历史(允许失败,不阻塞主流程)
|
||||||
|
|||||||
218
src/views/expiredModal.ts
Normal file
218
src/views/expiredModal.ts
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* 试用期过期弹窗
|
||||||
|
* 功能:在聊天面板内显示过期提醒模态窗口
|
||||||
|
* 依赖:无
|
||||||
|
* 使用场景:试用用户过期时在聊天面板内显示
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取过期弹窗的 HTML 内容
|
||||||
|
*/
|
||||||
|
export function getExpiredModalContent(logoUri?: string): string {
|
||||||
|
return `
|
||||||
|
<!-- 过期弹窗 -->
|
||||||
|
<div id="expiredModal" class="expired-modal" style="display: none;">
|
||||||
|
<div class="expired-modal-overlay"></div>
|
||||||
|
<div class="expired-modal-content">
|
||||||
|
${logoUri ? `<img src="${logoUri}" class="expired-logo-corner" alt="IC Coder" />` : ""}
|
||||||
|
|
||||||
|
<div class="expired-modal-header">
|
||||||
|
<div class="expired-icon">⏰</div>
|
||||||
|
<h2>您的试用期已到期</h2>
|
||||||
|
<p class="expired-modal-subtitle">感谢您使用 IC Coder!您的 15 天试用期已结束。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="expired-modal-body">
|
||||||
|
<p class="expired-message">如需继续使用,请联系我们获取正式版本。</p>
|
||||||
|
|
||||||
|
<button id="expiredContactBtn" class="expired-btn expired-btn-primary">
|
||||||
|
<span>联系我们</span>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||||
|
<path d="M6 3L11 8L6 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取过期弹窗的 CSS 样式
|
||||||
|
*/
|
||||||
|
export function getExpiredModalStyles(): string {
|
||||||
|
return `
|
||||||
|
/* 过期弹窗样式 */
|
||||||
|
.expired-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
font-family: var(--vscode-font-family, "Segoe UI", Tahoma, Geneva, Verdana, sans-serif);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-modal-content {
|
||||||
|
position: relative;
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
border: 1px solid var(--vscode-widget-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 450px;
|
||||||
|
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
||||||
|
animation: modalSlideIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-logo-corner {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 24px;
|
||||||
|
height: 40px;
|
||||||
|
width: auto;
|
||||||
|
opacity: 0.9;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-modal-header {
|
||||||
|
padding: 60px 32px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-modal-header h2 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vscode-errorForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-modal-subtitle {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-modal-body {
|
||||||
|
padding: 0 32px 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-message {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
margin: 20px 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
transition: all 0.2s;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-btn:hover {
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取过期弹窗的 JavaScript 逻辑
|
||||||
|
*/
|
||||||
|
export function getExpiredModalScript(): string {
|
||||||
|
return `
|
||||||
|
// 过期弹窗逻辑
|
||||||
|
(function() {
|
||||||
|
const modal = document.getElementById('expiredModal');
|
||||||
|
const contactBtn = document.getElementById('expiredContactBtn');
|
||||||
|
const overlay = modal?.querySelector('.expired-modal-overlay');
|
||||||
|
|
||||||
|
// 显示过期弹窗
|
||||||
|
window.showExpiredModal = function() {
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 隐藏过期弹窗
|
||||||
|
window.hideExpiredModal = function() {
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击"联系我们"按钮
|
||||||
|
if (contactBtn) {
|
||||||
|
contactBtn.addEventListener('click', function() {
|
||||||
|
// 可以打开联系页面
|
||||||
|
// window.open('https://iccoder.com/contact', '_blank');
|
||||||
|
hideExpiredModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击遮罩层关闭弹窗
|
||||||
|
if (overlay) {
|
||||||
|
overlay.addEventListener('click', function() {
|
||||||
|
hideExpiredModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止点击弹窗内容时关闭
|
||||||
|
const content = modal?.querySelector('.expired-modal-content');
|
||||||
|
if (content) {
|
||||||
|
content.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听来自后端的消息
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
const message = event.data;
|
||||||
|
if (message.command === 'showExpiredModal') {
|
||||||
|
showExpiredModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
@ -339,12 +339,14 @@ export function getInputAreaScript(): string {
|
|||||||
if (messageInput) {
|
if (messageInput) {
|
||||||
messageInput.addEventListener('input', autoResizeTextarea);
|
messageInput.addEventListener('input', autoResizeTextarea);
|
||||||
|
|
||||||
// 监听点击事件,检测工作区状态和邀请码验证状态
|
// 监听点击事件,检测工作区状态、试用期过期和邀请码验证状态
|
||||||
messageInput.addEventListener('focus', () => {
|
messageInput.addEventListener('focus', () => {
|
||||||
if (!hasCheckedWorkspace) {
|
if (!hasCheckedWorkspace) {
|
||||||
hasCheckedWorkspace = true;
|
hasCheckedWorkspace = true;
|
||||||
vscode.postMessage({ command: 'checkWorkspace' });
|
vscode.postMessage({ command: 'checkWorkspace' });
|
||||||
}
|
}
|
||||||
|
// 检查试用期是否过期
|
||||||
|
vscode.postMessage({ command: 'checkTrialExpiration' });
|
||||||
// 检查邀请码验证状态
|
// 检查邀请码验证状态
|
||||||
vscode.postMessage({ command: 'checkInvitationCode' });
|
vscode.postMessage({ command: 'checkInvitationCode' });
|
||||||
});
|
});
|
||||||
|
|||||||
@ -30,6 +30,16 @@ import {
|
|||||||
getInvitationModalStyles,
|
getInvitationModalStyles,
|
||||||
getInvitationModalScript,
|
getInvitationModalScript,
|
||||||
} from "./invitationModal";
|
} from "./invitationModal";
|
||||||
|
import {
|
||||||
|
getWelcomeModalContent,
|
||||||
|
getWelcomeModalStyles,
|
||||||
|
getWelcomeModalScript,
|
||||||
|
} from "./welcomeModal";
|
||||||
|
import {
|
||||||
|
getExpiredModalContent,
|
||||||
|
getExpiredModalStyles,
|
||||||
|
getExpiredModalScript,
|
||||||
|
} from "./expiredModal";
|
||||||
/**
|
/**
|
||||||
* 获取 WebView 面板的 HTML 内容
|
* 获取 WebView 面板的 HTML 内容
|
||||||
*/
|
*/
|
||||||
@ -100,6 +110,8 @@ export function getWebviewContent(
|
|||||||
${getProgressBarStyles()}
|
${getProgressBarStyles()}
|
||||||
${getInputAreaStyles()}
|
${getInputAreaStyles()}
|
||||||
${getInvitationModalStyles()}
|
${getInvitationModalStyles()}
|
||||||
|
${getWelcomeModalStyles()}
|
||||||
|
${getExpiredModalStyles()}
|
||||||
|
|
||||||
.file-editor-section {
|
.file-editor-section {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
@ -466,6 +478,8 @@ export function getWebviewContent(
|
|||||||
${getConversationHistoryBarContent()}
|
${getConversationHistoryBarContent()}
|
||||||
${getProgressBarContent()}
|
${getProgressBarContent()}
|
||||||
${getInvitationModalContent(qrCodeUri, logoUri)}
|
${getInvitationModalContent(qrCodeUri, logoUri)}
|
||||||
|
${getWelcomeModalContent(logoUri)}
|
||||||
|
${getExpiredModalContent(logoUri)}
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div style="display: flex; align-items: center; justify-content: center;">
|
<div style="display: flex; align-items: center; justify-content: center;">
|
||||||
<img src="${logoUri}" alt="IC Coder" style="max-width: 100%; height: auto; max-height: 80px;" />
|
<img src="${logoUri}" alt="IC Coder" style="max-width: 100%; height: auto; max-height: 80px;" />
|
||||||
@ -873,6 +887,8 @@ export function getWebviewContent(
|
|||||||
${getProgressBarScript()}
|
${getProgressBarScript()}
|
||||||
${getInputAreaScript()}
|
${getInputAreaScript()}
|
||||||
${getInvitationModalScript()}
|
${getInvitationModalScript()}
|
||||||
|
${getWelcomeModalScript()}
|
||||||
|
${getExpiredModalScript()}
|
||||||
</script></body>
|
</script></body>
|
||||||
</html>`;
|
</html>`;
|
||||||
}
|
}
|
||||||
|
|||||||
261
src/views/welcomeModal.ts
Normal file
261
src/views/welcomeModal.ts
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
/**
|
||||||
|
* 欢迎弹窗(试用用户)
|
||||||
|
* 功能:在聊天面板内显示欢迎模态窗口
|
||||||
|
* 依赖:无
|
||||||
|
* 使用场景:试用用户首次登录时在聊天面板内显示
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取欢迎弹窗的 HTML 内容
|
||||||
|
*/
|
||||||
|
export function getWelcomeModalContent(logoUri?: string): string {
|
||||||
|
return `
|
||||||
|
<!-- 欢迎弹窗 -->
|
||||||
|
<div id="welcomeModal" class="welcome-modal" style="display: none;">
|
||||||
|
<div class="welcome-modal-overlay"></div>
|
||||||
|
<div class="welcome-modal-content">
|
||||||
|
${logoUri ? `<img src="${logoUri}" class="welcome-logo-corner" alt="IC Coder" />` : ""}
|
||||||
|
|
||||||
|
<div class="welcome-modal-header">
|
||||||
|
<div class="welcome-icon">🎉</div>
|
||||||
|
<h2>欢迎使用 IC Coder!</h2>
|
||||||
|
<p class="welcome-modal-subtitle">您已成功激活 15 天试用期,让我们开始探索 IC Coder 的强大功能吧!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="welcome-modal-body">
|
||||||
|
<div class="welcome-step">
|
||||||
|
<div class="welcome-step-icon">📝</div>
|
||||||
|
<div class="welcome-step-content">
|
||||||
|
<h3>步骤 1:打开聊天面板</h3>
|
||||||
|
<p>点击侧边栏的 IC Coder 图标,或使用命令面板搜索 "IC Coder: Open Chat"</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="welcome-step">
|
||||||
|
<div class="welcome-step-icon">💬</div>
|
||||||
|
<div class="welcome-step-content">
|
||||||
|
<h3>步骤 2:输入您的需求</h3>
|
||||||
|
<p>描述您想要生成的 Verilog 代码或需要帮助的问题,AI 将为您提供专业的解决方案</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="welcome-step">
|
||||||
|
<div class="welcome-step-icon">🔬</div>
|
||||||
|
<div class="welcome-step-content">
|
||||||
|
<h3>步骤 3:运行仿真</h3>
|
||||||
|
<p>使用 "生成 VCD" 命令运行 iverilog 仿真,并通过波形查看器查看仿真结果</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="welcomeStartBtn" class="welcome-btn welcome-btn-primary">
|
||||||
|
<span>开始使用</span>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||||
|
<path d="M6 3L11 8L6 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取欢迎弹窗的 CSS 样式
|
||||||
|
*/
|
||||||
|
export function getWelcomeModalStyles(): string {
|
||||||
|
return `
|
||||||
|
/* 欢迎弹窗样式 */
|
||||||
|
.welcome-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
font-family: var(--vscode-font-family, "Segoe UI", Tahoma, Geneva, Verdana, sans-serif);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-modal-content {
|
||||||
|
position: relative;
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
border: 1px solid var(--vscode-widget-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
||||||
|
animation: modalSlideIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-logo-corner {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 24px;
|
||||||
|
height: 40px;
|
||||||
|
width: auto;
|
||||||
|
opacity: 0.9;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-modal-header {
|
||||||
|
padding: 60px 32px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-modal-header h2 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-modal-subtitle {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-modal-body {
|
||||||
|
padding: 0 32px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-step {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--vscode-editor-inactiveSelectionBackground);
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 4px solid var(--vscode-textLink-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-step-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-step-content h3 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vscode-textLink-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-step-content p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
transition: all 0.2s;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-btn:hover {
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取欢迎弹窗的 JavaScript 逻辑
|
||||||
|
*/
|
||||||
|
export function getWelcomeModalScript(): string {
|
||||||
|
return `
|
||||||
|
// 欢迎弹窗逻辑
|
||||||
|
(function() {
|
||||||
|
const modal = document.getElementById('welcomeModal');
|
||||||
|
const startBtn = document.getElementById('welcomeStartBtn');
|
||||||
|
const overlay = modal?.querySelector('.welcome-modal-overlay');
|
||||||
|
|
||||||
|
// 显示欢迎弹窗
|
||||||
|
window.showWelcomeModal = function() {
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 隐藏欢迎弹窗
|
||||||
|
window.hideWelcomeModal = function() {
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击"开始使用"按钮
|
||||||
|
if (startBtn) {
|
||||||
|
startBtn.addEventListener('click', function() {
|
||||||
|
hideWelcomeModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击遮罩层关闭弹窗
|
||||||
|
if (overlay) {
|
||||||
|
overlay.addEventListener('click', function() {
|
||||||
|
hideWelcomeModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止点击弹窗内容时关闭
|
||||||
|
const content = modal?.querySelector('.welcome-modal-content');
|
||||||
|
if (content) {
|
||||||
|
content.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听来自后端的消息
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
const message = event.data;
|
||||||
|
if (message.command === 'showWelcomeModal') {
|
||||||
|
showWelcomeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面加载时检查是否需要显示欢迎弹窗
|
||||||
|
vscode.postMessage({ command: 'checkWelcomeModal' });
|
||||||
|
})();
|
||||||
|
`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user