feat: optimize interface and database connection
Adjust MongoDB mock connector and update database structure. Enhance sidebar, data platform, and AI analysis tools. Clean up unnecessary code and update development docs. #VERCEL_SKIP Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { useState } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Plus, Trash2 } from "lucide-react"
|
||||
import { Trash2, Plus, Save } from "lucide-react"
|
||||
|
||||
interface RuleCondition {
|
||||
field: string
|
||||
operator: string
|
||||
value: string
|
||||
logicalOperator?: "AND" | "OR"
|
||||
}
|
||||
|
||||
interface TagRule {
|
||||
@@ -24,151 +24,263 @@ interface TagRule {
|
||||
addTags: string[]
|
||||
removeTags: string[]
|
||||
}
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 简易标签规则编辑器
|
||||
* 只实现基本字段编辑与本地状态保存,后端持久化可在后续迭代中接入
|
||||
*/
|
||||
export function RuleEditor({ ruleId }: { ruleId: string | null }) {
|
||||
const [rule, setRule] = useState<TagRule | null>(null)
|
||||
interface RuleEditorProps {
|
||||
rule?: TagRule
|
||||
onSave: (rule: TagRule) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
// ❗️此处仅示例:真实项目应替换为 API 请求
|
||||
useEffect(() => {
|
||||
if (!ruleId) {
|
||||
setRule(null)
|
||||
return
|
||||
}
|
||||
// 模拟异步加载
|
||||
setTimeout(() => {
|
||||
setRule({
|
||||
id: ruleId,
|
||||
name: "示例规则",
|
||||
description: "这是一个示例规则,用于演示编辑器",
|
||||
conditions: [
|
||||
{ field: "orderAmount", operator: ">", value: "500", logicalOperator: "AND" },
|
||||
{ field: "loginDays", operator: "<", value: "30" },
|
||||
],
|
||||
actions: { addTags: ["高价值用户"], removeTags: [] },
|
||||
export function RuleEditor({ rule, onSave, onCancel }: RuleEditorProps) {
|
||||
const [formData, setFormData] = useState<TagRule>(
|
||||
rule || {
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
conditions: [{ field: "", operator: "", value: "" }],
|
||||
actions: {
|
||||
addTags: [],
|
||||
removeTags: [],
|
||||
},
|
||||
enabled: true,
|
||||
},
|
||||
)
|
||||
|
||||
const [newAddTag, setNewAddTag] = useState("")
|
||||
const [newRemoveTag, setNewRemoveTag] = useState("")
|
||||
|
||||
const fieldOptions = [
|
||||
{ value: "imei", label: "IMEI" },
|
||||
{ value: "phone", label: "手机号" },
|
||||
{ value: "device_brand", label: "设备品牌" },
|
||||
{ value: "device_model", label: "设备型号" },
|
||||
{ value: "os_version", label: "系统版本" },
|
||||
{ value: "app_version", label: "应用版本" },
|
||||
{ value: "location", label: "地理位置" },
|
||||
{ value: "user_behavior", label: "用户行为" },
|
||||
{ value: "consumption_amount", label: "消费金额" },
|
||||
{ value: "activity_frequency", label: "活跃频率" },
|
||||
]
|
||||
|
||||
const operatorOptions = [
|
||||
{ value: "equals", label: "等于" },
|
||||
{ value: "not_equals", label: "不等于" },
|
||||
{ value: "contains", label: "包含" },
|
||||
{ value: "not_contains", label: "不包含" },
|
||||
{ value: "starts_with", label: "开始于" },
|
||||
{ value: "ends_with", label: "结束于" },
|
||||
{ value: "greater_than", label: "大于" },
|
||||
{ value: "less_than", label: "小于" },
|
||||
{ value: "in_range", label: "在范围内" },
|
||||
{ value: "regex", label: "正则匹配" },
|
||||
]
|
||||
|
||||
const addCondition = () => {
|
||||
setFormData({
|
||||
...formData,
|
||||
conditions: [...formData.conditions, { field: "", operator: "", value: "" }],
|
||||
})
|
||||
}
|
||||
|
||||
const removeCondition = (index: number) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
conditions: formData.conditions.filter((_, i) => i !== index),
|
||||
})
|
||||
}
|
||||
|
||||
const updateCondition = (index: number, field: keyof RuleCondition, value: string) => {
|
||||
const newConditions = [...formData.conditions]
|
||||
newConditions[index] = { ...newConditions[index], [field]: value }
|
||||
setFormData({ ...formData, conditions: newConditions })
|
||||
}
|
||||
|
||||
const addTag = (type: "addTags" | "removeTags") => {
|
||||
const tagValue = type === "addTags" ? newAddTag : newRemoveTag
|
||||
if (tagValue.trim()) {
|
||||
setFormData({
|
||||
...formData,
|
||||
actions: {
|
||||
...formData.actions,
|
||||
[type]: [...formData.actions[type], tagValue.trim()],
|
||||
},
|
||||
})
|
||||
}, 300)
|
||||
}, [ruleId])
|
||||
|
||||
if (!ruleId) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>请选择要编辑的规则</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)
|
||||
if (type === "addTags") {
|
||||
setNewAddTag("")
|
||||
} else {
|
||||
setNewRemoveTag("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!rule) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>加载中...</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)
|
||||
const removeTag = (type: "addTags" | "removeTags", index: number) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
actions: {
|
||||
...formData.actions,
|
||||
[type]: formData.actions[type].filter((_, i) => i !== index),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// === 内部更新函数 ===
|
||||
const updateRule = (partial: Partial<TagRule>) => setRule((prev) => (prev ? { ...prev, ...partial } : prev))
|
||||
|
||||
const addCondition = () =>
|
||||
updateRule({
|
||||
conditions: [...rule.conditions, { field: "", operator: "=", value: "" }],
|
||||
})
|
||||
|
||||
const removeCondition = (index: number) =>
|
||||
updateRule({
|
||||
conditions: rule.conditions.filter((_, i) => i !== index),
|
||||
})
|
||||
|
||||
const saveRule = () => {
|
||||
// TODO: 调用后端保存
|
||||
console.log("保存规则:", rule)
|
||||
const handleSave = () => {
|
||||
if (formData.name && formData.conditions.length > 0) {
|
||||
onSave(formData)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>编辑规则:{rule.name}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* 基本信息 */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium">规则名称</label>
|
||||
<Input value={rule.name} onChange={(e) => updateRule({ name: e.target.value })} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium">规则描述</label>
|
||||
<Textarea value={rule.description} onChange={(e) => updateRule({ description: e.target.value })} />
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>规则基本信息</CardTitle>
|
||||
<CardDescription>设置标签规则的基本信息</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="rule-name">规则名称</Label>
|
||||
<Input
|
||||
id="rule-name"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder="输入规则名称"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="rule-description">规则描述</Label>
|
||||
<Input
|
||||
id="rule-description"
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
placeholder="输入规则描述"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>触发条件</CardTitle>
|
||||
<CardDescription>设置标签规则的触发条件</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{formData.conditions.map((condition, index) => (
|
||||
<div key={index} className="flex items-center space-x-2 p-3 border rounded-lg">
|
||||
<Select value={condition.field} onValueChange={(value) => updateCondition(index, "field", value)}>
|
||||
<SelectTrigger className="w-[200px]">
|
||||
<SelectValue placeholder="选择字段" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{fieldOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={condition.operator} onValueChange={(value) => updateCondition(index, "operator", value)}>
|
||||
<SelectTrigger className="w-[150px]">
|
||||
<SelectValue placeholder="选择操作符" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{operatorOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* 条件编辑 */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium">条件</label>
|
||||
{rule.conditions.map((c, idx) => (
|
||||
<div key={idx} className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder="字段"
|
||||
value={c.field}
|
||||
onChange={(e) => {
|
||||
const next = [...rule.conditions]
|
||||
next[idx].field = e.target.value
|
||||
updateRule({ conditions: next })
|
||||
}}
|
||||
className="w-1/3"
|
||||
value={condition.value}
|
||||
onChange={(e) => updateCondition(index, "value", e.target.value)}
|
||||
placeholder="输入值"
|
||||
className="flex-1"
|
||||
/>
|
||||
<Input
|
||||
placeholder="运算符"
|
||||
value={c.operator}
|
||||
onChange={(e) => {
|
||||
const next = [...rule.conditions]
|
||||
next[idx].operator = e.target.value
|
||||
updateRule({ conditions: next })
|
||||
}}
|
||||
className="w-1/6"
|
||||
/>
|
||||
<Input
|
||||
placeholder="值"
|
||||
value={c.value}
|
||||
onChange={(e) => {
|
||||
const next = [...rule.conditions]
|
||||
next[idx].value = e.target.value
|
||||
updateRule({ conditions: next })
|
||||
}}
|
||||
className="w-1/3"
|
||||
/>
|
||||
<Button variant="ghost" size="icon" onClick={() => removeCondition(idx)} aria-label="删除条件">
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => removeCondition(index)}
|
||||
disabled={formData.conditions.length === 1}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button variant="outline" onClick={addCondition}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
添加条件
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 动作(标签) */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium">新增标签</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{rule.actions.addTags.map((tag, i) => (
|
||||
<Badge key={i}>{tag}</Badge>
|
||||
))}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>执行动作</CardTitle>
|
||||
<CardDescription>设置满足条件时的标签操作</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div>
|
||||
<Label>添加标签</Label>
|
||||
<div className="flex space-x-2 mt-2">
|
||||
<Input
|
||||
value={newAddTag}
|
||||
onChange={(e) => setNewAddTag(e.target.value)}
|
||||
placeholder="输入要添加的标签"
|
||||
onKeyPress={(e) => e.key === "Enter" && addTag("addTags")}
|
||||
/>
|
||||
<Button onClick={() => addTag("addTags")}>添加</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{formData.actions.addTags.map((tag, index) => (
|
||||
<Badge key={index} variant="secondary" className="flex items-center gap-1">
|
||||
{tag}
|
||||
<button onClick={() => removeTag("addTags", index)}>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 保存 */}
|
||||
<div className="text-right">
|
||||
<Button onClick={saveRule}>保存规则</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div>
|
||||
<Label>移除标签</Label>
|
||||
<div className="flex space-x-2 mt-2">
|
||||
<Input
|
||||
value={newRemoveTag}
|
||||
onChange={(e) => setNewRemoveTag(e.target.value)}
|
||||
placeholder="输入要移除的标签"
|
||||
onKeyPress={(e) => e.key === "Enter" && addTag("removeTags")}
|
||||
/>
|
||||
<Button onClick={() => addTag("removeTags")}>添加</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{formData.actions.removeTags.map((tag, index) => (
|
||||
<Badge key={index} variant="destructive" className="flex items-center gap-1">
|
||||
{tag}
|
||||
<button onClick={() => removeTag("removeTags", index)}>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button variant="outline" onClick={onCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
保存规则
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user