# 可试看功能实现文档 ## 📋 目录 1. [功能概述](#功能概述) 2. [技术架构](#技术架构) 3. [实现步骤](#实现步骤) 4. [代码示例](#代码示例) 5. [样式规范](#样式规范) 6. [常见问题](#常见问题) --- ## 功能概述 ### 功能描述 "可试看"功能允许特定课程在未购买或未解锁的情况下,通过iframe方式预览课程内容。该功能分为两个场景: 1. **课程直播间场景**:在课程列表中显示可试看标签,点击后在视频播放器区域内嵌显示课程内容 2. **课后作业场景**:在作业卡片上显示可试看标签,点击后全屏显示课程内容 ### 核心特性 - ✅ 视觉标签:醒目的"可试看"标签提示 - ✅ iframe内嵌:无缝内嵌外部网页内容 - ✅ 全屏支持:支持全屏观看模式 - ✅ 缩放适配:自动缩放适配显示区域 - ✅ 权限控制:精准控制哪些课程可试看 --- ## 技术架构 ### 架构流程图 ``` ┌─────────────────────────────────────────┐ │ 数据层 (Data Layer) │ │ ├─ canPreview: boolean │ │ ├─ previewUrl: string │ │ └─ isShowCase: boolean │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ 业务逻辑层 (Business Logic) │ │ ├─ 判断是否可试看 │ │ ├─ 控制UI展示状态 │ │ └─ 处理点击事件 │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ 展示层 (Presentation Layer) │ │ ├─ 可试看标签组件 │ │ ├─ iframe内嵌组件 │ │ └─ 交互控制组件 │ └─────────────────────────────────────────┘ ``` ### 技术栈 - **前端框架**: React 18+ - **UI组件库**: Arco Design - **样式方案**: CSS Modules / Less - **状态管理**: React Hooks (useState, useRef) --- ## 实现步骤 ### 步骤 1: 数据结构设计 #### 1.1 课程直播数据结构 ```javascript // 课程对象结构 const courseObj = { courseId: "course_001", courseName: "展会主题与品牌定位", teacherName: "张老师", date: "2025-09-15", unitName: "消费电子展品牌策划与执行", // 可试看相关字段 canPreview: true, // 是否可试看 previewUrl: "https://example.com/preview" // 试看页面URL }; ``` #### 1.2 课后作业数据结构 ```javascript // 作业对象结构 const homeworkItem = { id: 142, name: "展会主题与品牌定位", level: "completed", imageUrl: "https://example.com/poster.jpg", // 可试看相关字段 isShowCase: true // 是否可试看(作业场景) }; ``` ### 步骤 2: 数据标记逻辑 #### 2.1 在数据处理函数中添加标记 ```javascript // 示例:处理课程数据时添加可试看标记 const processCourseData = (rawData) => { const courses = rawData.map(item => { const courseObj = { courseId: item.id, courseName: item.title, // ... 其他字段 }; // 为特定课程添加可试看标记 if (shouldEnablePreview(item)) { courseObj.canPreview = true; courseObj.previewUrl = getPreviewUrl(item); } return courseObj; }); return courses; }; // 判断是否应该开启试看 const shouldEnablePreview = (item) => { // 示例:根据课程名称和单元名称判断 return item.title === "展会主题与品牌定位" && item.unitName === "消费电子展品牌策划与执行"; }; // 获取试看URL const getPreviewUrl = (item) => { // 可以从配置文件、数据库或API获取 const previewUrls = { "展会主题与品牌定位": "https://du9uay.github.io/zhanhui/" }; return previewUrls[item.title] || ""; }; ``` ### 步骤 3: UI组件实现 #### 3.1 可试看标签组件 ```jsx // PreviewBadge.jsx import React from 'react'; import './PreviewBadge.css'; const PreviewBadge = ({ type = 'course', // 'course' | 'homework' | 'unit' className = '' }) => { const classNames = { course: 'preview-badge-course', homework: 'preview-badge-homework', unit: 'preview-badge-unit' }; return ( 可试看 ); }; export default PreviewBadge; ``` **PreviewBadge.css** ```css .preview-badge { display: inline-block; font-weight: 600; white-space: nowrap; border-radius: 12px; } /* 课程列表中的标签 */ .preview-badge-course { position: absolute; left: 50%; top: 60%; transform: translate(-50%, -50%); padding: 4px 12px; background: #4080ff; color: #fff; font-size: 12px; z-index: 10; box-shadow: 0 3px 8px rgba(64, 128, 255, 0.3); } /* 作业卡片上的标签 */ .preview-badge-homework { padding: 2px 8px; font-size: 11px; color: #ff8c00; background: rgba(255, 140, 0, 0.1); border: 1px solid rgba(255, 140, 0, 0.3); margin-top: 2px; } /* 单元标题上的标签 */ .preview-badge-unit { margin-left: 8px; padding: 2px 8px; background: #4080ff; color: #fff; font-size: 11px; } ``` #### 3.2 iframe播放器组件 ```jsx // IframePlayer.jsx import React, { useState, useRef, useEffect } from 'react'; import './IframePlayer.css'; const IframePlayer = ({ url, title = '课程预览', zoom = 0.5, onClose }) => { const [isFullscreen, setIsFullscreen] = useState(false); const containerRef = useRef(null); // 全屏切换 const handleFullscreen = () => { const container = containerRef.current; if (!container) return; if (!isFullscreen) { // 进入全屏 if (container.requestFullscreen) { container.requestFullscreen(); } else if (container.webkitRequestFullscreen) { container.webkitRequestFullscreen(); } else if (container.mozRequestFullScreen) { container.mozRequestFullScreen(); } } else { // 退出全屏 if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } } }; // 监听全屏状态变化 useEffect(() => { const handleFullscreenChange = () => { setIsFullscreen( document.fullscreenElement === containerRef.current || document.webkitFullscreenElement === containerRef.current || document.mozFullScreenElement === containerRef.current ); }; document.addEventListener('fullscreenchange', handleFullscreenChange); document.addEventListener('webkitfullscreenchange', handleFullscreenChange); document.addEventListener('mozfullscreenchange', handleFullscreenChange); return () => { document.removeEventListener('fullscreenchange', handleFullscreenChange); document.removeEventListener('webkitfullscreenchange', handleFullscreenChange); document.removeEventListener('mozfullscreenchange', handleFullscreenChange); }; }, []); return (