HelpPage.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. import { Children, isValidElement, useEffect, useMemo, useState, type ReactNode } from 'react'
  2. import {
  3. ArrowLeft,
  4. ChevronDown,
  5. ChevronUp,
  6. FileQuestion,
  7. Layers,
  8. MessageCircle,
  9. MessageSquare,
  10. Search,
  11. UploadCloud,
  12. UserCheck,
  13. Video,
  14. } from 'lucide-react'
  15. import { Link } from 'react-router-dom'
  16. import { ContactModal } from '../components/ContactModal'
  17. import { Brand } from '../components/Brand'
  18. import { Footer } from '../components/Footer'
  19. import { useAuth } from '../AuthContext'
  20. interface FAQItem {
  21. id: string
  22. category: 'getting-started' | 'materials' | 'production' | 'account'
  23. question: string
  24. answer: ReactNode
  25. }
  26. const FEATURE_LIST = [
  27. { icon: Layers, title: '结构化大纲编排', description: '支持按集管理课程知识点、逐字稿讲义与预估时长,告别混乱的文档传输与版本断层。' },
  28. { icon: UserCheck, title: '数字讲师资产库', description: '个人和组织工作区分别管理数字讲师。课程可选择本工作区的一位讲师。' },
  29. { icon: UploadCloud, title: '全格式课件归集', description: 'PPT、PDF、教学讲义集中云端归档,自动匹配分集内容,让每一次制作都有迹可循。' },
  30. { icon: Video, title: '专业制作与成果交付', description: '一键提交进入流水线制作,在线实时跟踪制作状态,成片交付支持在线即时预览与下载。' },
  31. ]
  32. const WORKFLOW_LIST = [
  33. { step: '01', title: '创建课程', description: '录入课程名称、分类和简介,或直接选择系统提供的官方范例模板一键快速生成。' },
  34. { step: '02', title: '编排大纲与台词', description: '添加分集,填写每集知识主题与口播讲义,设定课程节奏。' },
  35. { step: '03', title: '选择讲师与上传课件', description: '从工作区选择数字讲师,上传参考照片、录音和配套课件;提交制作前补齐讲师照片和录音。' },
  36. { step: '04', title: '提交制作与交付', description: '确认无误后提交,顾问团队排期制作,完成后在工作区一键获取成品。' },
  37. ]
  38. const CATEGORIES = [
  39. { key: 'all', label: '全部问题' },
  40. { key: 'getting-started', label: '入门与创建' },
  41. { key: 'materials', label: '课件与素材规范' },
  42. { key: 'production', label: '制作与交付' },
  43. { key: 'account', label: '服务与支持' },
  44. ] as const
  45. const FAQ_LIST: FAQItem[] = [
  46. {
  47. id: 'what-is-xinghen',
  48. category: 'getting-started',
  49. question: '星痕·智能课程工坊是什么?它能为我做什么?',
  50. answer:
  51. '星痕是一个专为知识创作者、讲师和教研团队打造的数字化课程生产工坊。通过标准化的分集大纲编排、讲师形象资产归集与课件整理,星痕能将您的教案素材高效转化为高质量的音视频课件与数字化成品。',
  52. },
  53. {
  54. id: 'how-to-start',
  55. category: 'getting-started',
  56. question: '制作一门新课程的标准流程是怎样的?',
  57. answer: (
  58. <div>
  59. <p style={{ margin: '0 0 8px' }}>制作一门完整的课程通常包含以下 4 个步骤:</p>
  60. <ol style={{ margin: 0, paddingLeft: '20px', lineHeight: 1.8 }}>
  61. <li><strong>创建课程</strong>:填写课程主题、分类与简介,也可直接从官方模板一键生成;</li>
  62. <li><strong>编排分集大纲</strong>:添加各个分集的章节名称、预计时长与口播逐字稿;</li>
  63. <li><strong>配置讲师与上传素材</strong>:从工作区资产库选择一位数字讲师,上传照片、录音及配套 PPT 课件;照片和录音可稍后补齐,但提交制作前必须齐备;</li>
  64. <li><strong>提交制作</strong>:确认材料无误后点击“提交制作”,制作团队将跟进合成并交付最终视频。</li>
  65. </ol>
  66. </div>
  67. ),
  68. },
  69. {
  70. id: 'course-templates',
  71. category: 'getting-started',
  72. question: '新手可以如何快速体验?有预设模板吗?',
  73. answer:
  74. '在创建课程页面,我们提供了多种精选的官方课程模板(如企业培训、通识科普、专业讲座等)。使用示例后,可在课程页选择工作区数字讲师;模板不会自动生成可用于制作的讲师。',
  75. },
  76. {
  77. id: 'ppt-requirements',
  78. category: 'materials',
  79. question: '上传 PPT / 课件文件有什么格式和要求?',
  80. answer:
  81. '支持常见的 PPT、PPTX、PDF 以及压缩包文件。建议课件页面比例为 16:9(1920×1080 最佳),文字排版留有合适边距;若包含特殊字体或复杂排版动画,建议在提交前同步导出或上传一份 PDF 副本以便制作核对。',
  82. },
  83. {
  84. id: 'instructor-photo-guidelines',
  85. category: 'materials',
  86. question: '讲师照片与出镜形象有什么拍摄或上传规范?',
  87. answer: (
  88. <div>
  89. <p style={{ margin: '0 0 6px' }}>为保证最终合成的画质与真实感,推荐遵守以下要求:</p>
  90. <ul style={{ margin: 0, paddingLeft: '18px', lineHeight: 1.7 }}>
  91. <li><strong>背景要求</strong>:优先使用纯色背景(白底/绿幕/纯色墙面)或已抠图的透明背景 PNG;</li>
  92. <li><strong>姿态清晰</strong>:支持上传站姿、坐姿等高清照片,面部与服饰光线充足均匀,避免逆光与重度阴影;</li>
  93. <li><strong>格式大小</strong>:支持 JPG、PNG、WebP 等格式,单张建议在 5MB 以内。</li>
  94. </ul>
  95. </div>
  96. ),
  97. },
  98. {
  99. id: 'script-writing-tips',
  100. category: 'materials',
  101. question: '分集逐字稿(口播文案)怎么写更利于制作?',
  102. answer:
  103. '建议逐字稿使用通俗易懂的口语化表达,按讲解节奏合理分段并添加标点符号;对于生僻字、英文缩写或专业术语,可标注拼音或中文读音,以确保声音合成与讲解语调准确自然。',
  104. },
  105. {
  106. id: 'production-timeline',
  107. category: 'production',
  108. question: '课程提交制作后,通常需要多长时间交付?',
  109. answer:
  110. '提交制作后,课程会进入“等待制作”队列。通常 1~3 集的轻量课程在 1~3 个工作日内完成初版合成与质检;多集复杂课程将根据总时长与排期协商交付时间。您可以在控制台随时关注最新进度。',
  111. },
  112. {
  113. id: 'course-statuses-meaning',
  114. category: 'production',
  115. question: '课程的不同状态分别代表什么意思?',
  116. answer: (
  117. <div>
  118. <p style={{ margin: '0 0 6px' }}>课程生命周期包含以下状态:</p>
  119. <ul style={{ margin: 0, paddingLeft: '18px', lineHeight: 1.7 }}>
  120. <li><span className="status status--draft">草稿 DRAFT</span>:正在编辑完善阶段,可随时增删分集与素材;</li>
  121. <li><span className="status status--waiting_production">等待制作 WAITING</span>:已确认并提交,等待制作团队认领排期;</li>
  122. <li><span className="status status--in_production">制作中 IN PRODUCTION</span>:团队正在进行音视频渲染、剪辑与合成;</li>
  123. <li><span className="status status--completed">已完成 COMPLETED</span>:制作完成,所有分集交付物已上架供下载。</li>
  124. </ul>
  125. </div>
  126. ),
  127. },
  128. {
  129. id: 'edit-after-submit',
  130. category: 'production',
  131. question: '提交制作后如果发现材料有误,还能修改吗?',
  132. answer:
  133. '课程提交后工作区将进入锁定保护状态,以保证制作期间版本的一致性。如需紧急更新脚本或替换课件,可直接点击页面底部的“联系制作顾问”加入微信沟通群,告知客服进行素材补充或退回调整。',
  134. },
  135. {
  136. id: 'deliverables-download',
  137. category: 'production',
  138. question: '制作完成后我如何查看和下载最终成品?',
  139. answer:
  140. '课程状态变更为“已完成”后,进入该课程工作区即可看到绿色的「已交付成果」面板。您可以直接在线播放预览成片,或一键下载 MP4 高清视频文件及配套课件成果。',
  141. },
  142. {
  143. id: 'how-to-contact',
  144. category: 'account',
  145. question: '遇到问题如何联系官方制作顾问与技术支持?',
  146. answer:
  147. '您可以点击页面上的“联系顾问”按钮,扫描弹窗中的二维码加入「星痕课程制作沟通群」,将有专属制作顾问为您提供一对一咨询、进度追踪与课件定制支持。',
  148. },
  149. {
  150. id: 'profile-and-security',
  151. category: 'account',
  152. question: '如何修改我的联系人姓名或登录密码?',
  153. answer:
  154. '点击顶部导航栏右上角的个人账号名称(或前往「个人中心 /profile」),即可修改联系人姓名、微信号或登录密码。所属组织由组织管理员或平台管理员管理。',
  155. },
  156. ]
  157. function nodeToText(node: ReactNode): string {
  158. if (typeof node === 'string' || typeof node === 'number') return String(node)
  159. if (!isValidElement<{ children?: ReactNode }>(node)) return ''
  160. return Children.toArray(node.props.children).map(nodeToText).join(' ')
  161. }
  162. function matchesSearch(values: string[], keyword: string) {
  163. const terms = keyword.trim().toLocaleLowerCase().split(/\s+/).filter(Boolean)
  164. if (terms.length === 0) return true
  165. const content = values.join(' ').toLocaleLowerCase()
  166. return terms.every((term) => content.includes(term))
  167. }
  168. export function HelpPage() {
  169. const { user } = useAuth()
  170. const [activeCategory, setActiveCategory] = useState<string>('all')
  171. const [searchKeyword, setSearchKeyword] = useState<string>('')
  172. const [openId, setOpenId] = useState<string | null>('what-is-xinghen')
  173. const [showContact, setShowContact] = useState<boolean>(false)
  174. const toggleAccordion = (id: string) => {
  175. setOpenId((prev) => (prev === id ? null : id))
  176. }
  177. const isSearching = searchKeyword.trim().length > 0
  178. const filteredFeatures = useMemo(
  179. () => FEATURE_LIST.filter((item) => matchesSearch([item.title, item.description], searchKeyword)),
  180. [searchKeyword],
  181. )
  182. const filteredWorkflow = useMemo(
  183. () => WORKFLOW_LIST.filter((item) => matchesSearch([item.title, item.description], searchKeyword)),
  184. [searchKeyword],
  185. )
  186. const filteredFaqs = useMemo(() => FAQ_LIST.filter((item) => {
  187. const matchCategory = isSearching || activeCategory === 'all' || item.category === activeCategory
  188. return matchCategory && matchesSearch([item.question, nodeToText(item.answer)], searchKeyword)
  189. }), [activeCategory, isSearching, searchKeyword])
  190. const searchResultCount = filteredFeatures.length + filteredWorkflow.length + filteredFaqs.length
  191. useEffect(() => {
  192. if (isSearching && filteredFaqs.length > 0) setOpenId(filteredFaqs[0].id)
  193. }, [isSearching, searchKeyword])
  194. return (
  195. <div className="page help-page">
  196. <Link to="/" className="back-link">
  197. <ArrowLeft size={16} /> {user ? '返回我的课程' : '返回首页'}
  198. </Link>
  199. {/* 顶部介绍区 */}
  200. <section className="help-hero">
  201. <div className="help-hero-content">
  202. <h1>帮助与使用指南</h1>
  203. <p className="help-hero-desc">
  204. 了解星痕智能课程工坊的运作模式、素材规范与制作交付流程,让好课制作更轻松。
  205. </p>
  206. <div className="help-search-bar">
  207. <Search size={18} className="search-icon" />
  208. <input
  209. aria-label="搜索帮助页面内容"
  210. type="text"
  211. placeholder="搜索常见问题,例如:PPT要求、制作周期、讲师照片..."
  212. value={searchKeyword}
  213. onChange={(e) => setSearchKeyword(e.target.value)}
  214. />
  215. {searchKeyword && (
  216. <button
  217. type="button"
  218. className="clear-search-btn"
  219. onClick={() => setSearchKeyword('')}
  220. >
  221. 清空
  222. </button>
  223. )}
  224. </div>
  225. <div className="help-search-status" aria-live="polite">
  226. {isSearching && `找到 ${searchResultCount} 条与“${searchKeyword.trim()}”相关的内容`}
  227. </div>
  228. </div>
  229. </section>
  230. {/* 网站介绍与核心能力 */}
  231. {(!isSearching || filteredFeatures.length > 0) && <section className="help-section">
  232. <div className="help-section-header">
  233. <h2>认识星痕课程工坊</h2>
  234. <p>删去繁复,以工业级标准化流程助力数字化课程高效产出</p>
  235. </div>
  236. <div className={`help-feature-grid ${isSearching ? 'is-search-results' : ''}`}>
  237. {filteredFeatures.map(({ icon: Icon, title, description }) => <div className="help-feature-card" key={title}>
  238. <div className="feature-icon-box"><Icon size={22} /></div>
  239. <h3>{title}</h3><p>{description}</p>
  240. </div>)}
  241. </div>
  242. </section>}
  243. {/* 快速入门 4 步流程 */}
  244. {(!isSearching || filteredWorkflow.length > 0) && <section className="help-section">
  245. <div className="help-section-header">
  246. <h2>课程制作四步法</h2>
  247. <p>从创意构思到拿到成品成片,只需要清晰的四步</p>
  248. </div>
  249. <div className={`help-steps-grid ${isSearching ? 'is-search-results' : ''}`}>
  250. {filteredWorkflow.map(({ step, title, description }) => <div className="help-step-item" key={step}>
  251. <div className="step-badge">{step}</div>
  252. <div className="step-item-body"><h4>{title}</h4><p>{description}</p></div>
  253. </div>)}
  254. </div>
  255. </section>}
  256. {/* 常见问题 FAQ */}
  257. {(!isSearching || filteredFaqs.length > 0) && <section className="help-section">
  258. <div className="help-section-header">
  259. <h2>常见问题解答</h2>
  260. <p>遇到疑问?这里整理了大家最常咨询的操作与制作问题</p>
  261. </div>
  262. {/* 分类过滤标签 */}
  263. {!isSearching && <div className="faq-category-tabs">
  264. {CATEGORIES.map((cat) => (
  265. <button
  266. key={cat.key}
  267. type="button"
  268. className={`faq-tab-btn ${activeCategory === cat.key ? 'active' : ''}`}
  269. onClick={() => setActiveCategory(cat.key)}
  270. >
  271. {cat.label}
  272. </button>
  273. ))}
  274. </div>}
  275. {/* 问答列表 */}
  276. <div className="faq-accordion-list">
  277. {filteredFaqs.length === 0 ? (
  278. <div className="faq-empty-state">
  279. <FileQuestion size={36} />
  280. <h4>未找到匹配的问题</h4>
  281. <p>试着换个搜索关键词,或直接联系制作顾问获取支持。</p>
  282. </div>
  283. ) : (
  284. filteredFaqs.map((faq) => {
  285. const isOpen = openId === faq.id
  286. return (
  287. <div
  288. key={faq.id}
  289. className={`faq-accordion-item ${isOpen ? 'is-open' : ''}`}
  290. >
  291. <button
  292. type="button"
  293. className="faq-question-btn"
  294. onClick={() => toggleAccordion(faq.id)}
  295. aria-expanded={isOpen}
  296. >
  297. <span className="faq-question-text">{faq.question}</span>
  298. <span className="faq-chevron-icon">
  299. {isOpen ? <ChevronUp size={18} /> : <ChevronDown size={18} />}
  300. </span>
  301. </button>
  302. {isOpen && (
  303. <div className="faq-answer-content">
  304. <div className="faq-answer-inner">{faq.answer}</div>
  305. </div>
  306. )}
  307. </div>
  308. )
  309. })
  310. )}
  311. </div>
  312. </section>}
  313. {isSearching && searchResultCount === 0 && <div className="faq-empty-state help-search-empty">
  314. <FileQuestion size={36} />
  315. <h4>未找到相关内容</h4>
  316. <p>试着缩短关键词、使用其他说法,或清空搜索查看全部帮助内容。</p>
  317. <button type="button" className="secondary-button" onClick={() => setSearchKeyword('')}>查看全部内容</button>
  318. </div>}
  319. {/* 底部联系支持卡片 */}
  320. <section className="help-contact-banner">
  321. <div className="contact-banner-content">
  322. <div className="contact-banner-icon">
  323. <MessageSquare size={28} />
  324. </div>
  325. <div>
  326. <h3>还有其他疑问或需要定制支持?</h3>
  327. <p>
  328. 加入星痕专属制作沟通群,制作顾问与技术团队将为您提供 1 对 1 咨询与进度同步。
  329. </p>
  330. </div>
  331. </div>
  332. <div className="contact-banner-action">
  333. <button
  334. type="button"
  335. className="primary-button"
  336. onClick={() => setShowContact(true)}
  337. >
  338. <MessageCircle size={16} /> 联系制作顾问
  339. </button>
  340. </div>
  341. </section>
  342. {/* 联系顾问弹窗 */}
  343. {showContact && <ContactModal onClose={() => setShowContact(false)} />}
  344. </div>
  345. )
  346. }
  347. export function PublicHelpPage() {
  348. return (
  349. <div className="marketing-page auth-wrapper">
  350. <header className="marketing-nav public-help-nav">
  351. <Link to="/mkt" aria-label="星痕首页"><Brand /></Link>
  352. <div className="marketing-nav-actions">
  353. <Link className="marketing-login" to="/login">登录</Link>
  354. <Link className="primary-button small-button" to="/login?mode=register">免费注册</Link>
  355. </div>
  356. </header>
  357. <main className="app-main"><HelpPage /></main>
  358. <Footer />
  359. </div>
  360. )
  361. }