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