geo/frontend/app/(dashboard)/dashboard/content/editor/page.tsx

332 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { platformRulesApi, PlatformBrief, ContentValidationResponse } from "@/lib/api/platform-rules";
interface OptimizedContent {
title: string;
content: string;
platform: string;
tips: string[];
}
export default function ContentEditorPage() {
const [platforms, setPlatforms] = useState<PlatformBrief[]>([]);
const [selectedPlatform, setSelectedPlatform] = useState<string>("zhihu");
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [optimizedContent, setOptimizedContent] = useState<OptimizedContent | null>(null);
const [validationResult, setValidationResult] = useState<ContentValidationResponse | null>(null);
const [loading, setLoading] = useState(true);
const [validating, setValidating] = useState(false);
const [optimizing, setOptimizing] = useState(false);
useEffect(() => {
loadPlatforms();
}, []);
const loadPlatforms = async () => {
try {
setLoading(true);
const response = await platformRulesApi.listPlatforms();
setPlatforms(response.platforms);
} catch (error) {
console.error("加载平台列表失败:", error);
} finally {
setLoading(false);
}
};
const handleValidate = async () => {
if (!content || !title) return;
try {
setValidating(true);
const result = await platformRulesApi.validateContent(selectedPlatform, content, title);
setValidationResult(result);
} catch (error) {
console.error("验证失败:", error);
} finally {
setValidating(false);
}
};
const handleOptimize = async () => {
if (!content || !title) return;
try {
setOptimizing(true);
// 获取平台配置
const platformDetail = await platformRulesApi.getPlatformDetail(selectedPlatform);
const tips = await platformRulesApi.getOptimizationTips(selectedPlatform);
// 模拟优化处理实际应调用后端API
setOptimizedContent({
title: title,
content: content,
platform: selectedPlatform,
tips: tips.tips || [],
});
} catch (error) {
console.error("优化失败:", error);
} finally {
setOptimizing(false);
}
};
const handleCopyContent = (format: "html" | "markdown" | "text") => {
if (!optimizedContent) return;
let copyText = "";
switch (format) {
case "html":
// 简单的HTML格式化
copyText = `<h1>${optimizedContent.title}</h1>\n<p>${optimizedContent.content.replace(/\n\n/g, "</p><p>")}</p>`;
break;
case "markdown":
copyText = `# ${optimizedContent.title}\n\n${optimizedContent.content}`;
break;
case "text":
copyText = `${optimizedContent.title}\n\n${optimizedContent.content}`;
break;
}
navigator.clipboard.writeText(copyText);
};
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-muted-foreground">...</div>
</div>
);
}
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold tracking-tight"></h1>
<p className="text-muted-foreground">GEO内容</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* 左侧: 编辑区 */}
<Card className="lg:col-span-2">
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</div>
<Select value={selectedPlatform} onValueChange={setSelectedPlatform}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="选择平台" />
</SelectTrigger>
<SelectContent>
{platforms.map((p) => (
<SelectItem key={p.id} value={p.id}>
{p.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="title"></Label>
<Input
id="title"
placeholder="输入文章标题"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="content"></Label>
<Textarea
id="content"
placeholder="输入文章内容..."
className="min-h-[400px] font-mono text-sm"
value={content}
onChange={(e) => setContent(e.target.value)}
/>
</div>
<div className="flex gap-2">
<Button
variant="outline"
onClick={handleValidate}
disabled={validating || !content || !title}
>
{validating ? "验证中..." : "验证内容"}
</Button>
<Button
onClick={handleOptimize}
disabled={optimizing || !content || !title}
>
{optimizing ? "优化中..." : "一键优化"}
</Button>
</div>
{/* 验证结果 */}
{validationResult && (
<div className="space-y-2">
<div className="flex items-center gap-2">
<h4 className="font-semibold"></h4>
<Badge
variant={validationResult.is_valid ? "default" : "destructive"}
className={validationResult.is_valid ? "bg-green-600" : ""}
>
{validationResult.is_valid ? "通过" : "未通过"}
</Badge>
<Badge variant="outline">
: {validationResult.score}
</Badge>
</div>
{validationResult.issues.length > 0 && (
<div className="space-y-1">
{validationResult.issues.map((issue, i) => (
<div
key={i}
className={`text-sm p-2 rounded ${
issue.severity === "high"
? "bg-red-50 text-red-800"
: issue.severity === "medium"
? "bg-yellow-50 text-yellow-800"
: "bg-blue-50 text-blue-800"
}`}
>
<span className="font-medium">[{issue.severity.toUpperCase()}]</span>{" "}
{issue.message}
</div>
))}
</div>
)}
{validationResult.passed.length > 0 && (
<div className="space-y-1">
<h5 className="text-sm font-medium text-muted-foreground">:</h5>
{validationResult.passed.map((p, i) => (
<div key={i} className="text-sm text-green-700">
{p}
</div>
))}
</div>
)}
</div>
)}
</CardContent>
</Card>
{/* 右侧: 预览和工具 */}
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{!optimizedContent ? (
<div className="flex items-center justify-center h-64 text-muted-foreground">
<div className="text-center">
<p>"一键优化"</p>
</div>
</div>
) : (
<Tabs defaultValue="preview" className="space-y-4">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="preview"></TabsTrigger>
<TabsTrigger value="tips"></TabsTrigger>
</TabsList>
<TabsContent value="preview" className="space-y-4">
<div className="space-y-2">
<h4 className="font-semibold text-lg">{optimizedContent.title}</h4>
<div className="text-sm whitespace-pre-wrap">
{optimizedContent.content}
</div>
</div>
<div className="space-y-2">
<h5 className="font-medium">:</h5>
<div className="flex flex-wrap gap-2">
<Button size="sm" variant="outline" onClick={() => handleCopyContent("text")}>
</Button>
<Button size="sm" variant="outline" onClick={() => handleCopyContent("markdown")}>
Markdown
</Button>
<Button size="sm" variant="outline" onClick={() => handleCopyContent("html")}>
HTML
</Button>
</div>
</div>
</TabsContent>
<TabsContent value="tips">
<div className="space-y-2">
{optimizedContent.tips.length > 0 ? (
optimizedContent.tips.map((tip, i) => (
<div key={i} className="text-sm p-2 bg-muted rounded">
{tip}
</div>
))
) : (
<p className="text-sm text-muted-foreground"></p>
)}
</div>
</TabsContent>
</Tabs>
)}
</CardContent>
</Card>
</div>
{/* 优化流程说明 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="p-4 border rounded-lg">
<div className="font-semibold mb-2">1. AI化</div>
<p className="text-sm text-muted-foreground">
AI敏感度AI写作特征使
</p>
</div>
<div className="p-4 border rounded-lg">
<div className="font-semibold mb-2">2. </div>
<p className="text-sm text-muted-foreground">
</p>
</div>
<div className="p-4 border rounded-lg">
<div className="font-semibold mb-2">3. SEO优化</div>
<p className="text-sm text-muted-foreground">
</p>
</div>
<div className="p-4 border rounded-lg">
<div className="font-semibold mb-2">4. </div>
<p className="text-sm text-muted-foreground">
HTML规则和结构偏好
</p>
</div>
</div>
</CardContent>
</Card>
</div>
);
}