refactor: streamline mobile interface for data analysis focus
Remove settings, optimize mobile layout, highlight data analysis features Use generic company names, simulate today's timeline, responsive design Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
@@ -9,269 +9,555 @@ import { Textarea } from "@/components/ui/textarea"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||
import { AlertTriangle, Check } from "lucide-react"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { AlertCircle, Plus, X, Save, TestTube } from "lucide-react"
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
|
||||
interface RuleEditorProps {
|
||||
ruleId: string | null
|
||||
interface TagRule {
|
||||
id?: string
|
||||
name: string
|
||||
description: string
|
||||
conditions: RuleCondition[]
|
||||
actions: RuleAction[]
|
||||
priority: number
|
||||
enabled: boolean
|
||||
schedule?: RuleSchedule
|
||||
}
|
||||
|
||||
export function RuleEditor({ ruleId }: RuleEditorProps) {
|
||||
const [rule, setRule] = useState({
|
||||
interface RuleCondition {
|
||||
field: string
|
||||
operator: string
|
||||
value: string
|
||||
logicalOperator?: "AND" | "OR"
|
||||
}
|
||||
|
||||
interface RuleAction {
|
||||
type: "add_tag" | "remove_tag" | "update_field"
|
||||
target: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface RuleSchedule {
|
||||
type: "immediate" | "scheduled" | "recurring"
|
||||
startTime?: string
|
||||
interval?: string
|
||||
}
|
||||
|
||||
interface RuleEditorProps {
|
||||
ruleId?: string
|
||||
onSave: (rule: TagRule) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
export function RuleEditor({ ruleId, onSave, onCancel }: RuleEditorProps) {
|
||||
const [rule, setRule] = useState<TagRule>({
|
||||
name: "",
|
||||
description: "",
|
||||
targetTag: "",
|
||||
condition: "",
|
||||
priority: "2",
|
||||
status: "draft",
|
||||
sql: "",
|
||||
conditions: [{ field: "", operator: "", value: "" }],
|
||||
actions: [{ type: "add_tag", target: "", value: "" }],
|
||||
priority: 1,
|
||||
enabled: true,
|
||||
})
|
||||
const [activeTab, setActiveTab] = useState("visual")
|
||||
const [validationResult, setValidationResult] = useState<{
|
||||
isValid: boolean
|
||||
message: string
|
||||
} | null>(null)
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [testResults, setTestResults] = useState<any>(null)
|
||||
const [validationErrors, setValidationErrors] = useState<string[]>([])
|
||||
|
||||
const fieldOptions = [
|
||||
{ value: "user_type", label: "用户类型" },
|
||||
{ value: "registration_date", label: "注册日期" },
|
||||
{ value: "last_login", label: "最后登录" },
|
||||
{ value: "purchase_amount", label: "购买金额" },
|
||||
{ value: "activity_score", label: "活跃度评分" },
|
||||
{ value: "device_type", label: "设备类型" },
|
||||
{ value: "location", label: "地理位置" },
|
||||
{ value: "age", label: "年龄" },
|
||||
{ value: "gender", label: "性别" },
|
||||
]
|
||||
|
||||
const operatorOptions = [
|
||||
{ value: "equals", label: "等于" },
|
||||
{ value: "not_equals", label: "不等于" },
|
||||
{ value: "greater_than", label: "大于" },
|
||||
{ value: "less_than", label: "小于" },
|
||||
{ value: "contains", label: "包含" },
|
||||
{ value: "not_contains", label: "不包含" },
|
||||
{ value: "starts_with", label: "开始于" },
|
||||
{ value: "ends_with", label: "结束于" },
|
||||
{ value: "in_range", label: "在范围内" },
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
if (ruleId) {
|
||||
// 这里应该是获取规则详情的逻辑
|
||||
// 模拟从服务器获取数据
|
||||
setTimeout(() => {
|
||||
setRule({
|
||||
name: "高价值用户识别",
|
||||
description: "根据用户消费金额和频次识别高价值用户",
|
||||
targetTag: "高价值用户",
|
||||
condition: "消费金额 > 5000 AND 消费频次 > 10",
|
||||
priority: "1",
|
||||
status: "active",
|
||||
sql: "SELECT user_id FROM user_behavior WHERE total_amount > 5000 AND purchase_count > 10",
|
||||
})
|
||||
}, 300)
|
||||
loadRule(ruleId)
|
||||
}
|
||||
}, [ruleId])
|
||||
|
||||
const handleValidate = () => {
|
||||
// 模拟验证逻辑
|
||||
if (rule.condition.trim() === "") {
|
||||
setValidationResult({
|
||||
isValid: false,
|
||||
message: "规则条件不能为空",
|
||||
const loadRule = async (id: string) => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
// 模拟API调用
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
// 这里应该从API加载规则数据
|
||||
setRule({
|
||||
id,
|
||||
name: "示例规则",
|
||||
description: "这是一个示例规则",
|
||||
conditions: [{ field: "user_type", operator: "equals", value: "premium" }],
|
||||
actions: [{ type: "add_tag", target: "tag", value: "高价值用户" }],
|
||||
priority: 1,
|
||||
enabled: true,
|
||||
})
|
||||
return
|
||||
} catch (error) {
|
||||
console.error("加载规则失败:", error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const validateRule = (): string[] => {
|
||||
const errors: string[] = []
|
||||
|
||||
if (!rule.name.trim()) {
|
||||
errors.push("规则名称不能为空")
|
||||
}
|
||||
|
||||
if (activeTab === "sql" && rule.sql.trim() === "") {
|
||||
setValidationResult({
|
||||
isValid: false,
|
||||
message: "SQL 语句不能为空",
|
||||
})
|
||||
return
|
||||
if (rule.conditions.length === 0) {
|
||||
errors.push("至少需要一个条件")
|
||||
}
|
||||
|
||||
// 模拟验证成功
|
||||
setValidationResult({
|
||||
isValid: true,
|
||||
message: "规则验证通过,可以保存或执行",
|
||||
rule.conditions.forEach((condition, index) => {
|
||||
if (!condition.field || !condition.operator || !condition.value) {
|
||||
errors.push(`条件 ${index + 1} 不完整`)
|
||||
}
|
||||
})
|
||||
|
||||
if (rule.actions.length === 0) {
|
||||
errors.push("至少需要一个动作")
|
||||
}
|
||||
|
||||
rule.actions.forEach((action, index) => {
|
||||
if (!action.target || !action.value) {
|
||||
errors.push(`动作 ${index + 1} 不完整`)
|
||||
}
|
||||
})
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
// 这里应该是保存规则的逻辑
|
||||
console.log("保存规则:", rule)
|
||||
// 模拟保存成功
|
||||
alert("规则保存成功")
|
||||
const errors = validateRule()
|
||||
setValidationErrors(errors)
|
||||
|
||||
if (errors.length === 0) {
|
||||
onSave(rule)
|
||||
}
|
||||
}
|
||||
|
||||
const handleExecute = () => {
|
||||
// 这里应该是执行规则的逻辑
|
||||
console.log("执行规则:", rule)
|
||||
// 模拟执行成功
|
||||
alert("规则执行成功,影响用户 1250 人")
|
||||
const handleTest = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
// 模拟测试规则
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
setTestResults({
|
||||
matchedUsers: 156,
|
||||
estimatedImpact: "将为156个用户添加标签",
|
||||
executionTime: "预计执行时间: 2分钟",
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("测试规则失败:", error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const addCondition = () => {
|
||||
setRule({
|
||||
...rule,
|
||||
conditions: [...rule.conditions, { field: "", operator: "", value: "" }],
|
||||
})
|
||||
}
|
||||
|
||||
const removeCondition = (index: number) => {
|
||||
const newConditions = rule.conditions.filter((_, i) => i !== index)
|
||||
setRule({ ...rule, conditions: newConditions })
|
||||
}
|
||||
|
||||
const updateCondition = (index: number, field: keyof RuleCondition, value: string) => {
|
||||
const newConditions = [...rule.conditions]
|
||||
newConditions[index] = { ...newConditions[index], [field]: value }
|
||||
setRule({ ...rule, conditions: newConditions })
|
||||
}
|
||||
|
||||
const addAction = () => {
|
||||
setRule({
|
||||
...rule,
|
||||
actions: [...rule.actions, { type: "add_tag", target: "", value: "" }],
|
||||
})
|
||||
}
|
||||
|
||||
const removeAction = (index: number) => {
|
||||
const newActions = rule.actions.filter((_, i) => i !== index)
|
||||
setRule({ ...rule, actions: newActions })
|
||||
}
|
||||
|
||||
const updateAction = (index: number, field: keyof RuleAction, value: string) => {
|
||||
const newActions = [...rule.actions]
|
||||
newActions[index] = { ...newActions[index], [field]: value }
|
||||
setRule({ ...rule, actions: newActions })
|
||||
}
|
||||
|
||||
if (isLoading && !rule.name) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<p>加载规则中...</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{ruleId ? "编辑规则" : "创建新规则"}</CardTitle>
|
||||
<CardDescription>
|
||||
{ruleId ? "修改现有标签规则" : "创建新的标签生成规则"}
|
||||
</CardDescription>
|
||||
<CardDescription>{ruleId ? "修改现有标签规则的配置" : "创建新的自动化标签规则"}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="rule-name">规则名称</Label>
|
||||
<Input
|
||||
id="rule-name"
|
||||
value={rule.name}
|
||||
onChange={(e) => setRule({ ...rule, name: e.target.value })}
|
||||
className="mt-1"
|
||||
/>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="basic" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-4">
|
||||
<TabsTrigger value="basic">基本信息</TabsTrigger>
|
||||
<TabsTrigger value="conditions">条件设置</TabsTrigger>
|
||||
<TabsTrigger value="actions">动作设置</TabsTrigger>
|
||||
<TabsTrigger value="schedule">执行计划</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="basic" className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">规则名称</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={rule.name}
|
||||
onChange={(e) => setRule({ ...rule, name: e.target.value })}
|
||||
placeholder="输入规则名称"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="priority">优先级</Label>
|
||||
<Select
|
||||
value={rule.priority.toString()}
|
||||
onValueChange={(value) => setRule({ ...rule, priority: Number.parseInt(value) })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">高 (1)</SelectItem>
|
||||
<SelectItem value="2">中 (2)</SelectItem>
|
||||
<SelectItem value="3">低 (3)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="rule-description">规则描述</Label>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">规则描述</Label>
|
||||
<Textarea
|
||||
id="rule-description"
|
||||
id="description"
|
||||
value={rule.description}
|
||||
onChange={(e) => setRule({ ...rule, description: e.target.value })}
|
||||
className="mt-1"
|
||||
placeholder="描述这个规则的用途和逻辑"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="target-tag">目标标签</Label>
|
||||
<Input
|
||||
id="target-tag"
|
||||
value={rule.targetTag}
|
||||
onChange={(e) => setRule({ ...rule, targetTag: e.target.value })}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="rule-priority">优先级</Label>
|
||||
<Select
|
||||
value={rule.priority}
|
||||
onValueChange={(value) => setRule({ ...rule, priority: value })}
|
||||
>
|
||||
<SelectTrigger className="mt-1">
|
||||
<SelectValue placeholder="选择优先级" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">1 - 高</SelectItem>
|
||||
<SelectItem value="2">2 - 中</SelectItem>
|
||||
<SelectItem value="3">3 - 低</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="rule-status">规则状态</Label>
|
||||
<div className="flex items-center space-x-2 mt-1">
|
||||
<Switch
|
||||
id="rule-status"
|
||||
checked={rule.status === "active"}
|
||||
onCheckedChange={(checked) => setRule({ ...rule, status: checked ? "active" : "draft" })}
|
||||
/>
|
||||
<Label htmlFor="rule-status">
|
||||
{rule.status === "active" ? "启用" : "草稿"}
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Label>规则定义</Label>
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="visual">可视化编辑</TabsTrigger>
|
||||
<TabsTrigger value="sql">SQL 编辑</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="visual" className="space-y-4 mt-4">
|
||||
<Textarea
|
||||
value={rule.condition}
|
||||
onChange={(e) => setRule({ ...rule, condition: e.target.value })}
|
||||
placeholder="例如: 消费金额 > 5000 AND 消费频次 > 10"
|
||||
className="min-h-[200px]"
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="enabled"
|
||||
checked={rule.enabled}
|
||||
onCheckedChange={(checked) => setRule({ ...rule, enabled: checked })}
|
||||
/>
|
||||
<div className="bg-gray-50 p-4 rounded-md">
|
||||
<h4 className="text-sm font-medium mb-2">可用字段</h4>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " 消费金额" })}>
|
||||
消费金额
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " 消费频次" })}>
|
||||
消费频次
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " 最近登录时间" })}>
|
||||
最近登录时间
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " 活跃度" })}>
|
||||
活跃度
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " 注册时间" })}>
|
||||
注册时间
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " 年龄" })}>
|
||||
年龄
|
||||
</Button>
|
||||
</div>
|
||||
<h4 className="text-sm font-medium mt-4 mb-2">操作符</h4>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " >" })}>
|
||||
大于 (>)
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " <" })}>
|
||||
小于 (<)
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " =" })}>
|
||||
等于 (=)
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " !=" })}>
|
||||
不等于 (!=)
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " AND" })}>
|
||||
与 (AND)
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " OR" })}>
|
||||
或 (OR)
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " NOT" })}>
|
||||
非 (NOT)
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setRule({ ...rule, condition: rule.condition + " IN" })}>
|
||||
包含 (IN)
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="sql" className="space-y-4 mt-4">
|
||||
<Textarea
|
||||
value={rule.sql}
|
||||
onChange={(e) => setRule({ ...rule, sql: e.target.value })}
|
||||
placeholder="输入 SQL 查询语句"
|
||||
className="min-h-[200px] font-mono"
|
||||
/>
|
||||
<div className="bg-gray-50 p-4 rounded-md">
|
||||
<h4 className="text-sm font-medium mb-2">SQL 模板</h4>
|
||||
<div className="text-xs font-mono bg-gray-100 p-2 rounded">
|
||||
SELECT user_id FROM user_behavior WHERE total_amount > 5000 AND purchase_count > 10
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
SQL 查询必须返回 user_id 字段,用于标识需要打标签的用户。
|
||||
</p>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
<Label htmlFor="enabled">启用规则</Label>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{validationResult && (
|
||||
<Alert variant={validationResult.isValid ? "default" : "destructive"}>
|
||||
{validationResult.isValid ? (
|
||||
<Check className="h-4 w-4" />
|
||||
) : (
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
)}
|
||||
<AlertTitle>{validationResult.isValid ? "验证通过" : "验证失败"}</AlertTitle>
|
||||
<AlertDescription>{validationResult.message}</AlertDescription>
|
||||
<TabsContent value="conditions" className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-medium">触发条件</h3>
|
||||
<Button onClick={addCondition} size="sm">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
添加条件
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{rule.conditions.map((condition, index) => (
|
||||
<div key={index} className="border rounded-lg p-4 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="font-medium">条件 {index + 1}</h4>
|
||||
{rule.conditions.length > 1 && (
|
||||
<Button variant="ghost" size="sm" onClick={() => removeCondition(index)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>字段</Label>
|
||||
<Select value={condition.field} onValueChange={(value) => updateCondition(index, "field", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择字段" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{fieldOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>操作符</Label>
|
||||
<Select
|
||||
value={condition.operator}
|
||||
onValueChange={(value) => updateCondition(index, "operator", value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择操作符" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{operatorOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>值</Label>
|
||||
<Input
|
||||
value={condition.value}
|
||||
onChange={(e) => updateCondition(index, "value", e.target.value)}
|
||||
placeholder="输入比较值"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{index < rule.conditions.length - 1 && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label>逻辑关系:</Label>
|
||||
<Select
|
||||
value={condition.logicalOperator || "AND"}
|
||||
onValueChange={(value) => updateCondition(index, "logicalOperator", value)}
|
||||
>
|
||||
<SelectTrigger className="w-20">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="AND">AND</SelectItem>
|
||||
<SelectItem value="OR">OR</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="actions" className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-medium">执行动作</h3>
|
||||
<Button onClick={addAction} size="sm">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
添加动作
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{rule.actions.map((action, index) => (
|
||||
<div key={index} className="border rounded-lg p-4 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="font-medium">动作 {index + 1}</h4>
|
||||
{rule.actions.length > 1 && (
|
||||
<Button variant="ghost" size="sm" onClick={() => removeAction(index)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>动作类型</Label>
|
||||
<Select value={action.type} onValueChange={(value) => updateAction(index, "type", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="add_tag">添加标签</SelectItem>
|
||||
<SelectItem value="remove_tag">移除标签</SelectItem>
|
||||
<SelectItem value="update_field">更新字段</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>目标</Label>
|
||||
<Input
|
||||
value={action.target}
|
||||
onChange={(e) => updateAction(index, "target", e.target.value)}
|
||||
placeholder={
|
||||
action.type === "add_tag" || action.type === "remove_tag" ? "标签名称" : "字段名称"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>值</Label>
|
||||
<Input
|
||||
value={action.value}
|
||||
onChange={(e) => updateAction(index, "value", e.target.value)}
|
||||
placeholder="输入值"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="schedule" className="space-y-4">
|
||||
<h3 className="text-lg font-medium">执行计划</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>执行类型</Label>
|
||||
<Select
|
||||
value={rule.schedule?.type || "immediate"}
|
||||
onValueChange={(value) =>
|
||||
setRule({
|
||||
...rule,
|
||||
schedule: { ...rule.schedule, type: value as any },
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="immediate">立即执行</SelectItem>
|
||||
<SelectItem value="scheduled">定时执行</SelectItem>
|
||||
<SelectItem value="recurring">周期执行</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{rule.schedule?.type === "scheduled" && (
|
||||
<div className="space-y-2">
|
||||
<Label>执行时间</Label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={rule.schedule.startTime || ""}
|
||||
onChange={(e) =>
|
||||
setRule({
|
||||
...rule,
|
||||
schedule: { ...rule.schedule, startTime: e.target.value },
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{rule.schedule?.type === "recurring" && (
|
||||
<div className="space-y-2">
|
||||
<Label>执行间隔</Label>
|
||||
<Select
|
||||
value={rule.schedule.interval || "daily"}
|
||||
onValueChange={(value) =>
|
||||
setRule({
|
||||
...rule,
|
||||
schedule: { ...rule.schedule, interval: value },
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="hourly">每小时</SelectItem>
|
||||
<SelectItem value="daily">每天</SelectItem>
|
||||
<SelectItem value="weekly">每周</SelectItem>
|
||||
<SelectItem value="monthly">每月</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<Separator className="my-6" />
|
||||
|
||||
{validationErrors.length > 0 && (
|
||||
<Alert className="mb-4">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<ul className="list-disc list-inside">
|
||||
{validationErrors.map((error, index) => (
|
||||
<li key={index}>{error}</li>
|
||||
))}
|
||||
</ul>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{testResults && (
|
||||
<Alert className="mb-4">
|
||||
<TestTube className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<div className="space-y-1">
|
||||
<p>
|
||||
<strong>匹配用户:</strong> {testResults.matchedUsers}
|
||||
</p>
|
||||
<p>
|
||||
<strong>预期影响:</strong> {testResults.estimatedImpact}
|
||||
</p>
|
||||
<p>
|
||||
<strong>执行时间:</strong> {testResults.executionTime}
|
||||
</p>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="outline" onClick={handleTest} disabled={isLoading}>
|
||||
<TestTube className="h-4 w-4 mr-2" />
|
||||
{isLoading ? "测试中..." : "测试规则"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="outline" onClick={onCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={isLoading}>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
保存规则
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-between">
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" onClick={handleValidate}>
|
||||
验证规则
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline">取消</Button>
|
||||
<Button onClick={handleSave}>保存规则</Button>
|
||||
<Button variant="default" className="bg-green-600 hover:bg-green-700" onClick={handleExecute}>
|
||||
执行规则
|
||||
</Button>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user