@@ -32,6 +32,13 @@ graph TD
3232\`\`\`
3333` )
3434
35+ // 流式渲染相关状态
36+ const streamContent = ref <string >(' ' )
37+ const isStreaming = ref (false )
38+ const streamSpeed = ref (1 ) // 每次添加的字符数,可调整速度
39+ const streamInterval = ref (16 ) // 每次更新的时间间隔(毫秒)
40+ const showStreamSettings = ref (false ) // 是否显示流式渲染设置
41+
3542// 预加载 Monaco 编辑器和 worker
3643getUseMonaco ()
3744setKaTeXWorker (new KatexWorker ())
@@ -195,6 +202,56 @@ onMounted(() => {
195202 restoreFromUrl ()
196203 shareUrl .value = window .location .href
197204})
205+
206+ // 流式渲染函数
207+ let streamTimer: number | null = null
208+
209+ function startStreamRender() {
210+ if (isStreaming .value ) {
211+ // 如果正在流式渲染,停止它
212+ stopStreamRender ()
213+ return
214+ }
215+
216+ // 重置流式内容
217+ streamContent .value = ' '
218+ isStreaming .value = true
219+ let currentIndex = 0
220+ const fullText = input .value
221+
222+ const streamStep = () => {
223+ if (currentIndex >= fullText .length ) {
224+ // 完成流式渲染
225+ stopStreamRender ()
226+ return
227+ }
228+
229+ // 每次截取指定数量的字符
230+ const nextIndex = Math .min (currentIndex + streamSpeed .value , fullText .length )
231+ streamContent .value = fullText .slice (0 , nextIndex )
232+ currentIndex = nextIndex
233+
234+ // 继续下一次渲染,使用用户设置的时间间隔
235+ streamTimer = window .setTimeout (streamStep , streamInterval .value )
236+ }
237+
238+ streamStep ()
239+ }
240+
241+ function stopStreamRender() {
242+ if (streamTimer !== null ) {
243+ clearTimeout (streamTimer )
244+ streamTimer = null
245+ }
246+ isStreaming .value = false
247+ // 确保显示完整内容
248+ if (streamContent .value && streamContent .value !== input .value )
249+ streamContent .value = input .value
250+ }
251+
252+ function toggleStreamSettings() {
253+ showStreamSettings .value = ! showStreamSettings .value
254+ }
198255 </script >
199256
200257<template >
@@ -206,6 +263,20 @@ onMounted(() => {
206263 </h2 >
207264 <div class =" text-sm text-gray-500 flex items-center gap-3" >
208265 <span >左侧输入,右侧预览</span >
266+ <button
267+ class =" px-2 py-1 rounded text-sm flex items-center gap-2"
268+ :class =" isStreaming ? 'bg-red-600 text-white' : 'bg-purple-600 text-white'"
269+ @click =" startStreamRender"
270+ >
271+ {{ isStreaming ? '停止流式渲染' : '流式渲染' }}
272+ </button >
273+ <button
274+ class =" px-2 py-1 bg-gray-500 text-white rounded text-sm"
275+ :class =" { 'bg-gray-700': showStreamSettings }"
276+ @click =" toggleStreamSettings"
277+ >
278+ ⚙️ 设置
279+ </button >
209280 <button :disabled =" isWorking" class =" px-2 py-1 bg-blue-600 text-white rounded text-sm flex items-center gap-2" @click =" generateAndCopy" >
210281 生成并复制分享链接
211282 </button >
@@ -215,16 +286,66 @@ onMounted(() => {
215286 </div >
216287 </div >
217288
289+ <!-- 流式渲染设置面板 -->
290+ <div v-if =" showStreamSettings" class =" mb-4 p-4 bg-white dark:bg-gray-800 rounded border border-purple-300 dark:border-purple-700 shadow-md" >
291+ <h3 class =" text-sm font-semibold mb-3 text-gray-800 dark:text-gray-200" >
292+ 流式渲染设置
293+ </h3 >
294+ <div class =" grid grid-cols-1 md:grid-cols-2 gap-4" >
295+ <div >
296+ <label class =" block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1" >
297+ 每次截取字符数: <span class =" text-purple-600 dark:text-purple-400 font-semibold" >{{ streamSpeed }}</span >
298+ </label >
299+ <input
300+ v-model.number =" streamSpeed"
301+ type =" range"
302+ min =" 1"
303+ max =" 100"
304+ class =" w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
305+ >
306+ <div class =" flex justify-between text-xs text-gray-500 mt-1" >
307+ <span >1 (慢)</span >
308+ <span >100 (快)</span >
309+ </div >
310+ </div >
311+ <div >
312+ <label class =" block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1" >
313+ 更新间隔(毫秒): <span class =" text-purple-600 dark:text-purple-400 font-semibold" >{{ streamInterval }}ms</span >
314+ </label >
315+ <input
316+ v-model.number =" streamInterval"
317+ type =" range"
318+ min =" 10"
319+ max =" 500"
320+ step =" 10"
321+ class =" w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
322+ >
323+ <div class =" flex justify-between text-xs text-gray-500 mt-1" >
324+ <span >10ms (快)</span >
325+ <span >500ms (慢)</span >
326+ </div >
327+ </div >
328+ </div >
329+ <div class =" mt-3 p-2 bg-purple-50 dark:bg-purple-900/20 rounded text-xs text-gray-600 dark:text-gray-400" >
330+ 💡 提示:字符数越大或间隔越小,渲染速度越快
331+ </div >
332+ </div >
333+
218334 <div class =" grid grid-cols-1 md:grid-cols-2 gap-4" >
219335 <div >
220336 <label class =" block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200" >输入</label >
221337 <textarea v-model =" input" rows =" 18" class =" w-full p-3 rounded border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm text-gray-900 dark:text-gray-100 resize-none" />
222338 </div >
223339
224340 <div >
225- <label class =" block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200" >预览</label >
341+ <label class =" block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200" >
342+ 预览
343+ <span v-if =" streamContent" class =" ml-2 text-xs text-purple-600 dark:text-purple-400" >
344+ (流式渲染模式 {{ isStreaming ? '- 渲染中...' : '- 已完成' }})
345+ </span >
346+ </label >
226347 <div class =" max-w-none p-3 rounded border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 min-h-[14rem] overflow-auto" >
227- <MarkdownRender :content =" input" />
348+ <MarkdownRender :content =" streamContent || input" />
228349 </div >
229350 <div class =" mt-2 text-xs text-gray-500 break-words" >
230351 <template v-if =" tooLong " >
@@ -368,4 +489,35 @@ onMounted(() => {
368489 position : relative ;
369490 animation : renderingGlow 2s ease-in-out infinite ;
370491}
492+
493+ /* 滑块样式优化 */
494+ input [type = " range" ]::-webkit-slider-thumb {
495+ appearance : none ;
496+ width : 16px ;
497+ height : 16px ;
498+ border-radius : 50% ;
499+ background : #9333ea ;
500+ cursor : pointer ;
501+ transition : all 0.2s ;
502+ }
503+
504+ input [type = " range" ]::-webkit-slider-thumb :hover {
505+ background : #7c3aed ;
506+ transform : scale (1.2 );
507+ }
508+
509+ input [type = " range" ]::-moz-range-thumb {
510+ width : 16px ;
511+ height : 16px ;
512+ border-radius : 50% ;
513+ background : #9333ea ;
514+ cursor : pointer ;
515+ border : none ;
516+ transition : all 0.2s ;
517+ }
518+
519+ input [type = " range" ]::-moz-range-thumb :hover {
520+ background : #7c3aed ;
521+ transform : scale (1.2 );
522+ }
371523 </style >
0 commit comments