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
|
|
|
|
}
|