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:
@@ -26,9 +26,19 @@ import {
|
||||
Copy,
|
||||
Check,
|
||||
Zap,
|
||||
Plus,
|
||||
Download,
|
||||
} from "lucide-react"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog"
|
||||
import { useToast } from "@/components/ui/use-toast"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
|
||||
interface AIAnalysisTask {
|
||||
id: string
|
||||
@@ -297,8 +307,8 @@ export function AIAnalysisTools() {
|
||||
description: "为特定用户分群生成营销策略",
|
||||
type: "strategy-generation",
|
||||
parameters: {
|
||||
targetSegment: "所有用户",
|
||||
campaignGoal: "提高转化率",
|
||||
targetSegment: "流失风险用户",
|
||||
campaignGoal: "提高留存率",
|
||||
budgetConstraint: "中等",
|
||||
channelPreference: ["短信", "应用内推送", "邮件"],
|
||||
},
|
||||
@@ -664,9 +674,7 @@ export function AIAnalysisTools() {
|
||||
<Plus className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium mb-2">创建新模板</h3>
|
||||
<p className="text-sm text-muted-foreground text-center mb-4">
|
||||
创建自定义分析模板,保存常用的分析配置
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground text-center mb-4">创建自定义分析模板,保存常用的分析配置</p>
|
||||
<Button variant="outline">创建模板</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -719,9 +727,7 @@ export function AIAnalysisTools() {
|
||||
<SelectItem value="10">10</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
设置同时执行的AI分析任务数量,数值越大系统负载越高
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">设置同时执行的AI分析任务数量,数值越大系统负载越高</p>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
@@ -920,4 +926,231 @@ export function AIAnalysisTools() {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{selectedTask.result.clusters.map((cluster: any, index: number) => (
|
||||
<TableRow key={
|
||||
<TableRow key={index}>
|
||||
<TableCell className="font-medium">{cluster.name}</TableCell>
|
||||
<TableCell>{cluster.size.toLocaleString()}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{cluster.characteristics.map((char: string, charIndex: number) => (
|
||||
<Badge key={charIndex} variant="outline">
|
||||
{char}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedTask.result.tagCategories && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-medium">生成的标签分类</h4>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>分类名称</TableHead>
|
||||
<TableHead>标签数量</TableHead>
|
||||
<TableHead>覆盖率</TableHead>
|
||||
<TableHead>示例标签</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{selectedTask.result.tagCategories.map((category: any, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="font-medium">{category.name}</TableCell>
|
||||
<TableCell>{category.count}</TableCell>
|
||||
<TableCell>{category.coverage}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{category.examples.map((example: string, exampleIndex: number) => (
|
||||
<Badge key={exampleIndex} variant="outline">
|
||||
{example}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setSelectedTaskId(null)}>
|
||||
关闭
|
||||
</Button>
|
||||
{selectedTask?.status === "completed" && (
|
||||
<Button>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
下载报告
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 添加模型对话框 */}
|
||||
<Dialog open={isAddingModel} onOpenChange={setIsAddingModel}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加AI模型</DialogTitle>
|
||||
<DialogDescription>配置新的AI模型连接</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="model-name" className="text-right">
|
||||
模型名称
|
||||
</Label>
|
||||
<Input id="model-name" className="col-span-3" placeholder="例如:GPT-4" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="model-provider" className="text-right">
|
||||
提供商
|
||||
</Label>
|
||||
<Input id="model-provider" className="col-span-3" placeholder="例如:OpenAI" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="model-type" className="text-right">
|
||||
模型类型
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="model-type" className="col-span-3">
|
||||
<SelectValue placeholder="选择模型类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="llm">大语言模型</SelectItem>
|
||||
<SelectItem value="image-gen">图像生成模型</SelectItem>
|
||||
<SelectItem value="custom">专用分析模型</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="api-endpoint" className="text-right">
|
||||
API端点
|
||||
</Label>
|
||||
<Input id="api-endpoint" className="col-span-3" placeholder="例如:https://api.openai.com/v1/chat" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="api-key" className="text-right">
|
||||
API密钥
|
||||
</Label>
|
||||
<Input id="api-key" type="password" className="col-span-3" placeholder="输入API密钥" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-start gap-4">
|
||||
<Label htmlFor="capabilities" className="text-right pt-2">
|
||||
能力
|
||||
</Label>
|
||||
<Textarea id="capabilities" className="col-span-3" placeholder="例如:文本生成, 内容分析" rows={3} />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<div className="text-right">
|
||||
<Label htmlFor="model-status">启用模型</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 col-span-3">
|
||||
<Switch id="model-status" defaultChecked />
|
||||
<Label htmlFor="model-status">立即启用此模型</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsAddingModel(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={() => setIsAddingModel(false)}>添加模型</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 创建分析任务对话框 */}
|
||||
<Dialog open={isCreatingTask} onOpenChange={setIsCreatingTask}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>创建新的分析任务</DialogTitle>
|
||||
<DialogDescription>选择分析类型并配置参数</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="task-name" className="text-right">
|
||||
任务名称
|
||||
</Label>
|
||||
<Input id="task-name" className="col-span-3" placeholder="例如:高价值用户流失预测" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="task-type" className="text-right">
|
||||
分析类型
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="task-type" className="col-span-3">
|
||||
<SelectValue placeholder="选择分析类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="behavior-analysis">用户行为分析</SelectItem>
|
||||
<SelectItem value="segment-discovery">用户分群自动发现</SelectItem>
|
||||
<SelectItem value="churn-prediction">用户流失预测</SelectItem>
|
||||
<SelectItem value="strategy-generation">营销策略生成</SelectItem>
|
||||
<SelectItem value="tag-generation">自动标签生成</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="task-datasource" className="text-right">
|
||||
数据源
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="task-datasource" className="col-span-3">
|
||||
<SelectValue placeholder="选择数据源" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有数据源</SelectItem>
|
||||
<SelectItem value="user-db">用户数据库</SelectItem>
|
||||
<SelectItem value="transaction">交易系统</SelectItem>
|
||||
<SelectItem value="behavior">行为分析平台</SelectItem>
|
||||
<SelectItem value="user-value">用户价值模型</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-start gap-4">
|
||||
<Label htmlFor="task-parameters" className="text-right pt-2">
|
||||
分析参数
|
||||
</Label>
|
||||
<Textarea
|
||||
id="task-parameters"
|
||||
className="col-span-3"
|
||||
placeholder="输入JSON格式的分析参数,例如:{ 'userSegment': '高价值用户', 'timeRange': '近30天' }"
|
||||
rows={5}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<div className="text-right">
|
||||
<Label htmlFor="schedule-task">定期运行</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 col-span-3">
|
||||
<Switch id="schedule-task" />
|
||||
<Label htmlFor="schedule-task">定期执行此任务</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsCreatingTask(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={() => setIsCreatingTask(false)}>
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
创建任务
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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