285 lines
8.5 KiB
TypeScript
285 lines
8.5 KiB
TypeScript
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;
|