collect-system/apps/web/src/app/main/devicepage/page.tsx

583 lines
18 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import {
Button,
DatePicker,
Form,
Input,
Modal,
Select,
AutoComplete,
Tag,
Dropdown,
Space,
Card,
Row,
Col,
Typography,
} from "antd";
import { useCallback, useEffect, useState, useRef } from "react";
import _ from "lodash";
import { useMainContext } from "../layout/MainProvider";
import DeviceTable from "./devicetable/page";
import DeviceModal from "./devicemodal/page";
import React from "react";
import {
SearchOutlined,
ReloadOutlined,
PlusOutlined,
ImportOutlined,
ExportOutlined,
UpOutlined,
DownOutlined,
HistoryOutlined,
CloseOutlined,
FilterOutlined,
DatabaseOutlined,
} 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;
const { Title } = Typography;
// 添加筛选条件类型
type SearchCondition = {
deletedAt: null;
systemType?: string;
deviceType?: string;
deptId?: string;
responsiblePerson?: { contains: string };
createdAt?: {
gte: string;
lte: string;
};
};
// 搜索历史类型
type SearchHistory = {
id: string;
keyword: string;
conditions: SearchCondition;
timestamp: number;
label: string; // 用于显示的标签
};
export default function DeviceMessage() {
const {
form,
formValue,
setFormValue,
setVisible,
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 [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>("");
const [assetNumber, setAssetNumber] = useState<string>("");
const [manufacturer, setManufacturer] = useState<string>("");
const [model, setModel] = useState<string>("");
const [location, setLocation] = useState<string>("");
const [status, setStatus] = useState<string | null>(null);
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 = {
deletedAt: null,
...(selectedSystem && { systemType: selectedSystem }),
...(selectedDeviceType && { deviceType: selectedDeviceType }),
...(selectedFixType && { deviceStatus: selectedFixType }),
...(selectedDept && { deptId: selectedDept }),
...(dateRange &&
dateRange[0] &&
dateRange[1] && {
createdAt: {
gte: dateRange[0].startOf("day").toISOString(),
lte: dateRange[1].endOf("day").toISOString(),
},
}),
...(searchKeyword && {
OR: [
{ description: { contains: searchKeyword } },
{ location: { contains: searchKeyword } },
{ responsiblePerson: { contains: searchKeyword } },
],
}),
};
// 添加到搜索历史
addSearchHistory(whereCondition, searchKeyword);
// 更新查询条件到全局上下文
setSearchValue(whereCondition as any);
};
// 重置按钮处理 - 修改为重置日期范围
const handleReset = () => {
setSelectedSystem(null);
setSelectedDeviceType(null);
setSelectedDept(null);
setDateRange(null); // 重置日期范围
setSearchKeyword("");
setIpAddress("");
setMacAddress("");
setSerialNumber("");
setAssetNumber("");
setManufacturer("");
setModel("");
setLocation("");
saveSearchHistory([]);
// 重置为只查询未删除的记录
setSearchValue({ deletedAt: null } as any);
};
// 修复DepartmentSelect的onChange类型
const handleDeptChange = (value: string | string[]) => {
setSelectedDept(value as string);
};
// 处理选择变更的回调
const handleSelectedChange = (keys: React.Key[], data: any[]) => {
console.log("选中状态变化:", keys.length);
setSelectedKeys(keys);
setSelectedData(data);
};
// 处理导出按钮点击
const handleExport = () => {
if (tableRef.current) {
tableRef.current.handleExportSelected();
}
};
// 系统类别变化处理
const handleSystemTypeChange = (value: string) => {
setSelectedSystemTypeId(value);
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="min-h-screen bg-gray-50 p-4">
{/* 页面标题区域 */}
<div className="mb-6">
<Card className="shadow-sm border border-gray-200">
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center">
<DatabaseOutlined className="text-lg text-white" />
</div>
<div>
<Title level={3} className="!mb-0 text-gray-800">
</Title>
<p className="text-gray-500 text-sm mt-1">
</p>
</div>
</div>
<div className="flex items-center gap-3">
<Dropdown
menu={{ items: historyMenuItems }}
trigger={["click"]}
open={showHistory}
onOpenChange={setShowHistory}
placement="bottomRight"
>
<Button
icon={<HistoryOutlined />}
className="border-gray-300 hover:border-blue-500 hover:text-blue-600"
>
</Button>
</Dropdown>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleNew}
className="bg-blue-600 hover:bg-blue-700 border-blue-600"
>
</Button>
</div>
</div>
</Card>
</div>
{/* 搜索筛选区域 */}
<Card className="mb-6 shadow-sm border border-gray-200">
<div className="mb-4">
<div className="flex items-center gap-2 mb-4">
<FilterOutlined className="text-gray-600" />
<span className="font-medium text-gray-700"></span>
</div>
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={8} lg={6}>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-600">
</label>
<SystemTypeSelect
value={selectedSystem}
onChange={(value) => {
setSelectedSystem(value);
setSelectedSystemTypeId(value || ""); // 同步更新
setSelectedDeviceType(null); // 清空故障类型选择
}}
placeholder="选择网系类别"
className="w-full"
/>
</div>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-600">
</label>
<DeviceTypeSelect
value={selectedDeviceType}
onChange={setSelectedDeviceType}
placeholder="选择故障类型"
className="w-full"
systemTypeId={selectedSystemTypeId}
/>
</div>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-600">
</label>
<FixTypeSelect
value={selectedFixType}
onChange={setSelectedFixType}
placeholder="选择故障状态"
className="w-full"
/>
</div>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-600">
</label>
<DepartmentSelect
placeholder="选择单位"
className="w-full"
value={selectedDept}
onChange={handleDeptChange}
/>
</div>
</Col>
<Col xs={24} sm={12} md={16} lg={12}>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-600">
</label>
<RangePicker
placeholder={["开始日期", "结束日期"]}
className="w-full"
value={dateRange}
onChange={(dates) => setDateRange(dates)}
format="YYYY-MM-DD"
allowClear
/>
</div>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-600 opacity-0">
</label>
<div className="flex gap-2">
<Button
type="primary"
icon={<SearchOutlined />}
onClick={handleSearch}
className="bg-blue-600 hover:bg-blue-700 border-blue-600"
block
>
</Button>
<Button
icon={<ReloadOutlined />}
onClick={handleReset}
className="border-gray-300 hover:border-gray-400"
>
</Button>
</div>
</div>
</Col>
</Row>
</div>
{/* 搜索历史标签 */}
{searchHistory.length > 0 && (
<div className="mt-4 p-4 bg-gray-50 rounded-lg border border-gray-200">
<div className="flex items-center gap-3 mb-3">
<HistoryOutlined className="text-gray-600" />
<span className="text-sm font-medium text-gray-700">
</span>
</div>
<div className="flex flex-wrap gap-2">
{searchHistory.slice(0, 5).map((history) => (
<Tag
key={history.id}
className="cursor-pointer bg-white border-gray-300 text-gray-700 hover:bg-gray-50 hover:border-blue-400 hover:text-blue-600 transition-colors duration-200"
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>
)}
</Card>
{/* 表格区域 */}
<Card className="shadow-sm border border-gray-200">
<DeviceTable ref={tableRef} onSelectedChange={handleSelectedChange} />
<DeviceModal />
</Card>
</div>
);
}