diff --git a/examples/Directory.Packages.props b/examples/Directory.Packages.props
index c37a18d57..4a30fac0b 100644
--- a/examples/Directory.Packages.props
+++ b/examples/Directory.Packages.props
@@ -2,7 +2,7 @@
true
- 4.0.0-rc.51
+ 4.0.0
diff --git a/handbook/docusaurus.config.ts b/handbook/docusaurus.config.ts
index edd1892f1..15dccbe3c 100644
--- a/handbook/docusaurus.config.ts
+++ b/handbook/docusaurus.config.ts
@@ -41,10 +41,10 @@ const config = {
showLastUpdateAuthor: true,
sidebarCollapsible: true,
sidebarCollapsed: true,
- lastVersion: '3.1',
+ lastVersion: 'current',
versions: {
current: {
- label: '4.0-rc',
+ label: '4.0',
path: '/current',
},
// next: {
diff --git a/handbook/src/components/CoursePromotionModal.module.css b/handbook/src/components/CoursePromotionModal.module.css
new file mode 100644
index 000000000..0cde3486d
--- /dev/null
+++ b/handbook/src/components/CoursePromotionModal.module.css
@@ -0,0 +1,292 @@
+/* 遮罩层 */
+.overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 9998;
+ backdrop-filter: blur(2px);
+}
+
+/* 弹窗容器 */
+.modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: white;
+ border-radius: 16px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+ z-index: 9999;
+ min-width: 320px;
+ max-width: 500px;
+ width: 90%;
+ animation: modalSlideIn 0.3s ease-out;
+}
+
+@keyframes modalSlideIn {
+ from {
+ opacity: 0;
+ transform: translate(-50%, -60%);
+ }
+ to {
+ opacity: 1;
+ transform: translate(-50%, -50%);
+ }
+}
+
+/* 头部 */
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px 24px 0;
+ border-bottom: none;
+}
+
+.title {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ color: #1a1a1a;
+ line-height: 1.4;
+}
+
+.closeButton {
+ background: none;
+ border: none;
+ font-size: 24px;
+ color: #999;
+ cursor: pointer;
+ padding: 4px;
+ border-radius: 4px;
+ transition: all 0.2s ease;
+}
+
+.closeButton:hover {
+ background: #f5f5f5;
+ color: #666;
+}
+
+/* 内容区域 */
+.content {
+ padding: 16px 24px;
+ text-align: center;
+ position: relative;
+}
+
+.badge {
+ position: absolute;
+ top: 0;
+ right: 24px;
+ background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
+ color: white;
+ padding: 4px 12px;
+ border-radius: 12px;
+ font-size: 12px;
+ font-weight: 600;
+ box-shadow: 0 2px 8px rgba(255, 107, 107, 0.3);
+}
+
+.icon {
+ font-size: 48px;
+ margin-bottom: 16px;
+ line-height: 1;
+}
+
+.message {
+ font-size: 16px;
+ line-height: 1.6;
+ color: #333;
+ margin: 0 0 20px;
+}
+
+.priceSection {
+ margin: 20px 0;
+ padding: 16px;
+ background: linear-gradient(135deg, #fff9e6 0%, #ffe6e6 100%);
+ border-radius: 12px;
+}
+
+.originalPrice {
+ font-size: 14px;
+ color: #999;
+ text-decoration: line-through;
+ margin-bottom: 8px;
+}
+
+.promotionPrice {
+ font-size: 18px;
+ color: #333;
+ font-weight: 600;
+}
+
+.price {
+ font-size: 32px;
+ color: #ff6b6b;
+ font-weight: 700;
+}
+
+.countdown {
+ margin: 16px 0;
+ padding: 12px;
+ background: #f8f9fa;
+ border-radius: 8px;
+ border: 1px solid #e9ecef;
+}
+
+.countdownLabel {
+ font-size: 14px;
+ color: #666;
+ margin-bottom: 8px;
+}
+
+.countdownTime {
+ font-size: 18px;
+ font-weight: 700;
+ color: #ff6b6b;
+ font-family: 'Courier New', monospace;
+}
+
+.description {
+ font-size: 14px;
+ line-height: 1.5;
+ color: #666;
+ margin: 12px 0 0;
+}
+
+/* 按钮区域 */
+.actions {
+ display: flex;
+ gap: 12px;
+ padding: 0 24px 24px;
+}
+
+.viewButton {
+ flex: 1;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-size: 15px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+}
+
+.viewButton:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
+}
+
+.viewButton:active {
+ transform: translateY(0);
+}
+
+.laterButton {
+ flex: 0.8;
+ background: #f8f9fa;
+ color: #666;
+ border: 1px solid #e9ecef;
+ padding: 12px 20px;
+ border-radius: 8px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.laterButton:hover {
+ background: #e9ecef;
+ color: #495057;
+}
+
+/* 暗色主题适配 */
+[data-theme='dark'] .modal {
+ background: #2a2a2a;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
+}
+
+[data-theme='dark'] .title {
+ color: #ffffff;
+}
+
+[data-theme='dark'] .message {
+ color: #e0e0e0;
+}
+
+[data-theme='dark'] .priceSection {
+ background: linear-gradient(135deg, #3a3a2a 0%, #3a2a2a 100%);
+}
+
+[data-theme='dark'] .promotionPrice {
+ color: #e0e0e0;
+}
+
+[data-theme='dark'] .countdown {
+ background: #404040;
+ border-color: #555;
+}
+
+[data-theme='dark'] .countdownLabel {
+ color: #b0b0b0;
+}
+
+[data-theme='dark'] .description {
+ color: #b0b0b0;
+}
+
+[data-theme='dark'] .closeButton {
+ color: #b0b0b0;
+}
+
+[data-theme='dark'] .closeButton:hover {
+ background: #404040;
+ color: #e0e0e0;
+}
+
+[data-theme='dark'] .laterButton {
+ background: #404040;
+ color: #b0b0b0;
+ border-color: #555;
+}
+
+[data-theme='dark'] .laterButton:hover {
+ background: #4a4a4a;
+ color: #e0e0e0;
+}
+
+/* 移动端适配 */
+@media (max-width: 480px) {
+ .modal {
+ margin: 20px;
+ width: calc(100% - 40px);
+ }
+
+ .title {
+ font-size: 16px;
+ }
+
+ .message {
+ font-size: 15px;
+ }
+
+ .price {
+ font-size: 28px;
+ }
+
+ .countdownTime {
+ font-size: 16px;
+ }
+
+ .actions {
+ flex-direction: column;
+ }
+
+ .laterButton {
+ flex: 1;
+ }
+}
diff --git a/handbook/src/components/CoursePromotionModal.tsx b/handbook/src/components/CoursePromotionModal.tsx
new file mode 100644
index 000000000..b99d38657
--- /dev/null
+++ b/handbook/src/components/CoursePromotionModal.tsx
@@ -0,0 +1,112 @@
+import React, { useState, useEffect } from 'react';
+import styles from './CoursePromotionModal.module.css';
+
+const CoursePromotionModal: React.FC = () => {
+ const [isVisible, setIsVisible] = useState(false);
+ const [timeLeft, setTimeLeft] = useState('');
+
+ useEffect(() => {
+ // 检查当前会话是否已经显示过弹窗(使用 sessionStorage 记录)
+ const hasShownModal = sessionStorage.getItem('course-promotion-modal-shown-2025-11');
+
+ if (!hasShownModal) {
+ // 延迟显示弹窗,让页面先加载完成
+ const timer = setTimeout(() => {
+ setIsVisible(true);
+ }, 1000);
+
+ return () => clearTimeout(timer);
+ }
+ }, []);
+
+ useEffect(() => {
+ // 计算倒计时
+ const updateCountdown = () => {
+ const endDate = new Date('2025-11-26T23:59:59');
+ const now = new Date();
+ const diff = endDate.getTime() - now.getTime();
+
+ if (diff <= 0) {
+ setTimeLeft('活动已结束');
+ return;
+ }
+
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
+ const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+ const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
+ const seconds = Math.floor((diff % (1000 * 60)) / 1000);
+
+ setTimeLeft(`${days}天 ${hours}小时 ${minutes}分 ${seconds}秒`);
+ };
+
+ updateCountdown();
+ const interval = setInterval(updateCountdown, 1000);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ const handleClose = () => {
+ setIsVisible(false);
+ // 记录当前会话已显示过弹窗,刷新页面后会重新显示
+ sessionStorage.setItem('course-promotion-modal-shown-2025-11', 'true');
+ };
+
+ const handleViewCourse = () => {
+ // 在新窗口打开课程链接
+ window.open('https://www.bilibili.com/cheese/play/ss489296905', '_blank');
+ handleClose();
+ };
+
+ if (!isVisible) return null;
+
+ return (
+ <>
+ {/* 遮罩层 */}
+
+
+ {/* 弹窗内容 */}
+
+
+
🎉 TouchSocket 4.0 正式版发布!
+
+
+
+
+
限时优惠
+
🎓
+
+ 为庆祝 TouchSocket 4.0 正式版发布,
+ 官方课程现已开启限时特惠!
+
+
+
+
原价: ¥358
+
限时优惠价: ¥198
+
+
+
+
⏰ 距离活动结束还剩:
+
{timeLeft}
+
+
+
+ 活动时间:2025.11.23 - 2025.11.26
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default CoursePromotionModal;
diff --git a/handbook/src/components/VotingModal.tsx b/handbook/src/components/VotingModal.tsx
index 51dd8f2b5..bbbb6d503 100644
--- a/handbook/src/components/VotingModal.tsx
+++ b/handbook/src/components/VotingModal.tsx
@@ -2,6 +2,10 @@ import React, { useState, useEffect } from 'react';
import styles from './VotingModal.module.css';
const VotingModal: React.FC = () => {
+ // 暂时禁用该弹窗
+ return null;
+
+ // eslint-disable-next-line no-unreachable
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
diff --git a/handbook/src/pages/upgrade/index.mdx b/handbook/src/pages/upgrade/index.mdx
index c0d8d7a4d..f09f359a2 100644
--- a/handbook/src/pages/upgrade/index.mdx
+++ b/handbook/src/pages/upgrade/index.mdx
@@ -10,11 +10,7 @@ import Highlight from '@site/src/components/Highlight.js';
## v4.0
-**更新日期:**
-
-暂未发布正式版,Nuget请勾选预发布版本可以尝鲜更新。
-
-目前4.0版本已进入RC(发布候选)阶段,一般API基本不会再变动。正式版计划在2025年12月左右,随`.NET10`发布正式版。
+**更新日期:** 2025.11.23
**更新描述:**
diff --git a/handbook/src/theme/Root.tsx b/handbook/src/theme/Root.tsx
index 0f83f41f0..beff7cf85 100644
--- a/handbook/src/theme/Root.tsx
+++ b/handbook/src/theme/Root.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import VotingModal from '../components/VotingModal';
+import CoursePromotionModal from '../components/CoursePromotionModal';
// 包装原始的 Root 组件并添加我们的弹窗
export default function Root({ children }) {
@@ -7,6 +8,7 @@ export default function Root({ children }) {
<>
{children}
+
>
);
}
\ No newline at end of file