优化移动设备体验
This commit is contained in:
parent
c4c9372c0a
commit
55d44228ec
@ -15,6 +15,8 @@ import { MobileToolbar } from './components/MobileToolbar'
|
|||||||
import { MarkdownToolbar } from './components/MarkdownToolbar'
|
import { MarkdownToolbar } from './components/MarkdownToolbar'
|
||||||
import { type PreviewSize } from './constants'
|
import { type PreviewSize } from './constants'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
|
import { WechatStylePicker } from '@/components/template/WechatStylePicker'
|
||||||
|
import { Copy } from 'lucide-react'
|
||||||
|
|
||||||
export default function WechatEditor() {
|
export default function WechatEditor() {
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
@ -339,6 +341,7 @@ export default function WechatEditor() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
|
<div className="hidden sm:block">
|
||||||
<EditorToolbar
|
<EditorToolbar
|
||||||
value={value}
|
value={value}
|
||||||
isDraft={isDraft}
|
isDraft={isDraft}
|
||||||
@ -355,25 +358,39 @@ export default function WechatEditor() {
|
|||||||
onPreviewToggle={() => setShowPreview(!showPreview)}
|
onPreviewToggle={() => setShowPreview(!showPreview)}
|
||||||
styleOptions={styleOptions}
|
styleOptions={styleOptions}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col sm:flex-row overflow-hidden">
|
<div className="flex-1 flex flex-col sm:flex-row overflow-hidden">
|
||||||
{/* Mobile Tabs */}
|
{/* Mobile View */}
|
||||||
<div className="sm:hidden flex-1 flex flex-col">
|
<div className="sm:hidden flex-1 flex flex-col">
|
||||||
|
<div className="flex items-center justify-between p-2 border-b bg-background">
|
||||||
|
<div className="flex-1 mr-2">
|
||||||
|
<WechatStylePicker
|
||||||
|
value={selectedTemplate}
|
||||||
|
onSelect={setSelectedTemplate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleCopy}
|
||||||
|
className="flex items-center justify-center gap-1 px-2 py-1 rounded-md text-xs text-primary hover:bg-muted transition-colors"
|
||||||
|
>
|
||||||
|
<Copy className="h-3.5 w-3.5" />
|
||||||
|
复制
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<Tabs defaultValue="editor" className="flex-1 flex flex-col">
|
<Tabs defaultValue="editor" className="flex-1 flex flex-col">
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
<TabsTrigger value="editor">编辑</TabsTrigger>
|
<TabsTrigger value="editor">编辑</TabsTrigger>
|
||||||
<TabsTrigger value="preview">预览</TabsTrigger>
|
<TabsTrigger value="preview">预览</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="editor" className="flex-1 flex flex-col data-[state=inactive]:hidden">
|
<TabsContent value="editor" className="flex-1 data-[state=inactive]:hidden">
|
||||||
<div
|
<div
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"editor-container bg-background flex-1 flex flex-col",
|
"h-full overflow-y-auto",
|
||||||
selectedTemplate && templates.find(t => t.id === selectedTemplate)?.styles
|
selectedTemplate && templates.find(t => t.id === selectedTemplate)?.styles
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MarkdownToolbar onInsert={handleToolbarInsert} />
|
|
||||||
<div className="flex-1">
|
|
||||||
<textarea
|
<textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
value={value}
|
value={value}
|
||||||
@ -384,9 +401,9 @@ export default function WechatEditor() {
|
|||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="preview" className="flex-1 flex flex-col data-[state=inactive]:hidden">
|
<TabsContent value="preview" className="flex-1 data-[state=inactive]:hidden">
|
||||||
|
<div className="h-full overflow-y-auto">
|
||||||
<EditorPreview
|
<EditorPreview
|
||||||
previewRef={previewRef}
|
previewRef={previewRef}
|
||||||
selectedTemplate={selectedTemplate}
|
selectedTemplate={selectedTemplate}
|
||||||
@ -395,6 +412,7 @@ export default function WechatEditor() {
|
|||||||
previewContent={previewContent}
|
previewContent={previewContent}
|
||||||
onPreviewSizeChange={setPreviewSize}
|
onPreviewSizeChange={setPreviewSize}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
@ -437,18 +455,6 @@ export default function WechatEditor() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { Copy, Save, Settings, Image, Link } from 'lucide-react'
|
import { Copy, Save, Settings } from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useState } from 'react'
|
|
||||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
|
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
|
||||||
|
import { WechatStylePicker } from '../../template/WechatStylePicker'
|
||||||
|
|
||||||
interface MobileToolbarProps {
|
interface MobileToolbarProps {
|
||||||
showPreview: boolean
|
showPreview: boolean
|
||||||
@ -10,8 +10,8 @@ interface MobileToolbarProps {
|
|||||||
onSave: () => void
|
onSave: () => void
|
||||||
onCopy: () => void
|
onCopy: () => void
|
||||||
onCopyPreview: () => void
|
onCopyPreview: () => void
|
||||||
onImageUpload?: (file: File) => Promise<string>
|
selectedTemplate: string
|
||||||
onLinkInsert?: (url: string) => void
|
onTemplateSelect: (template: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MobileToolbar({
|
export function MobileToolbar({
|
||||||
@ -21,99 +21,62 @@ export function MobileToolbar({
|
|||||||
onSave,
|
onSave,
|
||||||
onCopy,
|
onCopy,
|
||||||
onCopyPreview,
|
onCopyPreview,
|
||||||
onImageUpload,
|
selectedTemplate,
|
||||||
onLinkInsert
|
onTemplateSelect
|
||||||
}: MobileToolbarProps) {
|
}: MobileToolbarProps) {
|
||||||
return (
|
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="bg-background border-t">
|
||||||
<div className="flex items-center justify-between p-2">
|
<div className="flex flex-col">
|
||||||
<div className="flex items-center gap-2">
|
<div className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||||
<button
|
<div className="p-2">
|
||||||
onClick={onSave}
|
<WechatStylePicker
|
||||||
className={cn(
|
value={selectedTemplate}
|
||||||
"flex flex-col items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors relative",
|
onSelect={onTemplateSelect}
|
||||||
isDraft
|
/>
|
||||||
? "text-primary"
|
</div>
|
||||||
: "text-muted-foreground"
|
</div>
|
||||||
)}
|
<div className="flex items-center justify-between p-2 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||||
>
|
|
||||||
<Save className="h-5 w-5" />
|
|
||||||
保存
|
|
||||||
{isDraft && <span className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />}
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={onCopyPreview}
|
onClick={onCopyPreview}
|
||||||
className="flex flex-col items-center gap-1 px-2 py-1 rounded-md text-xs text-muted-foreground transition-colors"
|
className="flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-md text-sm bg-primary text-primary-foreground hover:bg-primary/90 transition-colors mr-2"
|
||||||
>
|
>
|
||||||
<Copy className="h-5 w-5" />
|
<Copy className="h-4 w-4" />
|
||||||
复制
|
复制预览
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<Sheet>
|
<Sheet>
|
||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<button className="p-1 rounded-md text-muted-foreground">
|
<button className="flex items-center justify-center p-2 rounded-md text-muted-foreground hover:bg-muted/80">
|
||||||
<Settings className="h-5 w-5" />
|
<Settings className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent side="bottom" className="h-[40vh]">
|
<SheetContent side="bottom" className="h-[30vh]">
|
||||||
<div className="grid grid-cols-4 gap-4 p-4">
|
<div className="flex flex-col gap-4 p-4">
|
||||||
|
<button
|
||||||
|
onClick={onSave}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-center gap-2 px-4 py-2 rounded-md text-sm transition-colors",
|
||||||
|
isDraft
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "bg-muted text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Save className="h-4 w-4" />
|
||||||
|
保存文章
|
||||||
|
{isDraft && <span className="w-2 h-2 bg-primary-foreground rounded-full" />}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onCopy}
|
onClick={onCopy}
|
||||||
className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80"
|
className="flex items-center justify-center gap-2 px-4 py-2 rounded-md bg-muted text-muted-foreground text-sm"
|
||||||
>
|
>
|
||||||
<Copy className="h-6 w-6" />
|
<Copy className="h-4 w-4" />
|
||||||
<span className="text-xs">复制源码</span>
|
复制源码
|
||||||
</button>
|
</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>
|
|
||||||
{onImageUpload && (
|
|
||||||
<button
|
|
||||||
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={() => {
|
|
||||||
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>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user