refactor: restructure navigation and module layout

Reorganize navigation and module structure based on new requirements.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2026-01-31 04:32:36 +00:00
parent 22e725887a
commit b17b488f8e
105 changed files with 20530 additions and 3622 deletions

251
app/ai-insight/nlq/page.tsx Normal file
View File

@@ -0,0 +1,251 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { MessageSquare, ArrowLeft, Sparkles, Code, Copy, Play, Clock, CheckCircle2, Table } from "lucide-react"
import Link from "next/link"
const exampleQueries = [
"查询最近7天新增的高价值用户",
"统计各项目的用户流失率",
"找出消费金额前100的用户",
"分析用户活跃时段分布",
]
const queryHistory = [
{
id: 1,
query: "查询最近30天流失风险大于80的用户数量",
sql: "SELECT COUNT(*) FROM users WHERE churn_risk > 80 AND last_active >= DATE_SUB(NOW(), INTERVAL 30 DAY)",
result: "共 2,340 位用户",
time: "10分钟前",
},
{
id: 2,
query: "统计各省份的用户分布",
sql: "SELECT province, COUNT(*) as count FROM users GROUP BY province ORDER BY count DESC",
result: "返回 31 条记录",
time: "1小时前",
},
{
id: 3,
query: "找出RFM评分S级的用户",
sql: "SELECT * FROM users WHERE rfm_level = 'S' ORDER BY total_value DESC LIMIT 100",
result: "返回 100 条记录",
time: "2小时前",
},
]
export default function NLQPage() {
const [query, setQuery] = useState("")
const [isProcessing, setIsProcessing] = useState(false)
const [result, setResult] = useState<{
sql: string
data: { columns: string[]; rows: string[][] }
summary: string
} | null>(null)
const handleSubmit = () => {
if (!query.trim()) return
setIsProcessing(true)
// 模拟AI处理
setTimeout(() => {
setResult({
sql: `SELECT user_id, name, phone, rfm_score, total_value
FROM users
WHERE rfm_level = 'S'
AND last_active >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY total_value DESC
LIMIT 10`,
data: {
columns: ["用户ID", "姓名", "手机号", "RFM评分", "总价值"],
rows: [
["U001", "张伟", "138****8888", "95", "¥125,800"],
["U002", "李娜", "139****6666", "92", "¥98,500"],
["U003", "王芳", "137****5555", "90", "¥87,200"],
["U004", "刘洋", "136****4444", "88", "¥76,800"],
["U005", "陈静", "135****3333", "86", "¥65,400"],
],
},
summary: "找到 5 位符合条件的S级高价值用户总价值 ¥453,700",
})
setIsProcessing(false)
}, 1500)
}
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-emerald-50/30 p-6">
<div className="max-w-7xl mx-auto space-y-6">
{/* 页面标题 */}
<div className="flex items-center gap-4">
<Link href="/ai-insight">
<Button variant="ghost" size="icon" className="rounded-full">
<ArrowLeft className="w-5 h-5" />
</Button>
</Link>
<div>
<h1 className="text-2xl font-bold text-slate-800"></h1>
<p className="text-slate-500 mt-1">AI自动生成SQL并执行</p>
</div>
</div>
{/* 查询输入区 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-6">
<div className="flex items-center gap-3">
<div className="flex-1 relative">
<MessageSquare className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
<Input
placeholder="用自然语言描述你想查询的数据例如查询最近7天新增的高价值用户..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
className="pl-12 pr-4 py-6 text-base"
/>
</div>
<Button
onClick={handleSubmit}
disabled={isProcessing || !query.trim()}
className="bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-600 hover:to-teal-700 px-6 py-6"
>
{isProcessing ? (
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : (
<>
<Sparkles className="w-5 h-5 mr-2" />
AI查询
</>
)}
</Button>
</div>
{/* 示例查询 */}
<div className="flex items-center gap-2 mt-4 flex-wrap">
<span className="text-sm text-slate-500"></span>
{exampleQueries.map((eq, i) => (
<Button
key={i}
variant="outline"
size="sm"
className="text-xs bg-transparent"
onClick={() => setQuery(eq)}
>
{eq}
</Button>
))}
</div>
</CardContent>
</Card>
{/* 查询结果 */}
{result && (
<div className="space-y-4">
{/* 生成的SQL */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<CardTitle className="text-base flex items-center gap-2">
<Code className="w-4 h-4 text-slate-500" />
SQL
</CardTitle>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm">
<Copy className="w-4 h-4 mr-1" />
</Button>
<Button variant="outline" size="sm">
<Play className="w-4 h-4 mr-1" />
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<pre className="bg-slate-900 text-slate-100 p-4 rounded-lg text-sm overflow-x-auto">
<code>{result.sql}</code>
</pre>
</CardContent>
</Card>
{/* 查询结果表格 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<CardTitle className="text-base flex items-center gap-2">
<Table className="w-4 h-4 text-slate-500" />
</CardTitle>
<Badge className="bg-emerald-100 text-emerald-700">
<CheckCircle2 className="w-3 h-3 mr-1" />
{result.summary}
</Badge>
</div>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-slate-200">
{result.data.columns.map((col, i) => (
<th key={i} className="text-left py-3 px-4 text-xs font-medium text-slate-500">
{col}
</th>
))}
</tr>
</thead>
<tbody>
{result.data.rows.map((row, i) => (
<tr key={i} className="border-b border-slate-100 hover:bg-slate-50/50">
{row.map((cell, j) => (
<td key={j} className="py-3 px-4 text-sm text-slate-700">
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</Card>
</div>
)}
{/* 查询历史 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2">
<Clock className="w-4 h-4 text-slate-500" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{queryHistory.map((item) => (
<div key={item.id} className="p-4 bg-slate-50/80 rounded-lg border border-slate-200/60">
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="text-sm font-medium text-slate-800">{item.query}</p>
<pre className="mt-2 text-xs text-slate-500 bg-slate-100 p-2 rounded overflow-x-auto">
{item.sql}
</pre>
</div>
<div className="text-right ml-4">
<Badge variant="outline" className="text-xs">
{item.result}
</Badge>
<p className="text-xs text-slate-500 mt-1">{item.time}</p>
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
</div>
)
}