ai-course/app/backend/src/pages/virtual/components/TreeSelect/index.tsx

285 lines
8.5 KiB
TypeScript
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 styles from './index.module.less';
import { Button, Empty, GetProps, Input, message, Modal, Tree, type TreeDataNode } from 'antd';
import React, { useEffect, useMemo, useState } from 'react';
import { TreeProps } from 'antd/es/tree';
import { SoftCategoryCreate } from '../CreateClass';
import { deleteSoftwareClassApi } from '../../api/virtual';
type SearchProps = GetProps<typeof Input.Search>;
const { Search } = Input;
interface TreeSelectProps {
treeListData: any[];
hasOptions: boolean;
// allTreeKeys: string[];
classId?: number | null | string;
setClassId: (id: number | null | string) => void;
searchClassKey: string;
setSearchClassKey: (value: string) => void;
refreshTreeData: () => void;
}
const TreeSelect = (props: TreeSelectProps) => {
const {
hasOptions = false,
treeListData = [],
classId = null,
refreshTreeData,
setClassId,
searchClassKey,
setSearchClassKey,
} = props;
const [deArray, setDeArray] = useState<number[]>([]);
const [expandedKeys, setExpandedKeys] = useState<any[]>([]);
const [treeOptions, setTreeOptions] = useState<any[]>([]);
const [createClassVisible, setCreateClassVisible] = useState<boolean>(false);
const [selectedId, setSelectedId] = useState<number[]>([]);
const [deleteModalVisible, setDeleteModalVisible] = useState<boolean>(false);
const [confirmDeleteLoading, setConfirmDeleteLoading] = useState<boolean>(false);
const [searchInputShow, setSearchInputShow] = useState<string>(searchClassKey);
const [editSelectId, setEditSelectId] = useState<any>();
const [isEditClass, setIsEditClass] = useState<boolean>(false);
const [editSelectItem, setEditSelectItem] = useState<any>();
const onChangeSearchClassKey = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
setSearchInputShow(value);
};
const onSearchClass = (value: string) => {
setSearchClassKey(value);
};
useEffect(() => {
getTreeOptionsAndKey(treeListData);
}, [treeListData]);
const getTreeOptionsAndKey = (treeListData: any[]) => {
const new_arr: any = checkArr(treeListData, 0);
setTreeOptions(new_arr);
const allKeys = getAllKeys(new_arr);
// @ts-ignore
setExpandedKeys(allKeys);
};
const getAllKeys = (treeData: any[]): React.Key[] => {
let keys: React.Key[] = [];
treeData?.forEach((item) => {
keys.push(item.key);
if (item.children && item.children.length > 0) {
keys = keys.concat(getAllKeys(item.children));
}
});
return keys;
};
const checkArr = (treeData: any[], id: number) => {
const arr = [];
for (let i = 0; i < treeData[id]?.length; i++) {
if (!treeData[treeData[id][i].id]) {
arr.push({
title: treeData[id][i].className,
key: treeData[id][i].id,
});
} else {
const new_arr: any = checkArr(treeData, treeData[id][i].id);
arr.push({
title: treeData[id][i].className,
key: treeData[id][i].id,
children: new_arr,
});
}
}
return arr;
};
const treeOnSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
if (selectedKeys.length > 0) {
setClassId(selectedKeys[0].toString()); // 取第一个选中项作为classId
setEditSelectId(selectedKeys[0].toString());
} else {
setClassId('');
setEditSelectId('');
}
};
const treeOnCheck: TreeProps['onCheck'] = (checkedKeys, info) => {
const keysArray = Array.isArray(checkedKeys) ? checkedKeys : checkedKeys.checked;
const numericKeys = keysArray.map((key) => Number(key));
setDeArray(numericKeys);
setSelectedId(numericKeys);
};
const filteredTreeData = useMemo(() => {
const loop = (data: TreeDataNode[]): TreeDataNode[] =>
data.map((item) => {
const strTitle = item.title as string;
const index = strTitle.indexOf(searchClassKey);
const beforeStr = strTitle.substring(0, index);
const afterStr = strTitle.slice(index + searchClassKey.length);
const title =
index > -1 ? (
<span key={item.key}>
{beforeStr}
<span style={{ color: '#f50' }}>{searchClassKey}</span>
{afterStr}
</span>
) : (
<span key={item.key}>{strTitle}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key,
};
});
return loop(treeOptions);
}, [searchClassKey, treeOptions]);
const getDetailByClassId = (data: any, id: any) => {
const arrays = Object.values(data).filter(Array.isArray);
for (const array of arrays) {
const flattened = array.flat(Infinity);
// 添加类型转换和严格比较
const found = flattened.find(
(item) =>
item && (item.id == id || item.id === Number(id) || String(item.id) === String(id))
);
if (found) return found;
}
return null;
};
//根据id删除
const showDeleteClassConfirm = (id: number) => {
setSelectedId((prevIds) => {
if (!prevIds.includes(id)) {
return [...prevIds, id];
}
return prevIds;
});
setDeleteModalVisible(true);
};
const handleDelete = async () => {
if (selectedId.length === 0) return;
setConfirmDeleteLoading(true);
try {
await deleteSoftwareClassApi(deArray);
message.success('删除成功');
refreshTreeData();
} catch (error) {
message.error('删除失败');
console.error('删除失败:', error);
} finally {
setConfirmDeleteLoading(false);
setDeleteModalVisible(false);
setSelectedId([]);
}
};
const handleCancelDelete = () => {
setDeleteModalVisible(false);
};
/* id,
name,
parent_id: parentId,
sort,*/
// 编辑
const showEditClassModal = () => {
const result = getDetailByClassId(treeListData, editSelectId);
setEditSelectItem(result);
setIsEditClass(true);
setCreateClassVisible(true);
};
const handleChangeCategorySuccess = () => {
setEditSelectItem(null);
setEditSelectId(null);
setIsEditClass(false);
setCreateClassVisible(false);
refreshTreeData();
};
const handleChangeCategoryCancel = () => {
setEditSelectItem(null);
setIsEditClass(false);
setCreateClassVisible(false);
};
return (
<>
<div className={styles.box}>
<div className={styles.top_line}>
<div className={styles.tree_title}></div>
<div className={styles.tree_btns}>
<Button
type="primary"
style={{ marginRight: 15 }}
onClick={() => setCreateClassVisible(true)}
>
</Button>
<Button
type="primary"
style={{ marginRight: 15 }}
disabled={!editSelectId}
onClick={() => {
showEditClassModal();
}}
>
</Button>
<Button
type="primary"
disabled={selectedId.length === 0}
onClick={() => showDeleteClassConfirm(0)}
>
</Button>
</div>
</div>
<div className={styles.Search_Tree}>
<Search
style={{ marginBottom: 8 }}
placeholder="搜索分类"
onChange={onChangeSearchClassKey}
// onPressEnter={onSearchClass}
onSearch={onSearchClass}
allowClear
value={searchInputShow}
/>
<Tree
checkable
onSelect={treeOnSelect}
onCheck={treeOnCheck}
treeData={hasOptions ? filteredTreeData : []}
expandedKeys={expandedKeys}
onExpand={(keys) => setExpandedKeys(keys)} // 允许用户手动展开/折叠
defaultExpandParent={true}
/>
{!hasOptions && <Empty></Empty>}
</div>
</div>
{createClassVisible && (
<SoftCategoryCreate
open={createClassVisible}
isEdit={isEditClass}
editData={editSelectItem}
onCancel={handleChangeCategoryCancel}
onSuccess={handleChangeCategorySuccess}
/>
)}
<Modal
title="确认删除"
open={deleteModalVisible}
onOk={handleDelete}
onCancel={handleCancelDelete}
confirmLoading={confirmDeleteLoading}
>
<p></p>
</Modal>
</>
);
};
export default TreeSelect;