新增useUploadFile、useDeleteFile、useGetFile、LeftTree、SelectTree

master
LiuJiaNan 2025-11-03 16:50:34 +08:00
parent 9d64fd33ea
commit cdf432f970
17 changed files with 489 additions and 19 deletions

25
components/LeftTree/Basic/index.d.ts vendored Normal file
View File

@ -0,0 +1,25 @@
import type { TreeProps } from "antd/es/tree";
import type { FC } from "react";
/**
*
*/
export interface BasicLeftTreeProps extends TreeProps {
/** 树形数据 title 字段,默认 name */
nameKey?: string;
/** 树形数据 key 字段,默认 id */
idKey?: string;
/** 树形数据 children 字段,默认 childrenList */
childrenKey?: string;
/** 决定 onGetNodePaths 是否包含自身节点,默认 true */
onGetNodePathsIsIncludeOneself?: boolean;
/** 获取父级节点 */
onGetNodePaths?: () => Record<string, any>[];
}
/**
* 使使
*/
declare const BasicLeftTree: FC<BasicLeftTreeProps>;
export default BasicLeftTree;

View File

@ -0,0 +1,184 @@
import { Input, Tree } from "antd";
import { useEffect, useState } from "react";
const { Search } = Input;
/**
* 基础左侧树组件不建议直接使用此组件二次继承使用
*/
const BasicLeftTree = (props) => {
const {
onSelect,
onGetNodePaths,
onGetNodePathsIsIncludeOneself = true,
expandedKeys: externalExpandedKeys,
treeData = [],
nameKey = "name",
idKey = "id",
childrenKey = "childrenList",
...restProps
} = props;
const [expandedKeys, setExpandedKeys] = useState([]);
const [searchValue, setSearchValue] = useState("");
const [autoExpandParent, setAutoExpandParent] = useState(true);
useEffect(() => {
setExpandedKeys(externalExpandedKeys);
}, [externalExpandedKeys]);
// 展开所有包含匹配项的父节点
const getAllExpandedKeys = (data, searchValue, keys = []) => {
data.forEach((node) => {
if (node[childrenKey]) {
if (node[nameKey].includes(searchValue)
|| node[childrenKey].some(child => child[nameKey].includes(searchValue))) {
keys.push(node[idKey]);
}
getAllExpandedKeys(node[childrenKey], searchValue, keys);
}
});
return keys;
};
// 过滤树数据,只保留匹配的节点
const filterTreeData = (data, searchValue) => {
if (!searchValue) {
return data;
}
return data.reduce((acc, node) => {
// 检查当前节点是否匹配
const isMatch = node[nameKey].includes(searchValue);
// 递归处理子节点
const filteredChildren = node[childrenKey] ? filterTreeData(node[childrenKey], searchValue) : [];
// 如果当前节点匹配或者有匹配的子节点,则保留该节点
if (isMatch || filteredChildren.length > 0) {
acc.push({
...node,
[childrenKey]: filteredChildren.length > 0 ? filteredChildren : undefined,
});
}
return acc;
}, []);
};
const handleExpand = (newExpandedKeys) => {
setExpandedKeys(newExpandedKeys);
setAutoExpandParent(false);
};
const getNodePaths = (data, targetId, idKey, childrenKey, path = [], isIncludeOneself) => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const newPath = [...path, node];
// 找到目标节点根据isIncludeOneself决定是否包含自身
if (node[idKey] === targetId) {
if (isIncludeOneself)
return newPath; // 包含自身
else
return path; // 不包含自身,只返回父节点路径
}
// 递归查找子节点
if (node[childrenKey] && node[childrenKey].length > 0) {
const result = getNodePaths(node[childrenKey], targetId, idKey, childrenKey, newPath, isIncludeOneself);
if (result) {
return result;
}
}
}
return null;
};
const handleSelect = (selectedKeys, event) => {
if (selectedKeys.length > 0) {
const selectedNodeId = selectedKeys[0];
const parentNodes = getNodePaths(treeData, selectedNodeId, idKey, childrenKey, onGetNodePathsIsIncludeOneself);
onGetNodePaths?.(parentNodes);
}
onSelect?.(selectedKeys, event);
};
const onFilterTreeData = (value) => {
setSearchValue(value);
setAutoExpandParent(true);
if (!value) {
setExpandedKeys([]);
return;
}
const expandedKeys = getAllExpandedKeys(treeData, value);
setExpandedKeys(expandedKeys);
};
const onSearch = async (value) => {
if (value === searchValue)
return;
onFilterTreeData(value);
};
// 渲染带高亮的标题
const renderTitle = (name) => {
if (!searchValue)
return name;
const index = name.indexOf(searchValue);
if (index === -1)
return name;
const beforeStr = name.substring(0, index);
const afterStr = name.substring(index + searchValue.length);
return (
<span>
{beforeStr}
<span style={{ color: "#f50" }}>{searchValue}</span>
{afterStr}
</span>
);
};
// 递归处理树节点标题显示
const processTreeData = (data) => {
return data.map(node => ({
...node,
[nameKey]: renderTitle(node[nameKey]),
[childrenKey]: node[childrenKey] ? processTreeData(node[childrenKey]) : undefined,
}));
};
// 过滤并处理树数据
const filteredTreeData = filterTreeData(treeData, searchValue);
const processedTreeData = processTreeData(filteredTreeData);
return (
<div style={{ width: 300 }}>
<Search
style={{ marginBottom: 8 }}
placeholder="输入关键字进行过滤"
onSearch={onSearch}
/>
<Tree
onExpand={handleExpand}
onSelect={handleSelect}
autoExpandParent={autoExpandParent}
expandedKeys={expandedKeys}
treeData={processedTreeData}
fieldNames={{ title: nameKey, key: idKey, children: childrenKey }}
{...restProps}
/>
</div>
);
};
BasicLeftTree.displayName = "BasicLeftTree";
export default BasicLeftTree;

View File

@ -0,0 +1,17 @@
import type { FC } from "react";
import type { BasicLeftTreeProps } from "../../Basic";
/**
*
*/
export interface DepartmentLeftTreeProps extends Omit<BasicLeftTreeProps, "treeData"> {
/** 请求参数 */
params?: Record<string, any>;
}
/**
*
*/
declare const DepartmentLeftTree: FC<DepartmentLeftTreeProps>;
export default DepartmentLeftTree;

View File

@ -0,0 +1,32 @@
import { request } from "@cqsjjb/jjb-common-lib/http";
import { useEffect, useState } from "react";
import BasicLeftTree from "../../Basic";
/**
* 部门左侧树组件港务局版本
*/
function DepartmentLeftTree(props) {
const {
params = {},
...restProps
} = props;
const [treeData, setTreeData] = useState([]);
const getData = async () => {
const { data } = await request("/basic-info/department/listTree", "post", params);
setTreeData(data);
};
useEffect(() => {
getData();
}, []);
return (
<BasicLeftTree treeData={treeData} {...restProps} />
);
}
DepartmentLeftTree.displayName = "DepartmentLeftTree";
export default DepartmentLeftTree;

25
components/SelectTree/Basic/index.d.ts vendored Normal file
View File

@ -0,0 +1,25 @@
import type { TreeSelectProps } from "antd/es/tree-select";
import type { FC } from "react";
/**
*
*/
export interface BasicSelectTreeProps extends TreeSelectProps {
/** 树形数据 label 字段,默认 name */
nameKey?: string;
/** 树形数据 value 字段,默认 id */
idKey?: string;
/** 树形数据 children 字段,默认 childrenList */
childrenKey?: string;
/** 决定 onGetNodePaths 是否包含自身节点,默认 true */
onGetNodePathsIsIncludeOneself?: boolean;
/** 获取父级节点 */
onGetNodePaths?: (nodes: Record<string, any>[]) => void;
}
/**
* 使使
*/
declare const BasicSelectTree: FC<BasicSelectTreeProps>;
export default BasicSelectTree;

View File

@ -0,0 +1,50 @@
import { TreeSelect } from "antd";
import { getTreeNodePaths } from "../../../utils";
function BasicSelectTree(props) {
const {
onSelect,
onGetNodePaths,
onGetNodePathsIsIncludeOneself = true,
placeholder = "",
treeData = [],
nameKey = "name",
idKey = "id",
childrenKey = "childrenList",
...restProps
} = props;
const handleSelect = (value, node, extra) => {
if (value.length > 0) {
const parentNodes = getTreeNodePaths({
data: treeData,
targetId: value,
idKey,
childrenKey,
isIncludeOneself: onGetNodePathsIsIncludeOneself
});
onGetNodePaths?.(parentNodes);
}
onSelect?.(value, node, extra);
};
return (
<TreeSelect
showSearch
style={{ width: "100%" }}
styles={{
popup: { root: { maxHeight: 400, overflow: "auto" } },
}}
placeholder={`请选择${placeholder}`}
onSelect={handleSelect}
allowClear
treeData={treeData}
fieldNames={{ label: nameKey, value: idKey, children: childrenKey }}
{...restProps}
/>
);
}
BasicSelectTree.displayName = "BasicSelectTree";
export default BasicSelectTree;

View File

@ -0,0 +1,17 @@
import type { FC } from "react";
import type { BasicSelectTreeProps } from "../../Basic";
/**
*
*/
export interface DepartmentSelectTreeProps extends Omit<BasicSelectTreeProps, "treeData" | "placeholder"> {
/** 请求参数 */
params?: Record<string, any>;
}
/**
*
*/
declare const DepartmentSelectTree: FC<DepartmentSelectTreeProps>;
export default DepartmentSelectTree;

View File

@ -0,0 +1,58 @@
import { request } from "@cqsjjb/jjb-common-lib/http";
import { useEffect, useState } from "react";
import BasicLeftTree from "../../Basic";
/**
* 部门左侧树组件港务局版本
*/
function DepartmentSelectTree(props) {
const {
params = {},
...restProps
} = props;
const [treeData, setTreeData] = useState([
{
name: "parent 1",
id: "0-0",
childrenList: [
{
name: "parent 1-0",
id: "0-0-0",
childrenList: [
{
name: "leaf",
id: "0-0-0-0",
},
{
name: "leaf",
id: "0-0-0-1",
},
],
},
{
name: "parent 1-1",
id: "0-0-1",
childrenList: [{ name: <span style={{ color: "#1677ff" }}>sss</span>, id: "0-0-1-0" }],
},
],
},
]);
const getData = async () => {
const { data } = await request("/basic-info/department/listTree", "post", params);
setTreeData(data);
};
useEffect(() => {
getData();
}, []);
return (
<BasicLeftTree treeData={treeData} placeholder="部门" {...restProps} />
);
}
DepartmentSelectTree.displayName = "DepartmentSelectTree";
export default DepartmentSelectTree;

View File

@ -2,7 +2,7 @@ import { request } from "@cqsjjb/jjb-common-lib/http";
import { useState } from "react";
/**
* 删除文件 TODO
* 删除文件
*/
function useDeleteFile(returnType = "object") {
// loading状态
@ -23,7 +23,7 @@ function useDeleteFile(returnType = "object") {
// 发送请求
request(
single ? `/basic-info/imgFiles/${files[0].filePath}` : `/basic-info/imgFiles/ids?ids=${files.map(f => f.id)}`,
single ? `/basic-info/imgFiles/delete?filePath=${files[0].filePath}` : `/basic-info/imgFiles/ids?ids=${files.map(f => f.id)}`,
"delete",
)
.then((res) => {

View File

@ -1,10 +1,10 @@
import { request } from "@cqsjjb/jjb-common-lib/http";
import { useState } from "react";
import { UPLOAD_FILE_TYPE_ENUM } from "../../enum/uploadFile";
import { UPLOAD_FILE_TYPE_ENUM } from "../../enum/uploadFile/gwj";
import { addingPrefixToFile } from "../../utils";
/**
* 获取文件 TODO
* 获取文件
*/
function useGetFile(returnType = "object") {
// loading状态

View File

@ -8,25 +8,31 @@ export interface UploadFile {
[key: string]: any;
}
export interface Params {
export interface BaseParams {
/** 文件类型 */
type: number;
/** 所属端 */
path: string;
/** 企业id */
corpinfoId: string;
/** 外键id */
foreignKey: string;
corpinfoId?: string;
[key: string]: any;
}
export interface SingleParams extends BaseParams {
/** 外键id - 单文件上传时可选 */
foreignKey?: string;
}
export interface MultipleParams extends BaseParams {
/** 外键id - 多文件上传时必填 */
foreignKey: string;
}
export interface SingleUploadFileOptions {
/** 要上传的文件 */
files: UploadFile[];
/** 是否单文件上传 */
single?: true;
/** 上传的参数 */
params: Params;
params: SingleParams;
}
export interface MultipleUploadFileOptions {
@ -35,7 +41,7 @@ export interface MultipleUploadFileOptions {
/** 是否单文件上传 */
single: false;
/** 上传的参数 */
params: Params;
params: MultipleParams;
}
export interface MultipleFileResponse {

View File

@ -1,9 +1,9 @@
import { request } from "@cqsjjb/jjb-common-lib/http";
import { useState } from "react";
import { UPLOAD_FILE_PATH_ENUM, UPLOAD_FILE_TYPE_ENUM } from "../../enum/uploadFile";
import { UPLOAD_FILE_PATH_ENUM, UPLOAD_FILE_TYPE_ENUM } from "../../enum/uploadFile/gwj";
/**
* 上传文件 TODO
* 上传文件
*/
function useUploadFile(returnType = "object") {
// loading状态
@ -35,11 +35,8 @@ function useUploadFile(returnType = "object") {
if (!path)
throw new Error(`未找到 type ${params.type} 对应的 path `);
// if (!params.path)
// throw new Error("请传入 options.params.path");
if (params.corpinfoId === undefined || params.corpinfoId === null)
throw new Error("请传入 options.params.corpinfoId");
if (params.foreignKey === undefined || params.foreignKey === null)
// 当single为false时foreignKey是必需的
if (!single && (params.foreignKey === undefined || params.foreignKey === null))
throw new Error("请传入 options.params.foreignKey");
const formData = new FormData();

23
utils/index.d.ts vendored
View File

@ -123,6 +123,22 @@ interface IsEmptyToWhetherOptions {
yesValue?: string | number;
}
// 为 getTreeNodePaths 函数定义接口类型
interface GetTreeNodePathsOptions {
/** 树形数据 */
data: any[];
/** 目标节点ID */
targetId: string | number;
/** id字段名 */
idKey: string;
/** 子节点字段名 */
childrenKey: string;
/** 路径数组 */
path?: any[];
/** 是否包含自身 */
isIncludeOneself?: boolean;
}
/**
*
*/
@ -281,3 +297,10 @@ export function getIndexColumn(pagination: false | BasePaginationConfig): {
* url
*/
export function getFileUrl(): string;
/**
*
*/
export function getTreeNodePaths<T extends Record<string, any> = Record<string, any>>(
options: GetTreeNodePathsOptions,
): T[] | null;

View File

@ -394,6 +394,42 @@ export function getIndexColumn(pagination) {
};
}
/**
* 获取树形节点路径
*/
export function getTreeNodePaths(options) {
const { data, targetId, idKey, childrenKey, path = [], isIncludeOneself } = options;
for (let i = 0; i < data.length; i++) {
const node = data[i];
const newPath = [...path, node];
// 找到目标节点根据isIncludeOneself决定是否包含自身
if (node[idKey] === targetId) {
if (isIncludeOneself)
return newPath; // 包含自身
else
return path; // 不包含自身,只返回父节点路径
}
// 递归查找子节点
if (node[childrenKey] && node[childrenKey].length > 0) {
const result = getTreeNodePaths({
data: node[childrenKey],
targetId,
idKey,
childrenKey,
path: newPath,
isIncludeOneself,
});
if (result) {
return result;
}
}
}
return null;
}
/**
* 获取文件url
*/