doctor-mail/apps/web/src/components/models/term/term-select.tsx

304 lines
7.5 KiB
TypeScript
Raw Normal View History

2024-09-10 10:31:24 +08:00
import { TreeSelect, TreeSelectProps } from "antd";
2025-01-03 09:24:46 +08:00
import React, { useEffect, useState, useCallback, useRef } from "react";
2025-01-06 08:45:23 +08:00
import { getUniqueItems } from "@nice/common";
import { api } from "@nice/client";
2024-12-30 08:26:40 +08:00
import { DefaultOptionType } from "antd/es/select";
2025-02-17 09:26:30 +08:00
import "./TermSelect.css";
2024-09-10 10:31:24 +08:00
interface TermSelectProps {
2024-12-30 08:26:40 +08:00
defaultValue?: string | string[];
value?: string | string[];
onChange?: (value: string | string[]) => void;
placeholder?: string;
multiple?: boolean;
// rootId?: string;
// domain?: boolean;
taxonomyId?: string;
disabled?: boolean;
className?: string;
2025-01-03 09:24:46 +08:00
domainId?: string;
2024-09-10 10:31:24 +08:00
}
export default function TermSelect({
2024-12-30 08:26:40 +08:00
defaultValue,
value,
onChange,
className,
2025-01-25 02:27:40 +08:00
placeholder = "选择术语",
2024-12-30 08:26:40 +08:00
multiple = false,
taxonomyId,
2025-01-03 09:24:46 +08:00
domainId,
2024-12-30 08:26:40 +08:00
disabled = false,
2024-09-10 10:31:24 +08:00
}: TermSelectProps) {
2024-12-30 08:26:40 +08:00
const utils = api.useUtils();
const [listTreeData, setListTreeData] = useState<
Omit<DefaultOptionType, "label">[]
>([]);
2024-09-10 10:31:24 +08:00
2024-12-30 08:26:40 +08:00
const fetchParentTerms = useCallback(
async (termIds: string | string[], taxonomyId?: string) => {
const idsArray = Array.isArray(termIds)
? termIds
: [termIds].filter(Boolean);
try {
return await utils.term.getParentSimpleTree.fetch({
termIds: idsArray,
taxonomyId,
2025-01-03 09:24:46 +08:00
domainId,
2024-12-30 08:26:40 +08:00
});
} catch (error) {
console.error(
"Error fetching parent departments for deptIds",
idsArray,
":",
error
);
throw error;
}
},
[utils]
);
2024-09-10 10:31:24 +08:00
2024-12-30 08:26:40 +08:00
const fetchTerms = useCallback(async () => {
try {
const rootDepts = await utils.term.getChildSimpleTree.fetch({
taxonomyId,
2025-01-03 09:24:46 +08:00
domainId,
2024-12-30 08:26:40 +08:00
});
let combinedDepts = [...rootDepts];
if (defaultValue) {
const defaultDepts = await fetchParentTerms(
defaultValue,
taxonomyId
);
combinedDepts = getUniqueItems(
[...listTreeData, ...combinedDepts, ...defaultDepts] as any,
"id"
);
}
if (value) {
const valueDepts = await fetchParentTerms(value, taxonomyId);
combinedDepts = getUniqueItems(
[...listTreeData, ...combinedDepts, ...valueDepts] as any,
"id"
);
}
2024-09-10 10:31:24 +08:00
2024-12-30 08:26:40 +08:00
setListTreeData(combinedDepts);
} catch (error) {
2025-01-25 19:51:16 +08:00
console.error("Error fetching terms:", error);
2024-12-30 08:26:40 +08:00
}
}, [defaultValue, value, taxonomyId, utils, fetchParentTerms]);
2024-09-10 10:31:24 +08:00
2024-12-30 08:26:40 +08:00
useEffect(() => {
fetchTerms();
}, [defaultValue, value, taxonomyId, fetchTerms]);
2024-09-10 10:31:24 +08:00
2024-12-30 08:26:40 +08:00
const handleChange = (newValue: any) => {
if (onChange) {
const processedValue =
multiple && Array.isArray(newValue)
? newValue.map((item) => item.value)
: newValue;
onChange(processedValue);
}
};
2024-09-10 10:31:24 +08:00
2024-12-30 08:26:40 +08:00
const onLoadData: TreeSelectProps["loadData"] = async ({ id }) => {
try {
const result = await utils.term.getChildSimpleTree.fetch({
termIds: [id],
taxonomyId,
2025-01-03 09:24:46 +08:00
domainId,
2024-12-30 08:26:40 +08:00
});
const newItems = getUniqueItems([...listTreeData, ...result], "id");
setListTreeData(newItems);
} catch (error) {
console.error(
"Error loading data for node with id",
id,
":",
error
);
}
};
2024-09-10 10:31:24 +08:00
2024-12-30 08:26:40 +08:00
const handleExpand = async (keys: React.Key[]) => {
// console.log(keys);
try {
const allKeyIds =
keys.map((key) => key.toString()).filter(Boolean) || [];
const expandedNodes = await utils.term.getChildSimpleTree.fetch({
termIds: allKeyIds,
taxonomyId,
2025-01-03 09:24:46 +08:00
domainId,
2024-12-30 08:26:40 +08:00
});
const flattenedNodes = expandedNodes.flat();
const newItems = getUniqueItems(
[...listTreeData, ...flattenedNodes],
"id"
);
setListTreeData(newItems);
} catch (error) {
console.error("Error expanding nodes with keys", keys, ":", error);
}
};
const handleDropdownVisibleChange = async (open: boolean) => {
if (open) {
// This will attempt to expand all nodes and fetch their children when the dropdown opens
const allKeys = listTreeData.map((item) => item.id);
await handleExpand(allKeys);
}
};
2025-02-17 09:26:30 +08:00
// 检测旧版本浏览器
const isLegacyBrowser = () => {
const chromeMatch = navigator.userAgent.match(/Chrome\/(\d+)/);
if (chromeMatch) {
const version = parseInt(chromeMatch[1], 10);
return version <= 87;
}
return false;
};
// 为降级版本准备的状态
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
2024-12-30 08:26:40 +08:00
2025-02-17 09:26:30 +08:00
// 处理点击外部关闭下拉框
useEffect(() => {
if (isLegacyBrowser()) {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () =>
document.removeEventListener("mousedown", handleClickOutside);
}
}, []);
if (isLegacyBrowser()) {
return (
<div className="legacy-select-container" ref={dropdownRef}>
<div
className="legacy-select-trigger"
onClick={() => setIsOpen(!isOpen)}>
{multiple ? (
<div className="legacy-select-multiple-value">
{Array.isArray(value) && value.length > 0 ? (
value.map((v) => {
const item = listTreeData.find(
(i) => i.id === v
);
return (
<span key={v} className="legacy-tag">
{item?.title}
<span
className="legacy-tag-close"
onClick={(e) => {
e.stopPropagation();
const newValue =
value.filter(
(val: string) =>
val !== v
);
handleChange(newValue);
}}>
×
</span>
</span>
);
})
) : (
<span className="legacy-placeholder">
{placeholder}
</span>
)}
</div>
) : (
<div className="legacy-select-single-value">
{value ? (
listTreeData.find((i) => i.id === value)?.title
) : (
<span className="legacy-placeholder">
{placeholder}
</span>
)}
</div>
)}
<span className={`legacy-arrow ${isOpen ? "open" : ""}`}>
</span>
</div>
{isOpen && (
<div className="legacy-select-dropdown">
{listTreeData.map((item) => (
<div
key={item.id}
className={`legacy-select-option ${
multiple
? Array.isArray(value) &&
value.includes(item.id)
: value === item.id
? "selected"
: ""
}`}
onClick={() => {
if (multiple) {
const newValue = Array.isArray(value)
? value.includes(item.id)
? value.filter(
(v) => v !== item.id
)
: [...value, item.id]
: [item.id];
handleChange(newValue);
} else {
handleChange(item.id);
setIsOpen(false);
}
}}>
{multiple && (
<span className="legacy-checkbox">
{Array.isArray(value) &&
value.includes(item.id) &&
"✓"}
</span>
)}
{item.title}
</div>
))}
</div>
)}
</div>
);
}
2024-12-30 08:26:40 +08:00
return (
<TreeSelect
treeDataSimpleMode
disabled={disabled}
showSearch
allowClear
defaultValue={defaultValue}
value={value}
2025-02-17 09:26:30 +08:00
// className={`custom-tree-select ${className || ""}`}
2024-12-30 08:26:40 +08:00
placeholder={placeholder}
onChange={handleChange}
loadData={onLoadData}
treeData={listTreeData}
treeCheckable={multiple}
showCheckedStrategy={TreeSelect.SHOW_ALL}
treeCheckStrictly={multiple}
onClear={() => handleChange(multiple ? [] : undefined)}
onTreeExpand={handleExpand}
onDropdownVisibleChange={handleDropdownVisibleChange}
2025-02-17 09:26:30 +08:00
// 添加以下属性来优化显示
2024-12-30 08:26:40 +08:00
/>
);
2025-02-17 09:26:30 +08:00
}