新增历史搜索、下载附件、分页调整页面功能

This commit is contained in:
Li1304553726 2025-05-29 11:14:03 +08:00
parent fbc2c567e2
commit 33185aa340
5 changed files with 669 additions and 189 deletions

View File

@ -231,7 +231,11 @@ const DashboardPage = () => {
type: 'value',
name: '故障数量'
},
series: data.series
series: data.series,
label: {
show: true,
position: 'top',
}
};
};

View File

@ -10,6 +10,7 @@ import {
Divider,
Tabs,
Image,
message,
} from "antd";
import {
DownloadOutlined,
@ -28,6 +29,7 @@ interface ResourceFileListProps {
deviceId: string;
}
//表示资源项的结构
interface ResourceItem {
id?: string;
title?: string;
@ -52,16 +54,61 @@ export default function ResourceFileList({ deviceId }: ResourceFileListProps) {
enabled: !!deviceId, // 只有当deviceId存在时才执行查询
}
);
useEffect(() => {
// 每3秒刷新一次数据
// 每5秒刷新一次数据确保显示最新的附件信息。
const intervalId = setInterval(() => {
if (deviceId) {
refetch();
}
}, 3000);
}, 5000);
return () => clearInterval(intervalId);
}, [deviceId, refetch]);
// 下载文件的函数,支持中文文件名
const downloadFile = async (item: any) => {
console.log("下载文件:", item);
try {
const url = item.originalUrl || getFileUrl(item.fileId);
if (!url) {
message.error("文件链接无效");
return;
}
// 获取文件扩展名
const fileExtension = item.url?.split(".").pop() || "";
const fileName = item.title
? `${item.title}${fileExtension ? "." + fileExtension : ""}`
: `文件-${item.id}${fileExtension ? "." + fileExtension : ""}`;
// 使用fetch下载文件
const response = await fetch(url);
if (!response.ok) {
throw new Error("下载失败");
}
const blob = await response.blob();
// 创建下载链接
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
link.download = fileName; // 设置文件名
document.body.appendChild(link);
link.click();
// 清理下载链接
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
message.success("下载成功");
} catch (error) {
console.error("下载失败:", error);
message.error("下载失败,请重试");
}
};
// 使用useMemo处理资源数据
const { resources, imageResources, fileResources } = useMemo(() => {
if (!data || !Array.isArray(data))
@ -73,7 +120,7 @@ export default function ResourceFileList({ deviceId }: ResourceFileListProps) {
const processedResources = data.map((resource: any) => {
const fileUrl = resource.url || resource.fileId;
if (!fileUrl) return resource;
// 构建文件URL
const originalUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${fileUrl}`;
const isImageFile = isImage(fileUrl);
// console.log("环境变量:", {
@ -89,6 +136,7 @@ export default function ResourceFileList({ deviceId }: ResourceFileListProps) {
});
return {
// 将资源数据分为图片和文件两类,便于后续的展示和处理。
resources: processedResources,
imageResources: processedResources.filter((item) => item.isImage),
fileResources: processedResources.filter((item) => !item.isImage),
@ -103,11 +151,13 @@ export default function ResourceFileList({ deviceId }: ResourceFileListProps) {
return `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${fileId}`;
};
// 获取压缩后的图片URL
const getCompressedUrl = (fileId: string | undefined) => {
if (!fileId) return "";
return getCompressedImageUrl(getFileUrl(fileId));
};
// 加载状态的显示
if (isLoading) {
return (
<div className="text-center py-4 bg-gray-50 rounded-md">
@ -116,6 +166,7 @@ export default function ResourceFileList({ deviceId }: ResourceFileListProps) {
);
}
// 如果没有任何附件,显示空状态
if (resources.length === 0) {
return (
<Empty description="暂无附件" className="bg-gray-50 p-4 rounded-md" />
@ -163,13 +214,7 @@ export default function ResourceFileList({ deviceId }: ResourceFileListProps) {
key="download"
type="link"
icon={<DownloadOutlined />}
onClick={() => {
const url =
"originalUrl" in item
? (item as any).originalUrl
: getFileUrl(item.fileId);
if (url) window.open(url, "_blank");
}}
onClick={() => downloadFile(item)}
>
</Button>,
@ -261,15 +306,13 @@ export default function ResourceFileList({ deviceId }: ResourceFileListProps) {
<div className="rounded-xl p-1 border border-gray-100 bg-white">
<div className="flex flex-wrap gap-2">
{fileResources.map((item) => (
<a
<div
key={item.fileId || item.id}
className="flex-shrink-0 relative active:scale-95 transition-transform select-none"
href={item.originalUrl || getFileUrl(item.fileId)}
target="_blank"
download={true}
className="flex-shrink-0 relative active:scale-95 transition-transform select-none cursor-pointer"
onClick={() => downloadFile(item)}
title={`点击下载"${item.title || "未命名文件"}"`}
>
<div className="w-[120px] h-[90px] p-2 flex flex-col items-center justify-between rounded-xl hover:bg-blue-50 cursor-pointer">
<div className="w-[120px] h-[90px] p-2 flex flex-col items-center justify-between rounded-xl hover:bg-blue-50">
<div className="text-blue-600 text-xl">
{getFileIcon(item.url || "")}
</div>
@ -290,7 +333,7 @@ export default function ResourceFileList({ deviceId }: ResourceFileListProps) {
</div>
</div>
</div>
</a>
</div>
))}
</div>
</div>

View File

@ -50,6 +50,8 @@ const DeviceTable = forwardRef(
const { create } = useDevice();
// 描述信息模态框状态:控制可见性和内容
const [descModalVisible, setDescModalVisible] = useState(false);
const [pageSize, setPageSize] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [currentDesc, setCurrentDesc] = useState<{
id: string;
title: string;
@ -146,6 +148,16 @@ const DeviceTable = forwardRef(
},
});
};
// 在分页配置中添加事件处理
const handlePageChange = (page: number, size: number) => {
setCurrentPage(page);
setPageSize(size);
};
const handlePageSizeChange = (current: number, size: number) => {
setCurrentPage(1); // 重置到第一页
setPageSize(size);
};
const columns: ColumnsType<any> = [
{
@ -413,7 +425,6 @@ const DeviceTable = forwardRef(
);
return matchedDevice?.deptId || null;
};
// 获取状态键
const getStatusKeyByValue = (value: string) => {
const statusMap: Record<string, string> = {
@ -423,7 +434,6 @@ const DeviceTable = forwardRef(
};
return statusMap[value] || "normal";
};
// 改进批量导入记录函数
// 改进批量导入记录函数,使用分批处理
const batchImportRecords = async (records: any[]) => {
@ -447,7 +457,6 @@ const DeviceTable = forwardRef(
// 分批处理数据
for (let i = 0; i < validRecords.length; i += batchSize) {
const batch = validRecords.slice(i, i + batchSize);
// 串行处理每一批数据
for (const record of batch) {
try {
@ -476,7 +485,6 @@ const DeviceTable = forwardRef(
`成功导入 ${successCount}/${validRecords.length} 条数据,部分记录导入失败`
);
}
// 刷新数据
refetch();
} catch (error) {
@ -484,7 +492,6 @@ const DeviceTable = forwardRef(
toast.error("导入过程中发生错误");
}
};
// 添加导出模板功能
const handleExportTemplate = () => {
try {
@ -514,14 +521,12 @@ const DeviceTable = forwardRef(
toast.error("下载模板失败");
}
};
// 触发文件选择
const handleImportClick = () => {
if (uploadRef.current) {
uploadRef.current.click();
}
};
const rowSelection = {
selectedRowKeys,
onChange: onSelectChange,
@ -622,12 +627,15 @@ const DeviceTable = forwardRef(
pagination={{
position: ["bottomCenter"],
className: "flex justify-center mt-4",
pageSize: 10,
pageSize: pageSize,
current: currentPage,
showSizeChanger: true,
pageSizeOptions: ["10", "20", "30"],
pageSizeOptions: ["10", "15", "20"],
responsive: true,
showTotal: (total, range) => `${total} 条数据`,
showQuickJumper: true,
onChange: handlePageChange,
onShowSizeChange: handlePageSizeChange,
}}
components={{
header: {
@ -662,7 +670,6 @@ const DeviceTable = forwardRef(
<div className="bg-gray-50 p-4 rounded-md whitespace-pre-wrap">
{currentDesc.desc}
</div>
{/* 添加附件展示区域 */}
{currentDesc?.id && (
<div className="mt-4">

View File

@ -1,5 +1,16 @@
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { Button, DatePicker, Form, Input, Modal, Select } from "antd";
import {
Button,
DatePicker,
Form,
Input,
Modal,
Select,
AutoComplete,
Tag,
Dropdown,
Space,
} from "antd";
import { useCallback, useEffect, useState, useRef } from "react";
import _ from "lodash";
import { useMainContext } from "../layout/MainProvider";
@ -14,13 +25,18 @@ import {
ExportOutlined,
UpOutlined,
DownOutlined,
HistoryOutlined,
CloseOutlined,
} from "@ant-design/icons";
import DepartmentSelect from "@web/src/components/models/department/department-select";
import SystemTypeSelect from "@web/src/app/main/devicepage/select/System-select";
import DeviceTypeSelect from "@web/src/app/main/devicepage/select/Device-select";
import dayjs from "dayjs";
import FixTypeSelect from "./select/Fix-select";
import { api } from "@nice/client";
const { RangePicker } = DatePicker;
// 添加筛选条件类型
type SearchCondition = {
deletedAt: null;
@ -34,6 +50,15 @@ type SearchCondition = {
};
};
// 搜索历史类型
type SearchHistory = {
id: string;
keyword: string;
conditions: SearchCondition;
timestamp: number;
label: string; // 用于显示的标签
};
export default function DeviceMessage() {
const {
form,
@ -43,15 +68,20 @@ export default function DeviceMessage() {
setSearchValue,
editingRecord,
} = useMainContext();
// 控制展开/收起状态
const [expanded, setExpanded] = useState(false);
// 添加所有筛选条件的状态
const [selectedSystem, setSelectedSystem] = useState<string | null>(null);
const [selectedDeviceType, setSelectedDeviceType] = useState<string | null>(
null
);
const [selectedDept, setSelectedDept] = useState<string | null>(null);
const [time, setTime] = useState<string>("");
// 修改为日期范围
const [dateRange, setDateRange] = useState<
[dayjs.Dayjs | null, dayjs.Dayjs | null] | null
>(null);
const [ipAddress, setIpAddress] = useState<string>("");
const [macAddress, setMacAddress] = useState<string>("");
const [serialNumber, setSerialNumber] = useState<string>("");
@ -63,19 +93,156 @@ export default function DeviceMessage() {
const [selectedSystemTypeId, setSelectedSystemTypeId] = useState<string>("");
const [selectedFixType, setSelectedFixType] = useState<string | null>(null);
// 搜索历史相关状态
const [searchHistory, setSearchHistory] = useState<SearchHistory[]>([]);
const [searchKeyword, setSearchKeyword] = useState<string>("");
const [showHistory, setShowHistory] = useState(false);
// 创建ref以访问DeviceTable内部方法
const tableRef = useRef(null);
// 存储选中行的状态
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
const [selectedData, setSelectedData] = useState<any[]>([]);
// 添加数据查询以获取名称信息
const { data: systemTypeTerms } = api.term.findMany.useQuery({
where: { taxonomy: { slug: "system_type" }, deletedAt: null },
orderBy: { order: "asc" },
});
const { data: deviceTypeTerms } = api.term.findMany.useQuery({
where: { taxonomy: { slug: "device_type" }, deletedAt: null },
orderBy: { order: "asc" },
});
const { data: departments } = api.department.findMany.useQuery({
where: { deletedAt: null },
});
// 根据ID获取名称的辅助函数
const getSystemTypeName = (id: string) => {
const term = systemTypeTerms?.find((t) => t.id === id);
return term?.name || id;
};
const getDeviceTypeName = (id: string) => {
const term = deviceTypeTerms?.find((t) => t.id === id);
return term?.name || id;
};
const getDepartmentName = (id: string) => {
const dept = departments?.find((d) => d.id === id);
return dept?.name || id;
};
// 初始化时加载搜索历史
useEffect(() => {
const savedHistory = localStorage.getItem("device-search-history");
if (savedHistory) {
try {
setSearchHistory(JSON.parse(savedHistory));
} catch (error) {
console.error("加载搜索历史失败:", error);
}
}
}, []);
// 保存搜索历史到localStorage
const saveSearchHistory = (history: SearchHistory[]) => {
localStorage.setItem("device-search-history", JSON.stringify(history));
setSearchHistory(history);
};
// 生成搜索标签 - 修改为显示名称而不是ID并支持日期范围
const generateSearchLabel = (
conditions: SearchCondition,
keyword: string
) => {
const parts = [];
if (keyword) parts.push(`关键词: ${keyword}`);
if (conditions.systemType)
parts.push(`网系: ${getSystemTypeName(conditions.systemType)}`);
if (conditions.deviceType)
parts.push(`故障类型: ${getDeviceTypeName(conditions.deviceType)}`);
if (conditions.deptId)
parts.push(`单位: ${getDepartmentName(conditions.deptId)}`);
if (conditions.createdAt) {
const startDate = dayjs(conditions.createdAt.gte).format("YYYY-MM-DD");
const endDate = dayjs(conditions.createdAt.lte).format("YYYY-MM-DD");
if (startDate === endDate) {
parts.push(`日期: ${startDate}`);
} else {
parts.push(`日期: ${startDate} ~ ${endDate}`);
}
}
return parts.length > 0 ? parts.join(" | ") : "全部故障";
};
// 添加搜索历史
const addSearchHistory = (
conditions: SearchCondition,
keyword: string = ""
) => {
const newHistory: SearchHistory = {
id: Date.now().toString(),
keyword,
conditions,
timestamp: Date.now(),
label: generateSearchLabel(conditions, keyword),
};
const updatedHistory = [
newHistory,
...searchHistory.filter((h) => h.label !== newHistory.label),
].slice(0, 10); // 只保留最近10条
saveSearchHistory(updatedHistory);
};
// 应用历史搜索 - 修改为支持日期范围
const applyHistorySearch = (history: SearchHistory) => {
const { conditions, keyword } = history;
// 恢复搜索条件
setSelectedSystem(conditions.systemType || null);
setSelectedDeviceType(conditions.deviceType || null);
setSelectedDept(conditions.deptId || null);
setSelectedFixType(conditions.responsiblePerson?.contains || null);
setSearchKeyword(keyword);
if (conditions.createdAt) {
const startDate = dayjs(conditions.createdAt.gte);
const endDate = dayjs(conditions.createdAt.lte);
setDateRange([startDate, endDate]);
} else {
setDateRange(null);
}
// 应用搜索条件
setSearchValue(conditions as any);
setShowHistory(false);
};
// 删除搜索历史
const removeSearchHistory = (historyId: string, event: React.MouseEvent) => {
event.stopPropagation();
const updatedHistory = searchHistory.filter((h) => h.id !== historyId);
saveSearchHistory(updatedHistory);
};
// 清空所有搜索历史
const clearAllHistory = () => {
saveSearchHistory([]);
};
const handleNew = () => {
form.setFieldsValue(formValue);
console.log(editingRecord);
setVisible(true);
};
// 查询按钮点击处理
// 查询按钮点击处理 - 修改为支持日期范围
const handleSearch = () => {
// 构建查询条件
const whereCondition: SearchCondition = {
@ -84,29 +251,37 @@ export default function DeviceMessage() {
...(selectedDeviceType && { deviceType: selectedDeviceType }),
...(selectedFixType && { deviceStatus: selectedFixType }),
...(selectedDept && { deptId: selectedDept }),
...(time && {
...(dateRange &&
dateRange[0] &&
dateRange[1] && {
createdAt: {
gte: dayjs(time).startOf("day").toISOString(),
lte: dayjs(time).endOf("day").toISOString(),
gte: dateRange[0].startOf("day").toISOString(),
lte: dateRange[1].endOf("day").toISOString(),
},
}),
// ...(status && { status: { contains: status } }),
...(searchKeyword && {
OR: [
{ description: { contains: searchKeyword } },
{ location: { contains: searchKeyword } },
{ responsiblePerson: { contains: searchKeyword } },
],
}),
};
// 添加到搜索历史
addSearchHistory(whereCondition, searchKeyword);
// 更新查询条件到全局上下文
setSearchValue(whereCondition as any);
// 也可以直接使用refetch方法进行刷新确保查询条件生效
// 如果DeviceTable组件暴露了refetch方法可以通过ref调用
};
// 重置按钮处理
// 重置按钮处理 - 修改为重置日期范围
const handleReset = () => {
setSelectedSystem(null);
setSelectedDeviceType(null);
setSelectedDept(null);
setTime("");
setDateRange(null); // 重置日期范围
setSearchKeyword("");
setIpAddress("");
setMacAddress("");
setSerialNumber("");
@ -114,6 +289,7 @@ export default function DeviceMessage() {
setManufacturer("");
setModel("");
setLocation("");
saveSearchHistory([]);
// 重置为只查询未删除的记录
setSearchValue({ deletedAt: null } as any);
@ -144,14 +320,85 @@ export default function DeviceMessage() {
form.setFieldValue("deviceType", undefined); // 清空已选故障类型
};
// 搜索历史下拉菜单
const historyMenuItems = [
{
key: "header",
label: (
<div className="flex justify-between items-center p-2 border-b">
<span className="font-medium"></span>
{searchHistory.length > 0 && (
<Button
type="link"
size="small"
onClick={clearAllHistory}
className="text-red-500"
>
</Button>
)}
</div>
),
disabled: true,
},
...searchHistory.map((history) => ({
key: history.id,
label: (
<div
className="flex justify-between items-center p-2 hover:bg-gray-50 cursor-pointer"
onClick={() => applyHistorySearch(history)}
>
<div className="flex-1">
<div className="text-sm font-medium truncate">{history.label}</div>
<div className="text-xs text-gray-500">
{dayjs(history.timestamp).format("MM-DD HH:mm")}
</div>
</div>
<Button
type="text"
size="small"
icon={<CloseOutlined />}
onClick={(e) => removeSearchHistory(history.id, e)}
className="ml-2 opacity-60 hover:opacity-100"
/>
</div>
),
})),
...(searchHistory.length === 0
? [
{
key: "empty",
label: (
<div className="text-center text-gray-500 p-4"></div>
),
disabled: true,
},
]
: []),
];
return (
<div className="p-2 min-h-screen bg-white">
<div className="flex justify-between items-center mb-4">
<h1 className="text-xl font-normal"></h1>
<div className="flex items-center gap-2 justify-end">
<Dropdown
menu={{ items: historyMenuItems }}
trigger={["click"]}
open={showHistory}
onOpenChange={setShowHistory}
placement="bottomRight"
>
<Button icon={<HistoryOutlined />} title="搜索历史">
</Button>
</Dropdown>
<Button type="primary" icon={<PlusOutlined />} onClick={handleNew}>
</Button>
</div>
</div>
<div className="flex flex-wrap items-center gap-2 mb-4">
<div className="flex-1 min-w-[200px]">
<SystemTypeSelect
@ -186,12 +433,12 @@ export default function DeviceMessage() {
onChange={handleDeptChange}
/>
</div>
<div className="flex-1 min-w-[200px]">
<DatePicker
placeholder="选择日期"
<div className="flex-1 min-w-[250px]">
<RangePicker
placeholder={["开始日期", "结束日期"]}
className="w-full"
value={time ? dayjs(time) : null}
onChange={(date, dateString) => setTime(dateString as string)}
value={dateRange}
onChange={(dates) => setDateRange(dates)}
format="YYYY-MM-DD"
allowClear
/>
@ -203,6 +450,36 @@ export default function DeviceMessage() {
</Button>
</div>
{/* 显示当前搜索历史标签 */}
{searchHistory.length > 0 && (
<div className="mb-4 flex items-center gap-3 p-3 bg-gray-50 rounded-lg border border-gray-200">
<div className="text-sm font-medium text-gray-700 whitespace-nowrap">
:
</div>
<div className="flex flex-wrap gap-2 flex-1 min-w-0">
{searchHistory.slice(0, 5).map((history) => (
<Tag
key={history.id}
className="cursor-pointer hover:bg-blue-50 transition-colors duration-200 border-blue-200 text-blue-700"
onClick={() => applyHistorySearch(history)}
closable
onClose={(e) => {
e.preventDefault();
removeSearchHistory(history.id, e as any);
}}
>
<span className="text-xs">
{history.label.length > 25
? `${history.label.substring(0, 25)}...`
: history.label}
</span>
</Tag>
))}
</div>
</div>
)}
<div>
<DeviceTable ref={tableRef} onSelectedChange={handleSelectedChange} />
<DeviceModal />

View File

@ -1,10 +1,31 @@
// apps/web/src/components/models/term/term-manager.tsx
import { Button, Input, Modal, Space, Table, TreeSelect, Select } from "antd";
import {
Button,
Input,
Modal,
Space,
Table,
TreeSelect,
Select,
Card,
Row,
Col,
Typography,
Divider,
} from "antd";
import { api } from "@nice/client";
import { useState, useEffect } from "react";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
SearchOutlined,
FileTextOutlined,
} from "@ant-design/icons";
import { ObjectType } from "@nice/common";
const { Title } = Typography;
interface TermManagerProps {
title: string;
}
@ -18,6 +39,10 @@ export default function DeviceManager({ title }: TermManagerProps) {
const [searchValue, setSearchValue] = useState("");
const [treeData, setTreeData] = useState<any[]>([]);
const [taxonomySelectDisabled, setTaxonomySelectDisabled] = useState(false);
const [pageSize, setPageSize] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
// ... 保持原有的 API 调用和逻辑 ...
// 获取所有taxonomy
const { data: taxonomies } = api.taxonomy.getAll.useQuery({
@ -61,6 +86,15 @@ export default function DeviceManager({ title }: TermManagerProps) {
orderBy: { order: "asc" },
});
const handlePageChange = (page: number, size: number) => {
setCurrentPage(page);
setPageSize(size);
};
const handlePageSizeChange = (current: number, size: number) => {
setCurrentPage(1); // 重置到第一页
setPageSize(size);
};
// 构建包含两种分类的树形数据
useEffect(() => {
if (systemTypeTerms && deviceTypeTerms) {
@ -186,7 +220,6 @@ export default function DeviceManager({ title }: TermManagerProps) {
});
// 操作处理函数
// 修改handleAdd函数
const handleAdd = (parentRecord?: any) => {
setEditingTerm(null);
setTermName("");
@ -213,10 +246,6 @@ export default function DeviceManager({ title }: TermManagerProps) {
setIsModalVisible(true);
};
const handleEdit = (term: any) => {
setEditingTerm(term);
setTermName(term.name);
@ -329,123 +358,240 @@ export default function DeviceManager({ title }: TermManagerProps) {
};
return (
<div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginBottom: 16,
alignItems: "center",
gap: "10px",
}}
>
<div className="p-6 min-h-screen bg-gray-50">
{/* 主要内容卡片 */}
<Card className="shadow-sm" bodyStyle={{ padding: 0 }}>
{/* 统计信息区域 */}
<div className="p-4 bg-gray-50 border-b border-gray-100">
<Row gutter={16} justify="center">
<Col span={8}>
<div className="text-center">
<div className="text-2xl font-bold text-blue-600">
{systemTypeTerms?.length || 0}
</div>
<div className="text-sm text-gray-600"></div>
</div>
</Col>
<Col span={8}>
<div className="text-center">
<div className="text-2xl font-bold text-green-600">
{deviceTypeTerms?.filter((term) =>
systemTypeTerms?.some(
(sysType) => sysType.id === term.parentId
)
).length || 0}
</div>
<div className="text-sm text-gray-600"></div>
</div>
</Col>
{/* <Col span={8}> */}
{/* <div className="text-center"> */}
{/* <div className="text-2xl font-bold text-orange-600">
{deviceTypeTerms?.filter(term =>
!systemTypeTerms?.some(sysType => sysType.id === term.parentId) &&
deviceTypeTerms?.some(devType => devType.id === term.parentId)
).length || 0}
</div>
<div className="text-sm text-gray-600"></div> */}
{/* </div>
</Col> */}
</Row>
</div>
{/* 工具栏区域 */}
<div className="p-4 border-b border-gray-100 bg-white">
<Row gutter={[16, 16]} align="middle" justify="space-between">
<Col xs={24} sm={16} md={12} lg={10}>
<Input.Search
placeholder={`根据${title}搜索`}
placeholder={`搜索${title}名称...`}
onSearch={handleSearch}
onChange={(e) => handleSearch(e.target.value)}
value={searchValue}
style={{ width: 400 }}
allowClear
prefix={<SearchOutlined className="text-gray-400" />}
className="w-full"
size="middle"
/>
</Col>
<Col xs={24} sm={8} md={12} lg={14} className="flex justify-end">
<Space>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => handleAdd()}
size="middle"
className="shadow-sm"
>
{title}
{title}
</Button>
</Space>
</Col>
</Row>
</div>
{/* 表格区域 */}
<div className="p-4">
<Table
dataSource={treeData}
expandable={{
defaultExpandAllRows: true,
defaultExpandAllRows: false, // 改为不默认展开所有行
expandRowByClick: true,
indentSize: 20, // 设置缩进大小
}}
columns={[
{
title: "名称",
dataIndex: "name",
key: "name",
width: "50%", // 增加名称列的宽度
render: (text, record, index, ) => (
<div className="flex items-center gap-2">
{/* 根据层级添加不同的图标 */}
{record.taxonomyId === systemTypeTaxonomy?.id ? (
<span className="text-blue-500">📁</span>
) : record.taxonomyId === deviceTypeTaxonomy?.id ? (
<span className="text-green-500">📂</span>
) : (
<span className="text-orange-500">📄</span>
)}
<span className="font-medium">{text}</span>
</div>
),
},
{
title: "分类类型",
key: "taxonomyType",
width: "25%",
render: (_, record) => {
if (record.taxonomyId === systemTypeTaxonomy?.id) {
return "网系类别";
return (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
</span>
);
} else if (record.taxonomyId === deviceTypeTaxonomy?.id) {
return "故障类型";
return (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
</span>
);
} else {
return "具体故障";
return (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800">
</span>
);
}
},
},
{
title: "操作",
key: "action",
width: 200,
width: "25%",
render: (_, record: any) => (
<Space>
<div className="flex items-center gap-1">
<Button
type="text"
icon={<PlusOutlined />}
onClick={() => handleAdd(record)}
style={{ color: "green" }}
>
</Button>
className="text-green-600 hover:text-green-700 hover:bg-green-50"
size="small"
title="添加子项"
/>
<Button
type="text"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
style={{ color: "#1890ff" }}
className="text-blue-600 hover:text-blue-700 hover:bg-blue-50"
size="small"
title="编辑"
/>
<Button
type="text"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(record)}
size="small"
title="删除"
className="hover:bg-red-50"
/>
</Space>
</div>
),
},
]}
rowKey="id"
pagination={false}
rowClassName={(record, index) =>
index % 2 === 0 ? "bg-white" : "bg-gray-100"
}
onHeaderRow={() => {
return {
style: {
backgroundColor: "#d6e4ff",
},
};
pagination={{
pageSize: pageSize, // 每页显示数量
current: currentPage,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
pageSizeOptions: [ "10", "15", "20"],
onChange: handlePageChange,
onShowSizeChange: handlePageSizeChange,
}}
bordered
rowClassName={(record, index) =>
`hover:bg-blue-50 transition-colors duration-200 ${
record.taxonomyId === systemTypeTaxonomy?.id
? "bg-blue-25"
: record.taxonomyId === deviceTypeTaxonomy?.id
? "bg-green-25"
: "bg-orange-25"
}`
}
onHeaderRow={() => ({
style: {
backgroundColor: "#f8fafc",
fontWeight: "600",
borderBottom: "2px solid #e2e8f0",
},
})}
bordered={true} // 改为有边框
size="middle"
locale={{ emptyText: "暂无数据" }}
className="rounded-lg overflow-hidden shadow-sm"
showHeader={true}
scroll={{ x: 800 }} // 添加横向滚动
/>
</div>
</Card>
{/* 编辑/添加模态框 */}
<Modal
title={editingTerm ? `编辑${title}` : `添加${title}`}
title={
<div className="flex items-center gap-2">
{editingTerm ? <EditOutlined /> : <PlusOutlined />}
{editingTerm ? `编辑${title}` : `添加${title}`}
</div>
}
open={isModalVisible}
onOk={handleSave}
onCancel={() => setIsModalVisible(false)}
okText="保存"
cancelText="取消"
width={600}
destroyOnClose
>
<div style={{ marginBottom: 16 }}>
<label style={{ display: "block", marginBottom: 8 }}></label>
<Divider className="mt-4 mb-6" />
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
<span className="text-red-500">*</span>
</label>
<Input
placeholder={`请输入${title}名称`}
value={termName}
onChange={(e) => setTermName(e.target.value)}
size="large"
/>
</div>
{!editingTerm && (
<div style={{ marginBottom: 16 }}>
<label style={{ display: "block", marginBottom: 8 }}>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
<span className="text-red-500">*</span>
</label>
<Select
style={{ width: "100%" }}
@ -453,6 +599,7 @@ export default function DeviceManager({ title }: TermManagerProps) {
value={taxonomyId}
onChange={setTaxonomyId}
disabled={taxonomySelectDisabled}
size="large"
options={taxonomies?.map((tax) => ({
label: tax.name,
value: tax.id,
@ -462,20 +609,22 @@ export default function DeviceManager({ title }: TermManagerProps) {
)}
<div>
<label style={{ display: "block", marginBottom: 8 }}>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<TreeSelect
style={{ width: "100%" }}
dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
placeholder="请选择上级分类"
placeholder="请选择上级分类(可选)"
allowClear
treeDefaultExpandAll
value={parentId}
onChange={setParentId}
treeData={getParentOptions()}
size="large"
/>
</div>
</div>
</Modal>
</div>
);