import React, { useState, useMemo, useEffect, useRef } from "react"; import { useTheme } from "../contexts/ThemeContext"; import { Search, ChevronRight, PlayCircle, Clock, Star, FileText, User, Zap, BookOpen, TrendingUp, MessageSquare, Network, Link2, Filter, Layout, Sparkles, BarChart3, Users, Target, PieChart, Send, Plus, } from "lucide-react"; import { Button, Dialog, Radio, Upload, Input, MessagePlugin, Row, Col, Textarea, Loading, Empty, Pagination, } from "tdesign-react"; import { get } from "../lib/api"; import type { UploadFile, UploadProps } from "tdesign-react"; import { requestCrudService } from "@/service/request-axios-service"; import { AppCard } from "@/components/square/AppCard"; interface LightAppSquareProps { onNavigate: (page: string, appId?: string) => void; } // 定义轻应用类型 export type LightAppType = | "info-ty" // 信息TY | "info-pg" // 信息PG | "info-spread" // 信息传播分析 | "account-pg" // 账号PG | "sentiment" // 三情分析 | "event" // 事件分析 | "topic"; // 专题分析 // 应用分类 type AppCategory = "info" | "account" | "event" | "topic"; interface LightApp { id: LightAppType; name: string; description: string; category: AppCategory; categoryName: string; input: string; output: string; icon: React.ElementType; color: string; bgColor: string; borderColor: string; iconColor: string; dimensions?: string[]; // 可选的额外分析维度 } interface analysisDataItem { appName: string; appDesc: string; appIcon: string; jumpUrl: string; appType: number; } interface analysisListItem { id: number; categoryName: string; category: string; appName: string; appDesc: string; appIcon: string; jumpUrl: string; appType: number; } interface analysisListData { key: "info" | "account" | "event" | "topic"; list: analysisListItem[]; } // 应用数据 const lightApps: LightApp[] = [ // 信息分析类 { id: "info-ty", name: "信息TY", description: "围绕单条或多条信息进行TY向分析", category: "info", categoryName: "信息分析类", input: "信息文本/链接/素材", output: "信息特征、重点摘要、分析判断", icon: FileText, color: "blue", bgColor: "bg-blue-500/10", borderColor: "border-blue-500/30", iconColor: "text-blue-500", dimensions: ["基础分析", "风险提示", "摘要结论"], }, { id: "info-pg", name: "信息PG", description: "围绕单条或多条信息进行PG向分析", category: "info", categoryName: "信息分析类", input: "信息文本/链接/素材", output: "画像特征、相关判断", icon: User, color: "cyan", bgColor: "bg-cyan-500/10", borderColor: "border-cyan-500/30", iconColor: "text-cyan-500", dimensions: ["账号画像", "内容倾向", "行为特征"], }, { id: "info-spread", name: "信息传播分析", description: "分析信息传播路径、传播范围与传播表现", category: "info", categoryName: "信息分析类", input: "信息链接/信息集合", output: "传播过程、传播节点、传播影响", icon: Network, color: "teal", bgColor: "bg-teal-500/10", borderColor: "border-teal-500/30", iconColor: "text-teal-500", dimensions: ["传播路径", "传播节点", "传播表现", "影响摘要"], }, // 账号分析类 { id: "account-pg", name: "账号PG", description: "围绕账号内容与行为特征进行PG分析", category: "account", categoryName: "账号分析类", input: "账号ID/账号链接/内容集合", output: "账号画像、内容倾向、行为特征", icon: Users, color: "purple", bgColor: "bg-purple-500/10", borderColor: "border-purple-500/30", iconColor: "text-purple-500", dimensions: ["账号画像", "内容倾向", "行为特征", "影响判断"], }, // 事件分析类 { id: "sentiment", name: "三情分析", description: "对事件进行舆情、社情、民情综合分析", category: "event", categoryName: "事件分析类", input: "事件关键词/事件链接/素材", output: "情绪概览、关注焦点、风险提示", icon: MessageSquare, color: "orange", bgColor: "bg-orange-500/10", borderColor: "border-orange-500/30", iconColor: "text-orange-500", dimensions: ["舆情分析", "社情分析", "民情分析", "风险提示"], }, { id: "event", name: "事件分析", description: "围绕某个事件进行整体分析", category: "event", categoryName: "事件分析类", input: "事件名称/事件材料/事件链接", output: "事件脉络、关键节点、风险判断", icon: Target, color: "red", bgColor: "bg-red-500/10", borderColor: "border-red-500/30", iconColor: "text-red-500", dimensions: ["事件脉络", "关键节点", "风险判断", "初步建议"], }, // 专题分析类 { id: "topic", name: "专题分析", description: "对某专题做集中分析", category: "topic", categoryName: "专题分析类", input: "专题名称/专题材料/文本集合", output: "专题结构、核心问题、观点聚合、结论建议", icon: BookOpen, color: "green", bgColor: "bg-green-500/10", borderColor: "border-green-500/30", iconColor: "text-green-500", dimensions: ["专题结构", "核心问题", "观点聚合", "结论建议"], }, ]; const categoryConfig = { info: { title: "内部应用", hoverColor: "group-hover:text-blue-500", // 内部应用 hover 蓝色 btnTheme: { dark: "bg-blue-500/20 text-blue-400 hover:bg-blue-500/30 border-blue-500/30", light: "bg-blue-50 text-blue-600 hover:bg-blue-100 border-blue-200", }, }, account: { title: "外部应用", hoverColor: "group-hover:text-purple-500", // 外部应用 hover 紫色 btnTheme: { dark: "bg-purple-500/20 text-purple-400 hover:bg-purple-500/30 border-purple-500/30", light: "bg-red-50 text-red-600 hover:bg-red-100 border-red-200", // 保留你原本外部应用的红色风格 }, }, }; const categoryOrder: AppCategory[] = ["info", "account", "event", "topic"]; const zncmUrl = import.meta.env.VITE_API_BASE_URL || ""; const LightAppSquare: React.FC = ({ onNavigate }) => { const { theme } = useTheme(); const isDark = theme === "dark"; const [searchQuery, setSearchQuery] = useState(""); const [categoryFilter, setCategoryFilter] = useState( "all" ); const [recentApps, setRecentApps] = useState([ "sentiment", "event", "account-pg", ]); const [historyApps, setHistoryApps] = useState([]); const [visible, setVisible] = useState(false); const [editDataShow, setEditDataShow] = useState(false); const [analysisData, setAnalysisData] = useState({ appName: "", appDesc: "", appIcon: "", jumpUrl: "", appType: 2, }); const [analysisList, setAnalysisList] = useState([ { key: "info", list: [] }, { key: "account", list: [] }, ]); const [fileFail, setFileFail] = useState([ // 如果需要默认回显图片,可以在这里初始化 // { name: '默认图片.png', url: 'https://tdesign.gtimg.com/site/avatar.jpg', status: 'fail' } ]); const [loading, setLoading] = useState(false); const [pageNum, setPageNum] = useState(1); const [pageSize, setPageSize] = useState(10); // 每页显示 10 条 const [total, setTotal] = useState(0); const state = useRef({ pageNum: 1, pageSize: 10, }); const showImageFileName = true; const service = requestCrudService; // 解构出 Group 和 Button,方便使用 const { Group: RadioGroup, Button: RadioButton } = Radio; // 用于格式化接口返回的数据,使其符合 TDesign 的要求 const formatResponse = (res: any) => { // 假设接口返回 { code: 0, data: { url: '...' } } // 如果接口报错,可以在这里处理,让组件显示失败状态 if (res.code !== 0) { return { error: res.message || "上传失败", url: "", name: "", }; } return { url: res.data.url, name: res.data.name, }; }; const handleChange: UploadProps["onChange"] = (files) => { setFileFail(files); console.log("当前文件列表:", files); }; // 过滤应用 const filteredApps = useMemo(() => { return lightApps.filter((app) => { const matchesSearch = searchQuery === "" || app.name.toLowerCase().includes(searchQuery.toLowerCase()) || app.description.toLowerCase().includes(searchQuery.toLowerCase()) || app.input.toLowerCase().includes(searchQuery.toLowerCase()); const matchesCategory = categoryFilter === "all" || app.category === categoryFilter; return matchesSearch && matchesCategory; }); }, [searchQuery, categoryFilter]); // 按分类分组 const groupedApps = useMemo(() => { const groups: Record = { info: [], account: [], event: [], topic: [], }; filteredApps.forEach((app) => { groups[app.category].push(app); }); return groups; }, [filteredApps]); // 处理应用启动 const handleLaunchApp = (app: LightApp) => { console.log("启动应用:", app); // 更新最近使用 setRecentApps((prev) => { const filtered = prev.filter((id) => id !== app.id); return [app.id, ...filtered].slice(0, 5); }); onNavigate("light-app-run", app.id); }; // 获取分类颜色 const getCategoryColor = (category: AppCategory) => { const colors: Record = { info: { text: isDark ? "text-blue-400" : "text-blue-600", bg: isDark ? "bg-blue-500/20" : "bg-blue-50", }, account: { text: isDark ? "text-purple-400" : "text-purple-600", bg: isDark ? "bg-purple-500/20" : "bg-purple-50", }, event: { text: isDark ? "text-orange-400" : "text-orange-600", bg: isDark ? "bg-orange-500/20" : "bg-orange-50", }, topic: { text: isDark ? "text-green-400" : "text-green-600", bg: isDark ? "bg-green-500/20" : "bg-green-50", }, }; return colors[category]; }; // 获取应用列表 const analysisListSearch = async () => { const parmy = { appName: searchQuery, pageNum: state.current.pageNum, pageSize: state.current.pageSize, }; setLoading(true); const res = await service.post(`${zncmUrl}/app/analysis/list`, parmy); console.log(res); setLoading(false); const result = res.data; const listData: analysisListData[] = [ { key: "info", list: [] }, { key: "account", list: [] }, ]; result.records.map((item: analysisListItem) => { if (item.appType === 1) { item.category = "info"; item.categoryName = "内部应用"; listData[0].list.push(item); } else if (item.appType === 2) { item.category = "account"; item.categoryName = "外部应用"; listData[1].list.push(item); } setAnalysisList(listData); }); setTotal(result.total); }; // 添加应用 const analysieAddListChange = async () => { const res = await service.post(`${zncmUrl}/app/analysis/add`, analysisData); console.log(res); MessagePlugin.success("添加成功"); await analysisListSearch(); }; // 回车处理 const handleKeyDown = async (e) => { if ((e.metaKey || e.ctrlKey || e.shiftKey) && e.key === "Enter") { e.preventDefault(); } else if (e.keyCode === 13) { e.preventDefault(); await analysisListSearch(); } else if (e.key === "/") { e.preventDefault(); } }; // 添加应用 const showDialog = () => { setAnalysisData({ appName: "", appDesc: "", appIcon: "", jumpUrl: "", appType: 2, }); setFileFail([]); setVisible(true); }; const handleOk = async () => { await analysieAddListChange(); setVisible(false); }; const handleClose = () => { setVisible(false); }; // 编辑应用 const editShowChange = (item: analysisDataItem) => { setAnalysisData(item); if (item.appIcon) { setFileFail([ { name: "默认图片.png", url: item.appIcon, status: "fail" }, ]); } setEditDataShow(true); }; const handleOkEdit = () => { editDataChange(); setEditDataShow(false); }; const handleCloseEdit = () => { setEditDataShow(false); }; const getBase64 = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result as string); reader.onerror = (error) => reject(error); }); }; const imgUpload: UploadProps["requestMethod"] = async (file: any) => { console.log("file", file); const base64 = await getBase64(file.raw); handleDataChange("appIcon", base64); return { status: "success", // response 对象里的数据会传给 formatResponse response: { url: "", name: "", }, }; }; // 通用的处理函数 const handleDataChange = ( key: keyof typeof analysisData, value: string | number ) => { setAnalysisData((prev) => ({ ...prev, [key]: value, })); }; // 删除应用 const delDataChange = async (item: analysisListItem) => { console.log("删除", item); setLoading(true); const res = await service.get(`${zncmUrl}/app/analysis/delete/${item.id}`); console.log(res); MessagePlugin.success("操作成功"); await analysisListSearch(); }; const getHistoryApps = async () => { const reqParam = { limit: 5, }; const res = await service.get(`${zncmUrl}/app/history/recent`, reqParam); if (res && res.data) { console.log({ resData: res.data }); setHistoryApps(res.data); } }; // 编辑应用 const editDataChange = async () => { setLoading(true); const res = await service.post( `${zncmUrl}/app/analysis/update`, analysisData ); console.log(res); MessagePlugin.success("操作成功"); await analysisListSearch(); }; const pageIndexChange = async (value: number) => { state.current.pageNum = value; setPageNum(value); await analysisListSearch(); }; const pageSizeChange = async (value: number) => { state.current.pageSize = value; setPageSize(value); await analysisListSearch(); }; // 发送请求 useEffect(() => { analysisListSearch(); getHistoryApps(); }, []); return ( //获取最近使用应用列表
{/* 顶部工具栏 */}
{/* 面包屑和标题 */}
轻应用中心 / 应用广场
{/* 搜索框 */}
setSearchQuery(e.target.value)} onKeyDown={(e) => handleKeyDown(e)} placeholder="搜索应用名称或分析对象..." className={`w-full pl-10 pr-4 py-2 rounded-lg border transition-colors focus:outline-none focus:ring-2 ${ isDark ? "bg-slate-900 border-slate-600 text-slate-200 placeholder-slate-500 focus:border-blue-500 focus:ring-blue-500/20" : "bg-gray-50 border-gray-300 text-gray-900 placeholder-gray-400 focus:border-red-500 focus:ring-red-500/20" }`} />
{/* 筛选和操作 */}
{/* */}
{/* 最近使用 */} {historyApps.length > 0 && searchQuery === "" && categoryFilter === "all" && (

最近使用

{historyApps.map((app) => { // const app = historyApps.find((a) => a.id === appId); // if (!app) return null; // const Icon = app.icon; return ( ); })}
)} {/* 应用分组区 */}
{analysisList.map((section) => { const config = categoryConfig[section.key]; const colorStyle = getCategoryColor(section.key); return (
{/* 标题区域 */}

{config.title}

{section.list.length} 个应用
{/* 卡片网格 */} {section.list.length > 0 ? (
{section.list.map((app) => ( ))}
) : (
)}
); })}
pageIndexChange(Number(page))} onPageSizeChange={(size) => pageSizeChange(Number(size))} // 每页数量变化时的回调 />
{/* 底部提示 */}

关于轻应用中心

轻应用中心提供了一系列快速分析工具,适合处理临时性的分析需求。相比完整的任务级应用,轻应用更加轻量便捷,可以快速调用多种工具完成分析工作。运行结果可保存快照、导出或转为正式任务。

{ MessagePlugin.error(`文件 ${file.name} 上传失败`); }} />
名称:
handleDataChange("appName", val)} clearable />
详情: