Files
deerflow-code/web-code/pages/SceneList.tsx
T
2026-05-27 19:43:15 +08:00

482 lines
19 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import { Plus, Search, Eye, Edit, Copy, Trash2, Play, Pause, ChevronRight, Layers } from 'lucide-react';
import { useTheme } from '../contexts/ThemeContext';
import { PageType } from '../components/Sidebar';
import { requestCrudService } from '@/service/request-axios-service';
import { Pagination, Dialog, Input, Textarea, Radio, Button, MessagePlugin, Popconfirm, Loading } from 'tdesign-react';
import { useNavigate } from 'react-router-dom';
interface SceneListProps {
onViewDetail: (id: string) => void;
onNavigate: (page: PageType) => void;
}
interface sceneItem {
id?: number;
status?: number;
applicableTask?: string;
sceneName?: string;
sceneType: string;
sceneIntro: string;
sceneCode: string;
relatedAgent: string;
deleted?: number;
updateTime?: string;
taskJson?: string;
version: string;
}
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
const sceneStatus = {
0: '执行中',
1: '成功',
2: '失败',
3: '部分成功'
}
const SceneList: React.FC<SceneListProps> = ({ onViewDetail, onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
const [visible, setVisible] = useState(false);
const [editDataShow, setEditDataShow] = useState(false);
const [sceneName, setSceneName] = useState('');
const [scenes, setScenes] = useState<sceneItem[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [editLoading, setEditLoading] = useState<boolean>(false);
const [pageNum, setPageNum] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
const [total, setTotal] = useState<number>(0);
const [formData, setFormData] = useState<sceneItem>({
applicableTask: '',
relatedAgent: '',
sceneCode: '',
sceneIntro: '',
sceneName: '',
sceneType: '',
taskJson: '',
version: '',
});
const state = useRef({
pageNum: 1,
pageSize: 10
});
const navigate = useNavigate();
const scenesDataSearch = async (pageNumParam?: number, pageSizeParam?: number, sceneNameParam?: string) => {
const parmy = {
pageNum: pageNumParam ?? state.current.pageNum,
pageSize: pageSizeParam ?? state.current.pageSize,
sceneName: sceneNameParam ?? sceneName
}
setLoading(true);
const res = await requestCrudService.get(`${zncmUrl}/scene/scenes`, parmy);
setLoading(false);
res.data.records.forEach((item) => {
if (item.taskJson) {
item.taskJson = JSON.parse(item.taskJson);
}
})
console.log('res.data.records', res.data.records)
setScenes(res.data.records || []);
setTotal(res.data.total || 0);
}
const addScene = async () => {
const res = await requestCrudService.post(`${zncmUrl}/scene/sceneAdd`, formData);
setVisible(false);
MessagePlugin.success('新增场景成功');
await scenesDataSearch();
}
const updateScene = async () => {
setEditLoading(true);
const res = await requestCrudService.post(`${zncmUrl}/scene/sceneUpdate`, {
...formData,
});
setEditLoading(false);
setEditDataShow(false);
MessagePlugin.success('更新场景成功');
await scenesDataSearch();
}
const handleDataChange = (key: keyof typeof formData, value: string | number) => {
setFormData((prev) => ({
...prev,
[key]: value,
}));
};
const showDialog = () => {
setFormData({
applicableTask: '',
relatedAgent: '',
sceneCode: '',
sceneIntro: '',
sceneName: '',
sceneType: '',
version: ''
});
setVisible(true);
};
const editShowChange = async (item: sceneItem) => {
const response = await fetch('https://ch1.b.uat.4.cn/zncmweb/templates/6b_template.json');
const data = await response.json();
console.log('response', JSON.stringify(data));
// let taskJson =
setFormData({
applicableTask: item.applicableTask,
deleted: item.deleted,
id: item.id,
relatedAgent: item.relatedAgent,
sceneCode: item.sceneCode,
sceneIntro: item.sceneIntro,
sceneName: item.sceneName,
sceneType: item.sceneType,
status: item.status,
taskJson: JSON.stringify(data),
version: item.version
});
setEditDataShow(true);
}
const { Group: RadioGroup, Button: RadioButton } = Radio;
const delDataChange = async (item: sceneItem) => {
const res = await requestCrudService.delete(`${zncmUrl}/scene/sceneDelete/${item.id}`)
console.log(res);
MessagePlugin.success('场景删除成功');
await scenesDataSearch();
}
const handleConfirm = (item: sceneItem) => {
// 确认删除逻辑
console.log('用户点击了确认');
delDataChange(item);
return true; // 返回 true 表示确认执行
};
const handleCancel = () => {
// 取消删除逻辑
console.log('用户点击了取消');
return true; // 返回 true 表示取消
};
const pageIndexChange = async (value: number) => {
state.current.pageNum = value;
setPageNum(value);
await scenesDataSearch();
}
const pageSizeChange = async (value: number) => {
state.current.pageSize = value;
setPageSize(value);
await scenesDataSearch();
}
// 回车处理
const handleKeyDown = async (e) => {
if ((e.metaKey || e.ctrlKey || e.shiftKey) && e.key === "Enter") {
e.preventDefault();
} else if (e.keyCode === 13) {
e.preventDefault();
await scenesDataSearch();
} else if (e.key === "/") {
e.preventDefault();
}
};
useEffect(() => {
scenesDataSearch();
}, [])
return (
<div className="space-y-6">
{/* 页面标题区 */}
<div className="flex items-center justify-between">
<div>
<h1 className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'} mb-2`}>业务链条管理</h1>
<p className={`${isDark ? 'text-slate-400' : 'text-gray-600'} text-sm`}>管理和配置宣传策略流程模板</p>
</div>
<Button theme="primary" onClick={showDialog}>
<Plus className="w-4 h-4 mr-2" />
<span className="text-sm font-medium">新建场景</span>
</Button>
</div>
{/* 搜索和筛选区 */}
<div className="flex items-center gap-4">
<div className="relative flex-1 max-w-md">
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${isDark ? 'text-slate-500' : 'text-gray-400'}`} />
<input
type="text"
value={sceneName}
onChange={(e) => setSceneName(e.target.value)}
onKeyDown={(e) => handleKeyDown(e)}
placeholder="搜索场景名称..."
className={`w-full pl-10 pr-4 py-2.5 ${isDark ? 'bg-slate-800/30' : 'bg-gray-50'} border ${isDark ? 'border-blue-800/30' : 'border-gray-300'} rounded-lg ${isDark ? 'text-white' : 'text-gray-900'} placeholder-${isDark ? 'text-slate-500' : 'text-gray-500'} text-sm focus:outline-none focus:border-blue-500/50 transition-colors`}
/>
</div>
{/* <button className={`px-4 py-2.5 ${isDark ? 'bg-slate-800/50 border-blue-800/30' : 'bg-gray-100 border-gray-300'} rounded-lg ${isDark ? 'bg-slate-700/50 border-blue-800/30 text-slate-300 hover:bg-slate-600/50' : 'bg-gray-100 border-gray-300 text-gray-700 hover:bg-gray-200'} transition-colors`}>
全部类型
</button>
<button className={`px-4 py-2.5 ${isDark ? 'bg-slate-800/50 border-blue-800/30' : 'bg-gray-100 border-gray-300'} rounded-lg ${isDark ? 'bg-slate-700/50 border-blue-800/30 text-slate-300 hover:bg-slate-600/50' : 'bg-gray-100 border-gray-300 text-gray-700 hover:bg-gray-200'} transition-colors`}>
全部状态
</button> */}
</div>
{/* 场景列表 */}
<div className={`${isDark ? 'bg-slate-800/30' : 'bg-white'} backdrop-blur-xl border ${isDark ? 'border-blue-800/20' : 'border-gray-200'} rounded-xl overflow-hidden`}>
<Loading loading={loading} text="正在加载...">
<table className="w-full">
<thead>
<tr className={`border-b ${isDark ? 'border-blue-800/20' : 'border-gray-200'}`}>
<th className={`text-left px-6 py-4 text-xs font-semibold ${isDark ? 'text-slate-400' : 'text-gray-600'} uppercase tracking-wider`}>场景名称</th>
<th className={`text-left px-6 py-4 text-xs font-semibold ${isDark ? 'text-slate-400' : 'text-gray-600'} uppercase tracking-wider`}>场景类型</th>
<th className={`text-left px-6 py-4 text-xs font-semibold ${isDark ? 'text-slate-400' : 'text-gray-600'} uppercase tracking-wider`}>适用任务</th>
<th className={`text-left px-6 py-4 text-xs font-semibold ${isDark ? 'text-slate-400' : 'text-gray-600'} uppercase tracking-wider`}>版本</th>
<th className={`text-left px-6 py-4 text-xs font-semibold ${isDark ? 'text-slate-400' : 'text-gray-600'} uppercase tracking-wider`}>更新时间</th>
<th className={`text-left px-6 py-4 text-xs font-semibold ${isDark ? 'text-slate-400' : 'text-gray-600'} uppercase tracking-wider`}>状态</th>
<th className={`text-left px-6 py-4 text-xs font-semibold ${isDark ? 'text-slate-400' : 'text-gray-600'} uppercase tracking-wider`}>操作</th>
</tr>
</thead>
<tbody>
{scenes.map((scene) => (
<tr key={scene.id} className={`border-b ${isDark ? 'border-blue-800/20' : 'border-gray-200'} hover:bg-slate-700/20 transition-colors`}>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-blue-500/20 flex items-center justify-center">
<Layers className="w-5 h-5 text-blue-400" />
</div>
<span className={`${isDark ? 'text-white' : 'text-gray-900'} font-medium`}>{scene.sceneName}</span>
</div>
</td>
<td className="px-6 py-4">
<span className={`${isDark ? 'text-slate-400' : 'text-gray-600'} text-sm`}>{scene.sceneType}</span>
</td>
<td className="px-6 py-4">
<span className={`${isDark ? 'text-slate-400' : 'text-gray-600'} text-sm`}>{scene.sceneIntro}</span>
</td>
<td className="px-6 py-4">
<span className={`${isDark ? 'text-slate-400' : 'text-gray-600'} text-sm`}>{scene.version}</span>
</td>
<td className="px-6 py-4">
<span className={`${isDark ? 'text-slate-400' : 'text-gray-600'} text-sm`}>{scene.updateTime}</span>
</td>
<td className="px-6 py-4">
<span
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${scene.status === 0
? 'bg-green-500/20 text-green-400'
: scene.status === 1
? 'bg-yellow-500/20 text-yellow-400'
: `${isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-500/20 text-gray-500'}`
}`}
>
<span
className={`w-1.5 h-1.5 rounded-full ${scene.status === 0
? 'bg-green-400'
: scene.status === 1
? 'bg-yellow-400'
: isDark ? 'bg-slate-400' : 'bg-gray-400'
}`}
/>
{sceneStatus[scene.status]}
</span>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-2">
<button
onClick={() => {
navigate(`/scene-detail/${scene.id}`);
}}
className="p-2 hover:bg-slate-700/50 rounded-lg transition-colors group"
title="查看"
>
<Eye className={`w-4 h-4 ${isDark ? 'text-slate-500' : 'text-gray-500'} group-hover:${isDark ? 'text-white' : 'text-gray-900'}`} />
</button>
{/* <button className="p-2 hover:bg-slate-700/50 rounded-lg transition-colors group" title="编辑" onClick={() => editShowChange(scene)}>
<Edit className={`w-4 h-4 ${isDark ? 'text-slate-500' : 'text-gray-500'} group-hover:${isDark ? 'text-white' : 'text-gray-900'}`} />
</button> */}
{/* <button className="p-2 hover:bg-slate-700/50 rounded-lg transition-colors group" title="复制">
<Copy className={`w-4 h-4 ${isDark ? 'text-slate-500' : 'text-gray-500'} group-hover:${isDark ? 'text-white' : 'text-gray-900'}`} />
</button> */}
{/* <button
onClick={() => onNavigate('task-scene')}
className="p-2 hover:bg-blue-500/20 rounded-lg transition-colors group"
title="启动"
>
<Play className={`w-4 h-4 ${isDark ? 'text-slate-500' : 'text-gray-500'} group-hover:text-blue-400`} />
</button> */}
{/* <Popconfirm
content="确认删除吗"
onConfirm={() => handleConfirm(scene)}
onCancel={handleCancel}
>
<button className="p-2 hover:bg-red-500/20 rounded-lg transition-colors group" title="删除">
<Trash2 className={`w-4 h-4 ${isDark ? 'text-slate-500' : 'text-gray-500'} group-hover:text-red-400`} />
</button>
</Popconfirm> */}
</div>
</td>
</tr>
))}
</tbody>
</table>
</Loading>
</div>
{/* 分页 */}
<div className="p-4 text-center">
<Pagination
total={total}
current={pageNum}
pageSize={pageSize}
onCurrentChange={(page) => pageIndexChange(Number(page))}
onPageSizeChange={(size) => pageSizeChange(Number(size))}
/>
</div>
{/* 新增对话框 */}
<Dialog
header="新增场景"
visible={visible}
onConfirm={addScene}
onCancel={() => setVisible(false)}
onClose={() => setVisible(false)}
closeOnOverlayClick={true}
>
<div className="mb-4">
<Input
label="场景名称"
value={formData.sceneName}
onChange={(val) => handleDataChange('sceneName', val)}
placeholder="请输入场景名称"
className="w-full"
/>
</div>
<div className="mb-4">
<Input
label="场景类型"
value={formData.sceneType}
onChange={(val) => handleDataChange('sceneType', val)}
placeholder="请输入场景类型"
className="w-full"
/>
</div>
<div className="mb-4">
<Input
label="适用任务"
value={formData.applicableTask}
onChange={(val) => handleDataChange('applicableTask', val)}
placeholder="请输入适用任务"
className="w-full"
/>
</div>
<div className="mb-4">
<Input
label="场景代码"
value={formData.sceneCode}
onChange={(val) => handleDataChange('sceneCode', val)}
placeholder="请输入场景代码"
className="w-full"
/>
</div>
<div className="mb-4">
<Input
label="版本"
value={formData.version}
onChange={(val) => handleDataChange('version', val)}
placeholder="请输入版本"
className="w-full"
/>
</div>
<div>
<Textarea
value={formData.sceneIntro}
onChange={(val) => handleDataChange('sceneIntro', val)}
placeholder="请输入场景简介"
className="w-full"
/>
</div>
</Dialog>
{/* 编辑对话框 */}
<Dialog
header="编辑场景"
visible={editDataShow}
onConfirm={updateScene}
confirmBtn={{
content: '确定',
theme: 'primary',
loading: editLoading,
}}
onCancel={() => setEditDataShow(false)}
onClose={() => setEditDataShow(false)}
closeOnOverlayClick={true}
>
<div className="mb-4">
<Input
label="场景名称"
value={formData.sceneName}
onChange={(val) => handleDataChange('sceneName', val)}
placeholder="请输入场景名称"
className="w-full"
/>
</div>
<div className="mb-4">
<Input
label="场景类型"
value={formData.sceneType}
onChange={(val) => handleDataChange('sceneType', val)}
placeholder="请输入场景类型"
className="w-full"
/>
</div>
<div className="mb-4">
<Input
label="适用任务"
value={formData.applicableTask}
onChange={(val) => handleDataChange('applicableTask', val)}
placeholder="请输入适用任务"
className="w-full"
/>
</div>
<div className="mb-4">
<Input
label="场景代码"
value={formData.sceneCode}
onChange={(val) => handleDataChange('sceneCode', val)}
placeholder="请输入场景代码"
className="w-full"
/>
</div>
<div className="mb-4">
<Input
label="版本"
value={formData.version}
onChange={(val) => handleDataChange('version', val)}
placeholder="请输入版本"
className="w-full"
/>
</div>
<div className="mb-4">
<Textarea
value={formData.sceneIntro}
onChange={(val) => handleDataChange('sceneIntro', val)}
placeholder="请输入场景简介"
className="w-full"
/>
</div>
{/* <div>
<Textarea
value={formData.taskJson}
onChange={(val) => handleDataChange('taskJson', val)}
placeholder="请输入场景JSON"
className="w-full"
/>
</div> */}
</Dialog>
</div>
);
};
export default SceneList;