优化移动设备体验
This commit is contained in:
		
							parent
							
								
									9b36a08bbb
								
							
						
					
					
						commit
						c4c9372c0a
					
				| @ -14,6 +14,7 @@ import { EditorPreview } from './components/EditorPreview' | ||||
| import { MobileToolbar } from './components/MobileToolbar' | ||||
| import { MarkdownToolbar } from './components/MarkdownToolbar' | ||||
| import { type PreviewSize } from './constants' | ||||
| import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' | ||||
| 
 | ||||
| export default function WechatEditor() { | ||||
|   const { toast } = useToast() | ||||
| @ -356,50 +357,98 @@ export default function WechatEditor() { | ||||
|       /> | ||||
|        | ||||
|       <div className="flex-1 flex flex-col sm:flex-row overflow-hidden"> | ||||
|         <div  | ||||
|           ref={editorRef} | ||||
|           className={cn( | ||||
|             "editor-container bg-background transition-all duration-300 ease-in-out flex flex-col", | ||||
|             showPreview  | ||||
|               ? "h-[50%] sm:h-full sm:w-1/2 border-b sm:border-r"  | ||||
|               : "h-full w-full", | ||||
|             selectedTemplate && templates.find(t => t.id === selectedTemplate)?.styles | ||||
|           )} | ||||
|         > | ||||
|           <MarkdownToolbar onInsert={handleToolbarInsert} /> | ||||
|           <div className="flex-1"> | ||||
|             <textarea | ||||
|               ref={textareaRef} | ||||
|               value={value} | ||||
|               onChange={handleInput} | ||||
|               onKeyDown={handleKeyDown} | ||||
|               className="w-full h-full resize-none outline-none p-4 font-mono text-base leading-relaxed" | ||||
|               placeholder="开始写作..." | ||||
|               spellCheck={false} | ||||
|             /> | ||||
|           </div> | ||||
|         {/* Mobile Tabs */} | ||||
|         <div className="sm:hidden flex-1 flex flex-col"> | ||||
|           <Tabs defaultValue="editor" className="flex-1 flex flex-col"> | ||||
|             <TabsList className="grid w-full grid-cols-2"> | ||||
|               <TabsTrigger value="editor">编辑</TabsTrigger> | ||||
|               <TabsTrigger value="preview">预览</TabsTrigger> | ||||
|             </TabsList> | ||||
|             <TabsContent value="editor" className="flex-1 flex flex-col data-[state=inactive]:hidden"> | ||||
|               <div  | ||||
|                 ref={editorRef} | ||||
|                 className={cn( | ||||
|                   "editor-container bg-background flex-1 flex flex-col", | ||||
|                   selectedTemplate && templates.find(t => t.id === selectedTemplate)?.styles | ||||
|                 )} | ||||
|               > | ||||
|                 <MarkdownToolbar onInsert={handleToolbarInsert} /> | ||||
|                 <div className="flex-1"> | ||||
|                   <textarea | ||||
|                     ref={textareaRef} | ||||
|                     value={value} | ||||
|                     onChange={handleInput} | ||||
|                     onKeyDown={handleKeyDown} | ||||
|                     className="w-full h-full resize-none outline-none p-4 font-mono text-base leading-relaxed" | ||||
|                     placeholder="开始写作..." | ||||
|                     spellCheck={false} | ||||
|                   /> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </TabsContent> | ||||
|             <TabsContent value="preview" className="flex-1 flex flex-col data-[state=inactive]:hidden"> | ||||
|               <EditorPreview  | ||||
|                 previewRef={previewRef} | ||||
|                 selectedTemplate={selectedTemplate} | ||||
|                 previewSize={previewSize} | ||||
|                 isConverting={isConverting} | ||||
|                 previewContent={previewContent} | ||||
|                 onPreviewSizeChange={setPreviewSize} | ||||
|               /> | ||||
|             </TabsContent> | ||||
|           </Tabs> | ||||
|         </div> | ||||
| 
 | ||||
|         {/* Desktop Split View */} | ||||
|         <div className="hidden sm:flex flex-1 flex-row"> | ||||
|           <div  | ||||
|             ref={editorRef} | ||||
|             className={cn( | ||||
|               "editor-container bg-background transition-all duration-300 ease-in-out flex flex-col", | ||||
|               showPreview  | ||||
|                 ? "h-full w-1/2 border-r"  | ||||
|                 : "h-full w-full", | ||||
|               selectedTemplate && templates.find(t => t.id === selectedTemplate)?.styles | ||||
|             )} | ||||
|           > | ||||
|             <MarkdownToolbar onInsert={handleToolbarInsert} /> | ||||
|             <div className="flex-1"> | ||||
|               <textarea | ||||
|                 ref={textareaRef} | ||||
|                 value={value} | ||||
|                 onChange={handleInput} | ||||
|                 onKeyDown={handleKeyDown} | ||||
|                 className="w-full h-full resize-none outline-none p-4 font-mono text-base leading-relaxed" | ||||
|                 placeholder="开始写作..." | ||||
|                 spellCheck={false} | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|            | ||||
|           {showPreview && ( | ||||
|             <EditorPreview  | ||||
|               previewRef={previewRef} | ||||
|               selectedTemplate={selectedTemplate} | ||||
|               previewSize={previewSize} | ||||
|               isConverting={isConverting} | ||||
|               previewContent={previewContent} | ||||
|               onPreviewSizeChange={setPreviewSize} | ||||
|             /> | ||||
|           )} | ||||
|         </div> | ||||
|          | ||||
|         {showPreview && ( | ||||
|           <EditorPreview  | ||||
|             previewRef={previewRef} | ||||
|             selectedTemplate={selectedTemplate} | ||||
|             previewSize={previewSize} | ||||
|             isConverting={isConverting} | ||||
|             previewContent={previewContent} | ||||
|             onPreviewSizeChange={setPreviewSize} | ||||
|           /> | ||||
|         )} | ||||
|       </div> | ||||
| 
 | ||||
|       <MobileToolbar  | ||||
|         showPreview={showPreview} | ||||
|         isDraft={isDraft} | ||||
|         onPreviewToggle={() => setShowPreview(!showPreview)} | ||||
|         onSave={handleSave} | ||||
|         onCopy={copyContent} | ||||
|         onCopyPreview={handleCopy} | ||||
|       /> | ||||
|       {/* Only show mobile toolbar on desktop */} | ||||
|       <div className="hidden sm:block"> | ||||
|         <MobileToolbar  | ||||
|           showPreview={showPreview} | ||||
|           isDraft={isDraft} | ||||
|           onPreviewToggle={() => setShowPreview(!showPreview)} | ||||
|           onSave={handleSave} | ||||
|           onCopy={copyContent} | ||||
|           onCopyPreview={handleCopy} | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| import { Copy, Save, Smartphone, Settings, Image, Link, ZoomIn, ZoomOut } from 'lucide-react' | ||||
| import { Copy, Save, Settings, Image, Link } from 'lucide-react' | ||||
| import { cn } from '@/lib/utils' | ||||
| import { useState } from 'react' | ||||
| import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet' | ||||
| @ -24,61 +24,10 @@ export function MobileToolbar({ | ||||
|   onImageUpload, | ||||
|   onLinkInsert | ||||
| }: MobileToolbarProps) { | ||||
|   const [zoom, setZoom] = useState(100) | ||||
| 
 | ||||
|   const handleZoomIn = () => { | ||||
|     setZoom(prev => Math.min(prev + 10, 200)) | ||||
|   } | ||||
| 
 | ||||
|   const handleZoomOut = () => { | ||||
|     setZoom(prev => Math.max(prev - 10, 50)) | ||||
|   } | ||||
| 
 | ||||
|   const handleImageUpload = async () => { | ||||
|     if (!onImageUpload) return | ||||
| 
 | ||||
|     const input = document.createElement('input') | ||||
|     input.type = 'file' | ||||
|     input.accept = 'image/*' | ||||
|     input.onchange = async (e) => { | ||||
|       const file = (e.target as HTMLInputElement).files?.[0] | ||||
|       if (file) { | ||||
|         try { | ||||
|           const url = await onImageUpload(file) | ||||
|           // 处理上传成功后的图片URL
 | ||||
|         } catch (error) { | ||||
|           console.error('Image upload failed:', error) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     input.click() | ||||
|   } | ||||
| 
 | ||||
|   const handleLinkInsert = () => { | ||||
|     if (!onLinkInsert) return | ||||
|      | ||||
|     const url = window.prompt('请输入链接地址') | ||||
|     if (url) { | ||||
|       onLinkInsert(url) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="sm:hidden fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-t"> | ||||
|       <div className="flex items-center justify-between p-2"> | ||||
|         <div className="flex items-center gap-2"> | ||||
|           <button | ||||
|             onClick={onPreviewToggle} | ||||
|             className={cn( | ||||
|               "flex flex-col items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors relative", | ||||
|               showPreview  | ||||
|                 ? "text-primary" | ||||
|                 : "text-muted-foreground" | ||||
|             )} | ||||
|           > | ||||
|             <Smartphone className="h-5 w-5" /> | ||||
|             {showPreview ? '编辑' : '预览'} | ||||
|           </button> | ||||
|           <button | ||||
|             onClick={onSave} | ||||
|             className={cn( | ||||
| @ -92,69 +41,78 @@ export function MobileToolbar({ | ||||
|             保存 | ||||
|             {isDraft && <span className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />} | ||||
|           </button> | ||||
|           <button | ||||
|             onClick={onCopyPreview} | ||||
|             className="flex flex-col items-center gap-1 px-2 py-1 rounded-md text-xs text-muted-foreground transition-colors" | ||||
|           > | ||||
|             <Copy className="h-5 w-5" /> | ||||
|             复制 | ||||
|           </button> | ||||
|         </div> | ||||
| 
 | ||||
|         <div className="flex items-center gap-2"> | ||||
|           {showPreview && ( | ||||
|             <> | ||||
|         <Sheet> | ||||
|           <SheetTrigger asChild> | ||||
|             <button className="p-1 rounded-md text-muted-foreground"> | ||||
|               <Settings className="h-5 w-5" /> | ||||
|             </button> | ||||
|           </SheetTrigger> | ||||
|           <SheetContent side="bottom" className="h-[40vh]"> | ||||
|             <div className="grid grid-cols-4 gap-4 p-4"> | ||||
|               <button | ||||
|                 onClick={handleZoomOut} | ||||
|                 className="p-1 rounded-md text-muted-foreground" | ||||
|                 disabled={zoom <= 50} | ||||
|                 onClick={onCopy} | ||||
|                 className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80" | ||||
|               > | ||||
|                 <ZoomOut className="h-5 w-5" /> | ||||
|                 <Copy className="h-6 w-6" /> | ||||
|                 <span className="text-xs">复制源码</span> | ||||
|               </button> | ||||
|               <span className="text-xs text-muted-foreground">{zoom}%</span> | ||||
|               <button | ||||
|                 onClick={handleZoomIn} | ||||
|                 className="p-1 rounded-md text-muted-foreground" | ||||
|                 disabled={zoom >= 200} | ||||
|                 onClick={onCopyPreview} | ||||
|                 className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80" | ||||
|               > | ||||
|                 <ZoomIn className="h-5 w-5" /> | ||||
|                 <Copy className="h-6 w-6" /> | ||||
|                 <span className="text-xs">复制预览</span> | ||||
|               </button> | ||||
|             </> | ||||
|           )} | ||||
|            | ||||
|           <Sheet> | ||||
|             <SheetTrigger asChild> | ||||
|               <button className="p-1 rounded-md text-muted-foreground"> | ||||
|                 <Settings className="h-5 w-5" /> | ||||
|               </button> | ||||
|             </SheetTrigger> | ||||
|             <SheetContent side="bottom" className="h-[40vh]"> | ||||
|               <div className="grid grid-cols-4 gap-4 p-4"> | ||||
|               {onImageUpload && ( | ||||
|                 <button | ||||
|                   onClick={handleImageUpload} | ||||
|                   onClick={() => { | ||||
|                     const input = document.createElement('input') | ||||
|                     input.type = 'file' | ||||
|                     input.accept = 'image/*' | ||||
|                     input.onchange = async (e) => { | ||||
|                       const file = (e.target as HTMLInputElement).files?.[0] | ||||
|                       if (file && onImageUpload) { | ||||
|                         try { | ||||
|                           await onImageUpload(file) | ||||
|                         } catch (error) { | ||||
|                           console.error('Image upload failed:', error) | ||||
|                         } | ||||
|                       } | ||||
|                     } | ||||
|                     input.click() | ||||
|                   }} | ||||
|                   className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80" | ||||
|                 > | ||||
|                   <Image className="h-6 w-6" /> | ||||
|                   <span className="text-xs">插入图片</span> | ||||
|                 </button> | ||||
|               )} | ||||
|               {onLinkInsert && ( | ||||
|                 <button | ||||
|                   onClick={handleLinkInsert} | ||||
|                   onClick={() => { | ||||
|                     const url = window.prompt('请输入链接地址') | ||||
|                     if (url && onLinkInsert) { | ||||
|                       onLinkInsert(url) | ||||
|                     } | ||||
|                   }} | ||||
|                   className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80" | ||||
|                 > | ||||
|                   <Link className="h-6 w-6" /> | ||||
|                   <span className="text-xs">插入链接</span> | ||||
|                 </button> | ||||
|                 <button | ||||
|                   onClick={onCopy} | ||||
|                   className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80" | ||||
|                 > | ||||
|                   <Copy className="h-6 w-6" /> | ||||
|                   <span className="text-xs">复制源码</span> | ||||
|                 </button> | ||||
|                 <button | ||||
|                   onClick={onCopyPreview} | ||||
|                   className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80" | ||||
|                 > | ||||
|                   <Copy className="h-6 w-6" /> | ||||
|                   <span className="text-xs">复制预览</span> | ||||
|                 </button> | ||||
|               </div> | ||||
|             </SheetContent> | ||||
|           </Sheet> | ||||
|         </div> | ||||
|               )} | ||||
|             </div> | ||||
|           </SheetContent> | ||||
|         </Sheet> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 tianyaxiang
						tianyaxiang