980 lines
30 KiB
TypeScript
980 lines
30 KiB
TypeScript
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<LightAppSquareProps> = ({ onNavigate }) => {
|
|
const { theme } = useTheme();
|
|
const isDark = theme === "dark";
|
|
const [searchQuery, setSearchQuery] = useState("");
|
|
const [categoryFilter, setCategoryFilter] = useState<AppCategory | "all">(
|
|
"all"
|
|
);
|
|
const [recentApps, setRecentApps] = useState<LightAppType[]>([
|
|
"sentiment",
|
|
"event",
|
|
"account-pg",
|
|
]);
|
|
const [historyApps, setHistoryApps] = useState([]);
|
|
const [visible, setVisible] = useState(false);
|
|
const [editDataShow, setEditDataShow] = useState(false);
|
|
const [analysisData, setAnalysisData] = useState<analysisDataItem>({
|
|
appName: "",
|
|
appDesc: "",
|
|
appIcon: "",
|
|
jumpUrl: "",
|
|
appType: 2,
|
|
});
|
|
const [analysisList, setAnalysisList] = useState<analysisListData[]>([
|
|
{ key: "info", list: [] },
|
|
{ key: "account", list: [] },
|
|
]);
|
|
const [fileFail, setFileFail] = useState<UploadFile[]>([
|
|
// 如果需要默认回显图片,可以在这里初始化
|
|
// { name: '默认图片.png', url: 'https://tdesign.gtimg.com/site/avatar.jpg', status: 'fail' }
|
|
]);
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
const [pageNum, setPageNum] = useState<number>(1);
|
|
const [pageSize, setPageSize] = useState<number>(10); // 每页显示 10 条
|
|
const [total, setTotal] = useState<number>(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<AppCategory, LightApp[]> = {
|
|
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<AppCategory, { text: string; bg: string }> = {
|
|
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<string> => {
|
|
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 (
|
|
//获取最近使用应用列表
|
|
|
|
<div className="space-y-6">
|
|
{/* 顶部工具栏 */}
|
|
<div
|
|
className={`rounded-xl border p-4 ${
|
|
isDark
|
|
? "bg-slate-800/50 border-slate-700"
|
|
: "bg-white border-gray-200"
|
|
}`}
|
|
>
|
|
<div className="flex flex-col lg:flex-row lg:items-center gap-4">
|
|
{/* 面包屑和标题 */}
|
|
<div className="flex items-center gap-2 flex-1">
|
|
<Layout
|
|
className={`w-5 h-5 ${isDark ? "text-blue-400" : "text-red-500"}`}
|
|
/>
|
|
<span
|
|
className={`text-sm ${
|
|
isDark ? "text-slate-400" : "text-gray-500"
|
|
}`}
|
|
>
|
|
轻应用中心 /
|
|
</span>
|
|
<span
|
|
className={`text-sm font-medium ${
|
|
isDark ? "text-white" : "text-gray-900"
|
|
}`}
|
|
>
|
|
应用广场
|
|
</span>
|
|
</div>
|
|
|
|
{/* 搜索框 */}
|
|
<div className="flex-1 max-w-md">
|
|
<div className="relative">
|
|
<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={searchQuery}
|
|
onChange={(e) => 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"
|
|
}`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 筛选和操作 */}
|
|
<div className="flex items-center gap-3">
|
|
{/* <select
|
|
value={categoryFilter}
|
|
onChange={(e) => setCategoryFilter(e.target.value as AppCategory | 'all')}
|
|
className={`px-3 py-2 rounded-lg border text-sm transition-colors focus:outline-none focus:ring-2 ${isDark
|
|
? 'bg-slate-900 border-slate-600 text-slate-200 focus:border-blue-500'
|
|
: 'bg-gray-50 border-gray-300 text-gray-700 focus:border-red-500'
|
|
}`}
|
|
>
|
|
<option value="all">全部分类</option>
|
|
<option value="info">信息分析类</option>
|
|
<option value="account">账号分析类</option>
|
|
<option value="event">事件分析类</option>
|
|
<option value="topic">专题分析类</option>
|
|
</select> */}
|
|
<button
|
|
onClick={() => onNavigate("light-app-records")}
|
|
className={`px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 transition-colors ${
|
|
isDark
|
|
? "bg-slate-700 text-slate-300 hover:bg-slate-600"
|
|
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
|
|
}`}
|
|
>
|
|
<Clock className="w-4 h-4" />
|
|
运行记录
|
|
</button>
|
|
<Button theme="primary" onClick={showDialog}>
|
|
<span className="flex items-center">
|
|
<Plus className="w-4 h-4 mr-[10px]" />
|
|
添加
|
|
</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 最近使用 */}
|
|
{historyApps.length > 0 &&
|
|
searchQuery === "" &&
|
|
categoryFilter === "all" && (
|
|
<div
|
|
className={`rounded-xl border p-4 ${
|
|
isDark
|
|
? "bg-slate-800/50 border-slate-700"
|
|
: "bg-white border-gray-200"
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<Clock
|
|
className={`w-4 h-4 ${
|
|
isDark ? "text-blue-400" : "text-red-500"
|
|
}`}
|
|
/>
|
|
<h3
|
|
className={`text-sm font-medium ${
|
|
isDark ? "text-slate-300" : "text-gray-700"
|
|
}`}
|
|
>
|
|
最近使用
|
|
</h3>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{historyApps.map((app) => {
|
|
// const app = historyApps.find((a) => a.id === appId);
|
|
// if (!app) return null;
|
|
// const Icon = app.icon;
|
|
return (
|
|
<button
|
|
// key={app.id}
|
|
// onClick={() => handleLaunchApp(app)}
|
|
className={`px-4 py-2 rounded-lg border text-sm font-medium flex items-center gap-2 transition-all hover:scale-105 ${
|
|
isDark
|
|
? `bg-slate-700/50 border-slate-600 text-slate-300 hover:bg-slate-700`
|
|
: `bg-gray-50 border-gray-200 text-gray-700 hover:bg-gray-100`
|
|
}`}
|
|
>
|
|
{/* <Icon className={`w-4 h-4 ${app.iconColor}`} /> */}
|
|
{app.appName}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 应用分组区 */}
|
|
<Loading loading={loading} text="正在加载中...">
|
|
<div className="space-y-6">
|
|
{analysisList.map((section) => {
|
|
const config = categoryConfig[section.key];
|
|
const colorStyle = getCategoryColor(section.key);
|
|
|
|
return (
|
|
<div key={section.key} className="space-y-4">
|
|
{/* 标题区域 */}
|
|
<div className="flex items-center gap-2">
|
|
<h2
|
|
className={`text-lg font-semibold ${
|
|
isDark ? "text-white" : "text-gray-900"
|
|
}`}
|
|
>
|
|
{config.title}
|
|
</h2>
|
|
<span
|
|
className={`px-2 py-0.5 rounded text-xs font-medium ${colorStyle.bg} ${colorStyle.text}`}
|
|
>
|
|
{section.list.length} 个应用
|
|
</span>
|
|
</div>
|
|
|
|
{/* 卡片网格 */}
|
|
{section.list.length > 0 ? (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{section.list.map((app) => (
|
|
<AppCard
|
|
key={app.id}
|
|
app={app}
|
|
isDark={isDark}
|
|
hoverColor={config.hoverColor}
|
|
btnClass={
|
|
isDark ? config.btnTheme.dark : config.btnTheme.light
|
|
}
|
|
delDataChange={delDataChange}
|
|
editShowChange={editShowChange}
|
|
/>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div
|
|
className={`text-center py-10 text-sm ${
|
|
isDark ? "text-slate-500" : "text-gray-400"
|
|
}`}
|
|
>
|
|
<Empty description="暂无数据" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</Loading>
|
|
<div className="p-4 text-center">
|
|
<Pagination
|
|
total={total}
|
|
current={pageNum}
|
|
pageSize={pageSize}
|
|
onCurrentChange={(page) => pageIndexChange(Number(page))}
|
|
onPageSizeChange={(size) => pageSizeChange(Number(size))} // 每页数量变化时的回调
|
|
/>
|
|
</div>
|
|
|
|
{/* 底部提示 */}
|
|
<div
|
|
className={`p-4 rounded-xl border ${
|
|
isDark
|
|
? "bg-blue-500/10 border-blue-500/30"
|
|
: "bg-blue-50 border-blue-200"
|
|
}`}
|
|
>
|
|
<div className="flex items-start gap-3">
|
|
<Sparkles
|
|
className={`w-5 h-5 mt-0.5 ${
|
|
isDark ? "text-blue-400" : "text-blue-500"
|
|
}`}
|
|
/>
|
|
<div>
|
|
<p
|
|
className={`text-sm font-medium ${
|
|
isDark ? "text-blue-300" : "text-blue-800"
|
|
}`}
|
|
>
|
|
关于轻应用中心
|
|
</p>
|
|
<p
|
|
className={`mt-1 text-sm ${
|
|
isDark ? "text-blue-400/80" : "text-blue-700"
|
|
}`}
|
|
>
|
|
轻应用中心提供了一系列快速分析工具,适合处理临时性的分析需求。相比完整的任务级应用,轻应用更加轻量便捷,可以快速调用多种工具完成分析工作。运行结果可保存快照、导出或转为正式任务。
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Dialog
|
|
header="添加应用"
|
|
visible={visible}
|
|
onClose={handleClose} // 监听关闭事件
|
|
onConfirm={handleOk} // 点击确定按钮
|
|
onCancel={handleClose} // 点击取消按钮
|
|
closeOnOverlayClick={true} // 点击遮罩层是否关闭
|
|
>
|
|
<div className="mb-[10px]">
|
|
<Upload
|
|
// 对应 v-model
|
|
files={fileFail}
|
|
onChange={handleChange}
|
|
// 对应 theme="image"
|
|
theme="image"
|
|
requestMethod={imgUpload}
|
|
// 对应 accept
|
|
accept="image/*"
|
|
tips="系统logo"
|
|
// 对应 :show-image-file-name
|
|
showImageFileName={showImageFileName}
|
|
// 对应 :format-response
|
|
formatResponse={formatResponse}
|
|
// 可选:限制上传数量(演示单张)
|
|
multiple={false}
|
|
// 可选:上传失败时的提示(如果接口直接返回 4xx/5xx)
|
|
onFail={({ e, file }) => {
|
|
MessagePlugin.error(`文件 ${file.name} 上传失败`);
|
|
}}
|
|
/>
|
|
</div>
|
|
<div className="mb-[10px] overflow-hidden">
|
|
<Row>
|
|
<Col span={2}>
|
|
<div>名称:</div>
|
|
</Col>
|
|
<Col span={9}>
|
|
<Input
|
|
placeholder="请输入应用名称"
|
|
value={analysisData.appName}
|
|
onChange={(val) => handleDataChange("appName", val)}
|
|
clearable
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
<div className="mb-[10px] overflow-hidden">
|
|
<Row>
|
|
<Col span={2}>
|
|
<div>详情:</div>
|
|
</Col>
|
|
<Col span={9}>
|
|
<Textarea
|
|
placeholder="请输入应用详情"
|
|
value={analysisData.appDesc}
|
|
onChange={(val) => handleDataChange("appDesc", val)}
|
|
autosize
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
<div className="mb-[10px] overflow-hidden">
|
|
<Row>
|
|
<Col span={2}>
|
|
<div>类型:</div>
|
|
</Col>
|
|
<Col span={9}>
|
|
<RadioGroup
|
|
variant="primary-filled" // 对应 variant="primary-filled"
|
|
value={analysisData.appType} // 绑定状态值
|
|
onChange={(value) => handleDataChange("appType", Number(value))}
|
|
>
|
|
{/* 3. 使用 RadioButton */}
|
|
<RadioButton value={1}>内部应用</RadioButton>
|
|
<RadioButton value={2}>外部应用</RadioButton>
|
|
<RadioButton value={3}>第三方集成</RadioButton>
|
|
</RadioGroup>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
<div className="mb-[10px] overflow-hidden">
|
|
<Row>
|
|
<Col span={2}>
|
|
<div>链接:</div>
|
|
</Col>
|
|
<Col span={9}>
|
|
<Input
|
|
placeholder="请输入链接"
|
|
value={analysisData.jumpUrl}
|
|
onChange={(val) => handleDataChange("jumpUrl", val)}
|
|
clearable
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
</Dialog>
|
|
<Dialog
|
|
header="编辑应用"
|
|
visible={editDataShow}
|
|
onClose={handleCloseEdit} // 监听关闭事件
|
|
onConfirm={handleOkEdit} // 点击确定按钮
|
|
onCancel={handleCloseEdit} // 点击取消按钮
|
|
closeOnOverlayClick={true} // 点击遮罩层是否关闭
|
|
>
|
|
<div className="mb-[10px]">
|
|
{/* <Upload
|
|
// 对应 v-model
|
|
files={fileFail}
|
|
onChange={handleChange}
|
|
// 对应 theme="image"
|
|
theme="image"
|
|
requestMethod={imgUpload}
|
|
// 对应 accept
|
|
accept="image/*"
|
|
tips="系统logo"
|
|
// 对应 :show-image-file-name
|
|
showImageFileName={showImageFileName}
|
|
// 对应 :format-response
|
|
formatResponse={formatResponse}
|
|
// 可选:限制上传数量(演示单张)
|
|
multiple={false}
|
|
// 可选:上传失败时的提示(如果接口直接返回 4xx/5xx)
|
|
onFail={({ e, file }) => {
|
|
MessagePlugin.error(`文件 ${file.name} 上传失败`);
|
|
}} /> */}
|
|
</div>
|
|
<div className="mb-[10px] overflow-hidden">
|
|
<Row>
|
|
<Col span={2}>
|
|
<div>名称:</div>
|
|
</Col>
|
|
<Col span={9}>
|
|
<Input
|
|
placeholder="请输入应用名称"
|
|
value={analysisData.appName}
|
|
onChange={(val) => handleDataChange("appName", val)}
|
|
clearable
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
<div className="mb-[10px] overflow-hidden">
|
|
<Row>
|
|
<Col span={2}>
|
|
<div>详情:</div>
|
|
</Col>
|
|
<Col span={9}>
|
|
<Textarea
|
|
placeholder="请输入应用详情"
|
|
value={analysisData.appDesc}
|
|
onChange={(val) => handleDataChange("appDesc", val)}
|
|
autosize
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
<div className="mb-[10px] overflow-hidden">
|
|
<Row>
|
|
<Col span={2}>
|
|
<div>类型:</div>
|
|
</Col>
|
|
<Col span={9}>
|
|
<RadioGroup
|
|
variant="primary-filled" // 对应 variant="primary-filled"
|
|
value={analysisData.appType} // 绑定状态值
|
|
onChange={(value) => handleDataChange("appType", Number(value))}
|
|
>
|
|
{/* 3. 使用 RadioButton */}
|
|
<RadioButton value={1}>内部应用</RadioButton>
|
|
<RadioButton value={2}>外部应用</RadioButton>
|
|
<RadioButton value={3}>第三方集成</RadioButton>
|
|
</RadioGroup>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
<div className="mb-[10px] overflow-hidden">
|
|
<Row>
|
|
<Col span={2}>
|
|
<div>链接:</div>
|
|
</Col>
|
|
<Col span={9}>
|
|
<Input
|
|
placeholder="请输入链接"
|
|
value={analysisData.jumpUrl}
|
|
onChange={(val) => handleDataChange("jumpUrl", val)}
|
|
clearable
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LightAppSquare;
|