2025-05-20 10:29:13 +08:00
|
|
|
|
// apps/web/src/components/models/term/term-manager.tsx
|
2025-05-29 11:14:03 +08:00
|
|
|
|
import {
|
|
|
|
|
Button,
|
|
|
|
|
Input,
|
|
|
|
|
Modal,
|
|
|
|
|
Space,
|
|
|
|
|
Table,
|
|
|
|
|
TreeSelect,
|
|
|
|
|
Select,
|
|
|
|
|
Card,
|
|
|
|
|
Row,
|
|
|
|
|
Col,
|
|
|
|
|
Typography,
|
|
|
|
|
Divider,
|
|
|
|
|
} from "antd";
|
2025-05-20 10:29:13 +08:00
|
|
|
|
import { api } from "@nice/client";
|
|
|
|
|
import { useState, useEffect } from "react";
|
2025-05-29 11:14:03 +08:00
|
|
|
|
import {
|
|
|
|
|
PlusOutlined,
|
|
|
|
|
EditOutlined,
|
|
|
|
|
DeleteOutlined,
|
|
|
|
|
SearchOutlined,
|
|
|
|
|
FileTextOutlined,
|
|
|
|
|
} from "@ant-design/icons";
|
2025-05-20 10:29:13 +08:00
|
|
|
|
import { ObjectType } from "@nice/common";
|
|
|
|
|
|
2025-05-29 11:14:03 +08:00
|
|
|
|
const { Title } = Typography;
|
|
|
|
|
|
2025-05-20 10:29:13 +08:00
|
|
|
|
interface TermManagerProps {
|
|
|
|
|
title: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function DeviceManager({ title }: TermManagerProps) {
|
|
|
|
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
|
|
|
|
const [editingTerm, setEditingTerm] = useState<any>(null);
|
|
|
|
|
const [termName, setTermName] = useState("");
|
|
|
|
|
const [parentId, setParentId] = useState<string | null>(null);
|
|
|
|
|
const [taxonomyId, setTaxonomyId] = useState<string | null>(null);
|
|
|
|
|
const [searchValue, setSearchValue] = useState("");
|
|
|
|
|
const [treeData, setTreeData] = useState<any[]>([]);
|
|
|
|
|
const [taxonomySelectDisabled, setTaxonomySelectDisabled] = useState(false);
|
2025-05-29 11:14:03 +08:00
|
|
|
|
const [pageSize, setPageSize] = useState(10);
|
|
|
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
|
|
|
|
|
|
|
|
// ... 保持原有的 API 调用和逻辑 ...
|
2025-05-20 10:29:13 +08:00
|
|
|
|
|
|
|
|
|
// 获取所有taxonomy
|
|
|
|
|
const { data: taxonomies } = api.taxonomy.getAll.useQuery({
|
|
|
|
|
type: ObjectType.DEVICE,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 获取所有故障类型taxonomy
|
2025-05-29 11:14:03 +08:00
|
|
|
|
// 故障网系类别taxonomy
|
2025-05-20 10:29:13 +08:00
|
|
|
|
const { data: systemTypeTaxonomy } = api.taxonomy.findBySlug.useQuery({
|
|
|
|
|
slug: "system_type",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 故障类型taxonomy
|
|
|
|
|
const { data: deviceTypeTaxonomy } = api.taxonomy.findBySlug.useQuery({
|
|
|
|
|
slug: "device_type",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 获取所有网系类别条目
|
|
|
|
|
const { data: systemTypeTerms, refetch: refetchSystemType } =
|
|
|
|
|
api.term.findMany.useQuery({
|
|
|
|
|
where: {
|
|
|
|
|
taxonomy: { slug: "system_type" },
|
|
|
|
|
deletedAt: null,
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
children: true,
|
|
|
|
|
},
|
|
|
|
|
orderBy: { order: "asc" },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 获取所有故障类型条目
|
|
|
|
|
const { data: deviceTypeTerms, refetch: refetchDeviceType } =
|
|
|
|
|
api.term.findMany.useQuery({
|
|
|
|
|
where: {
|
|
|
|
|
taxonomy: { slug: "device_type" },
|
|
|
|
|
deletedAt: null,
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
children: true,
|
|
|
|
|
},
|
|
|
|
|
orderBy: { order: "asc" },
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-29 11:14:03 +08:00
|
|
|
|
const handlePageChange = (page: number, size: number) => {
|
|
|
|
|
setCurrentPage(page);
|
|
|
|
|
setPageSize(size);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handlePageSizeChange = (current: number, size: number) => {
|
|
|
|
|
setCurrentPage(1); // 重置到第一页
|
|
|
|
|
setPageSize(size);
|
|
|
|
|
};
|
2025-05-20 10:29:13 +08:00
|
|
|
|
// 构建包含两种分类的树形数据
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (systemTypeTerms && deviceTypeTerms) {
|
|
|
|
|
// 先获取顶级网系类别
|
|
|
|
|
const rootTerms = systemTypeTerms.filter((term) => !term.parentId);
|
|
|
|
|
|
|
|
|
|
// 构建树形数据
|
|
|
|
|
const buildTreeData = (items: any[]): any[] => {
|
|
|
|
|
return items.map((item) => {
|
|
|
|
|
// 找到与此网系类别关联的故障类型
|
|
|
|
|
const deviceChildren = deviceTypeTerms.filter(
|
|
|
|
|
(t) => t.parentId === item.id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 为每个故障类型找到其子故障
|
|
|
|
|
const processedDeviceChildren = deviceChildren.map((deviceType) => {
|
|
|
|
|
const deviceItems = deviceTypeTerms.filter(
|
|
|
|
|
(t) => t.parentId === deviceType.id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...deviceType,
|
|
|
|
|
key: deviceType.id,
|
|
|
|
|
children: deviceItems.map((device) => ({
|
|
|
|
|
...device,
|
|
|
|
|
key: device.id,
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...item,
|
|
|
|
|
key: item.id,
|
|
|
|
|
children: processedDeviceChildren,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setTreeData(buildTreeData(rootTerms));
|
|
|
|
|
}
|
|
|
|
|
}, [systemTypeTerms, deviceTypeTerms]);
|
|
|
|
|
|
|
|
|
|
// 搜索过滤逻辑
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (systemTypeTerms && deviceTypeTerms) {
|
|
|
|
|
if (!searchValue) {
|
|
|
|
|
// 重新构建完整的树形结构
|
|
|
|
|
const rootTerms = systemTypeTerms.filter((term) => !term.parentId);
|
|
|
|
|
const buildTreeData = (items: any[]): any[] => {
|
|
|
|
|
return items.map((item) => {
|
|
|
|
|
const deviceChildren = deviceTypeTerms.filter(
|
|
|
|
|
(t) => t.parentId === item.id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const processedDeviceChildren = deviceChildren.map((deviceType) => {
|
|
|
|
|
const deviceItems = deviceTypeTerms.filter(
|
|
|
|
|
(t) => t.parentId === deviceType.id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...deviceType,
|
|
|
|
|
key: deviceType.id,
|
|
|
|
|
children: deviceItems.map((device) => ({
|
|
|
|
|
...device,
|
|
|
|
|
key: device.id,
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...item,
|
|
|
|
|
key: item.id,
|
|
|
|
|
children: processedDeviceChildren,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setTreeData(buildTreeData(rootTerms));
|
|
|
|
|
} else {
|
|
|
|
|
// 搜索匹配所有项
|
|
|
|
|
const allTerms = [...systemTypeTerms, ...deviceTypeTerms];
|
|
|
|
|
const filtered = allTerms.filter((term) =>
|
|
|
|
|
term.name.toLowerCase().includes(searchValue.toLowerCase())
|
|
|
|
|
);
|
|
|
|
|
setTreeData(filtered.map((item) => ({ ...item, key: item.id })));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [systemTypeTerms, deviceTypeTerms, searchValue]);
|
|
|
|
|
|
|
|
|
|
const handleSearch = (value: string) => {
|
|
|
|
|
setSearchValue(value);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// API调用
|
|
|
|
|
const { mutate: createTerm } = api.term.create.useMutation({
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
refetchSystemType();
|
|
|
|
|
refetchDeviceType();
|
|
|
|
|
setIsModalVisible(false);
|
|
|
|
|
setTermName("");
|
|
|
|
|
setParentId(null);
|
|
|
|
|
setTaxonomyId(null);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { mutate: updateTerm } = api.term.update.useMutation({
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
refetchSystemType();
|
|
|
|
|
refetchDeviceType();
|
|
|
|
|
setIsModalVisible(false);
|
|
|
|
|
setEditingTerm(null);
|
|
|
|
|
setTermName("");
|
|
|
|
|
setParentId(null);
|
|
|
|
|
setTaxonomyId(null);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { mutate: softDeleteByIds } = api.term.softDeleteByIds.useMutation({
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
refetchSystemType();
|
|
|
|
|
refetchDeviceType();
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 操作处理函数
|
|
|
|
|
const handleAdd = (parentRecord?: any) => {
|
|
|
|
|
setEditingTerm(null);
|
|
|
|
|
setTermName("");
|
|
|
|
|
setParentId(parentRecord?.id || null);
|
|
|
|
|
refetchSystemType();
|
|
|
|
|
refetchDeviceType();
|
|
|
|
|
// 根据父记录类型自动选择taxonomy
|
|
|
|
|
if (parentRecord) {
|
|
|
|
|
// 如果父类是网系类别,则自动设置为故障类型
|
|
|
|
|
if (parentRecord.taxonomyId === systemTypeTaxonomy?.id) {
|
|
|
|
|
setTaxonomyId(deviceTypeTaxonomy?.id);
|
|
|
|
|
// 可以设置状态来禁用Select组件
|
|
|
|
|
setTaxonomySelectDisabled(true);
|
|
|
|
|
} else {
|
|
|
|
|
setTaxonomyId(parentRecord.taxonomyId);
|
|
|
|
|
setTaxonomySelectDisabled(false);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果是顶级项,默认设为网系类别
|
|
|
|
|
setTaxonomyId(systemTypeTaxonomy?.id || null);
|
|
|
|
|
setTaxonomySelectDisabled(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setIsModalVisible(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleEdit = (term: any) => {
|
|
|
|
|
setEditingTerm(term);
|
|
|
|
|
setTermName(term.name);
|
|
|
|
|
setParentId(term.parentId);
|
|
|
|
|
setTaxonomyId(term.taxonomyId);
|
|
|
|
|
setIsModalVisible(true);
|
|
|
|
|
refetchSystemType();
|
|
|
|
|
refetchDeviceType();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDelete = (term: any) => {
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
title: "确认删除",
|
|
|
|
|
content: `确定要删除"${term.name}"吗?这将同时删除其下所有子项!`,
|
|
|
|
|
onOk: () => softDeleteByIds({ ids: [term.id] }),
|
|
|
|
|
});
|
|
|
|
|
refetchSystemType();
|
|
|
|
|
refetchDeviceType();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSave = () => {
|
|
|
|
|
if (!termName.trim() || !taxonomyId) return;
|
|
|
|
|
|
|
|
|
|
if (editingTerm) {
|
|
|
|
|
updateTerm({
|
|
|
|
|
where: { id: editingTerm.id },
|
|
|
|
|
data: {
|
|
|
|
|
name: termName,
|
|
|
|
|
parentId: parentId,
|
|
|
|
|
hasChildren: editingTerm.hasChildren,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
createTerm({
|
|
|
|
|
data: {
|
|
|
|
|
name: termName,
|
|
|
|
|
taxonomyId: taxonomyId,
|
|
|
|
|
parentId: parentId,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
refetchSystemType();
|
|
|
|
|
refetchDeviceType();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 构建父级选择器的选项
|
|
|
|
|
const getParentOptions = () => {
|
|
|
|
|
if (!systemTypeTerms || !deviceTypeTerms) return [];
|
|
|
|
|
|
|
|
|
|
const allTerms = [...systemTypeTerms, ...deviceTypeTerms];
|
|
|
|
|
|
|
|
|
|
// 根据编辑对象和当前选择的taxonomy过滤有效的父级选项
|
|
|
|
|
let validParents = allTerms;
|
|
|
|
|
|
|
|
|
|
// 如果是编辑现有项
|
|
|
|
|
if (editingTerm) {
|
|
|
|
|
// 递归查找所有子孙节点ID,避免循环引用
|
|
|
|
|
const findAllDescendantIds = (itemId: string): string[] => {
|
|
|
|
|
const directChildren = allTerms.filter((t) => t.parentId === itemId);
|
|
|
|
|
const descendantIds = directChildren.map((c) => c.id);
|
|
|
|
|
|
|
|
|
|
directChildren.forEach((child) => {
|
|
|
|
|
const childDescendants = findAllDescendantIds(child.id);
|
|
|
|
|
descendantIds.push(...childDescendants);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return descendantIds;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const invalidIds = [
|
|
|
|
|
editingTerm.id,
|
|
|
|
|
...findAllDescendantIds(editingTerm.id),
|
|
|
|
|
];
|
|
|
|
|
validParents = allTerms.filter((t) => !invalidIds.includes(t.id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果是添加故障类型,只能选择网系类别作为父级
|
|
|
|
|
if (!editingTerm && taxonomyId === deviceTypeTaxonomy?.id) {
|
|
|
|
|
validParents = systemTypeTerms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果是添加具体故障,只能选择故障类型作为父级
|
|
|
|
|
if (
|
|
|
|
|
!editingTerm &&
|
|
|
|
|
taxonomyId &&
|
|
|
|
|
taxonomyId !== systemTypeTaxonomy?.id &&
|
|
|
|
|
taxonomyId !== deviceTypeTaxonomy?.id
|
|
|
|
|
) {
|
|
|
|
|
validParents = deviceTypeTerms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 转换为TreeSelect需要的格式
|
|
|
|
|
const buildTreeOptions = (items: any[], depth = 0): any[] => {
|
|
|
|
|
return items
|
|
|
|
|
.filter((item) => (depth === 0 ? !item.parentId : true))
|
|
|
|
|
.map((item) => {
|
|
|
|
|
const children = allTerms.filter((t) => t.parentId === item.id);
|
|
|
|
|
return {
|
|
|
|
|
title: "—".repeat(depth) + (depth > 0 ? " " : "") + item.name,
|
|
|
|
|
value: item.id,
|
|
|
|
|
children:
|
|
|
|
|
children.length > 0
|
|
|
|
|
? buildTreeOptions(children, depth + 1)
|
|
|
|
|
: undefined,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return buildTreeOptions(validParents);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
2025-05-29 11:14:03 +08:00
|
|
|
|
<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}名称...`}
|
|
|
|
|
onSearch={handleSearch}
|
|
|
|
|
onChange={(e) => handleSearch(e.target.value)}
|
|
|
|
|
value={searchValue}
|
|
|
|
|
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">
|
2025-05-20 10:29:13 +08:00
|
|
|
|
<Space>
|
|
|
|
|
<Button
|
2025-05-29 11:14:03 +08:00
|
|
|
|
type="primary"
|
2025-05-20 10:29:13 +08:00
|
|
|
|
icon={<PlusOutlined />}
|
2025-05-29 11:14:03 +08:00
|
|
|
|
onClick={() => handleAdd()}
|
|
|
|
|
size="middle"
|
|
|
|
|
className="shadow-sm"
|
2025-05-20 10:29:13 +08:00
|
|
|
|
>
|
2025-05-29 11:14:03 +08:00
|
|
|
|
添加{title}
|
2025-05-20 10:29:13 +08:00
|
|
|
|
</Button>
|
|
|
|
|
</Space>
|
2025-05-29 11:14:03 +08:00
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
</div>
|
2025-05-20 10:29:13 +08:00
|
|
|
|
|
2025-05-29 11:14:03 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 表格区域 */}
|
|
|
|
|
<div className="p-4">
|
|
|
|
|
<Table
|
|
|
|
|
dataSource={treeData}
|
|
|
|
|
expandable={{
|
|
|
|
|
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 (
|
|
|
|
|
<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 (
|
|
|
|
|
<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 (
|
|
|
|
|
<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: "25%",
|
|
|
|
|
render: (_, record: any) => (
|
|
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
<Button
|
|
|
|
|
type="text"
|
|
|
|
|
icon={<PlusOutlined />}
|
|
|
|
|
onClick={() => handleAdd(record)}
|
|
|
|
|
className="text-green-600 hover:text-green-700 hover:bg-green-50"
|
|
|
|
|
size="small"
|
|
|
|
|
title="添加子项"
|
|
|
|
|
/>
|
|
|
|
|
<Button
|
|
|
|
|
type="text"
|
|
|
|
|
icon={<EditOutlined />}
|
|
|
|
|
onClick={() => handleEdit(record)}
|
|
|
|
|
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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
]}
|
|
|
|
|
rowKey="id"
|
|
|
|
|
pagination={{
|
|
|
|
|
pageSize: pageSize, // 每页显示数量
|
|
|
|
|
current: currentPage,
|
|
|
|
|
showSizeChanger: true,
|
|
|
|
|
showQuickJumper: true,
|
|
|
|
|
showTotal: (total) => `共 ${total} 条记录`,
|
|
|
|
|
pageSizeOptions: [ "10", "15", "20"],
|
|
|
|
|
onChange: handlePageChange,
|
|
|
|
|
onShowSizeChange: handlePageSizeChange,
|
|
|
|
|
}}
|
|
|
|
|
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>
|
|
|
|
|
|
|
|
|
|
{/* 编辑/添加模态框 */}
|
2025-05-20 10:29:13 +08:00
|
|
|
|
<Modal
|
2025-05-29 11:14:03 +08:00
|
|
|
|
title={
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
{editingTerm ? <EditOutlined /> : <PlusOutlined />}
|
|
|
|
|
{editingTerm ? `编辑${title}` : `添加${title}`}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2025-05-20 10:29:13 +08:00
|
|
|
|
open={isModalVisible}
|
|
|
|
|
onOk={handleSave}
|
|
|
|
|
onCancel={() => setIsModalVisible(false)}
|
2025-05-29 11:14:03 +08:00
|
|
|
|
okText="保存"
|
|
|
|
|
cancelText="取消"
|
|
|
|
|
width={600}
|
|
|
|
|
destroyOnClose
|
2025-05-20 10:29:13 +08:00
|
|
|
|
>
|
2025-05-29 11:14:03 +08:00
|
|
|
|
<Divider className="mt-4 mb-6" />
|
2025-05-20 10:29:13 +08:00
|
|
|
|
|
2025-05-29 11:14:03 +08:00
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
名称 <span className="text-red-500">*</span>
|
2025-05-20 10:29:13 +08:00
|
|
|
|
</label>
|
2025-05-29 11:14:03 +08:00
|
|
|
|
<Input
|
|
|
|
|
placeholder={`请输入${title}名称`}
|
|
|
|
|
value={termName}
|
|
|
|
|
onChange={(e) => setTermName(e.target.value)}
|
|
|
|
|
size="large"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{!editingTerm && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
分类类型 <span className="text-red-500">*</span>
|
|
|
|
|
</label>
|
|
|
|
|
<Select
|
|
|
|
|
style={{ width: "100%" }}
|
|
|
|
|
placeholder="请选择分类类型"
|
|
|
|
|
value={taxonomyId}
|
|
|
|
|
onChange={setTaxonomyId}
|
|
|
|
|
disabled={taxonomySelectDisabled}
|
|
|
|
|
size="large"
|
|
|
|
|
options={taxonomies?.map((tax) => ({
|
|
|
|
|
label: tax.name,
|
|
|
|
|
value: tax.id,
|
|
|
|
|
}))}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
上级分类
|
|
|
|
|
</label>
|
|
|
|
|
<TreeSelect
|
2025-05-20 10:29:13 +08:00
|
|
|
|
style={{ width: "100%" }}
|
2025-05-29 11:14:03 +08:00
|
|
|
|
dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
|
|
|
|
|
placeholder="请选择上级分类(可选)"
|
|
|
|
|
allowClear
|
|
|
|
|
treeDefaultExpandAll
|
|
|
|
|
value={parentId}
|
|
|
|
|
onChange={setParentId}
|
|
|
|
|
treeData={getParentOptions()}
|
|
|
|
|
size="large"
|
2025-05-20 10:29:13 +08:00
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Modal>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|