book_manage/apps/web/src/app/main/devicepage/select/Device-manager.tsx

632 lines
20 KiB
TypeScript
Raw Normal View History

2025-05-20 10:29:13 +08:00
// apps/web/src/components/models/term/term-manager.tsx
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";
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
SearchOutlined,
FileTextOutlined,
} from "@ant-design/icons";
2025-05-20 10:29:13 +08:00
import { ObjectType } from "@nice/common";
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);
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
// 故障网系类别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" },
});
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 (
<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
type="primary"
2025-05-20 10:29:13 +08:00
icon={<PlusOutlined />}
onClick={() => handleAdd()}
size="middle"
className="shadow-sm"
2025-05-20 10:29:13 +08:00
>
{title}
2025-05-20 10:29:13 +08:00
</Button>
</Space>
</Col>
</Row>
</div>
2025-05-20 10:29:13 +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
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)}
okText="保存"
cancelText="取消"
width={600}
destroyOnClose
2025-05-20 10:29:13 +08:00
>
<Divider className="mt-4 mb-6" />
2025-05-20 10:29:13 +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>
<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%" }}
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>
);
}