替换 所有选择品类组件

This commit is contained in:
孙景学
2025-08-11 11:25:19 +08:00
parent ad6b7536f5
commit e81f00eb00
16 changed files with 78 additions and 63 deletions

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { Input, Select, Row, Col, Table, Button, Form, Tooltip, message } from 'antd';
import { RightOutlined, LeftOutlined } from '@ant-design/icons';
import { getSupplierPage } from '@/servers/api/supplier';
import CategorySelector from '@/components/CategorySelector/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import './SupplierSelector.less';
import { useIntl } from 'umi';
import { useSupplierDetailModal } from '@/components/SupplierDetailModalContext/SupplierDetailModalContext';
@ -304,7 +304,7 @@ const SupplierSelector: React.FC<SupplierSelectorProps> = ({
name="categoryId"
label={intl.formatMessage({ id: 'supplierTaskManage.form.category' })}
>
<CategorySelector multiple={false} style={{ width: 150 }} />
<CategorySelector multiple={false} />
</Form.Item>
<Form.Item>

View File

@ -6,8 +6,8 @@ import { categoryTree } from './services';
const { Search } = Input;
interface Props {
value?: React.Key[];
onChange?: (value: React.Key[]) => void;
value?: React.Key[] | React.Key;
onChange?: (value: React.Key[] | React.Key) => void;
onChangeDetail?: (payload: {
categoryIds: string[];
coscoAccessCategoryList: {
@ -110,7 +110,6 @@ const TreeCategorySelector: React.FC<Props> = ({
parentKeysMapRef.current = parentKeysMap;
leafSetRef.current = leafSet;
initialExpandedKeysRef.current = Array.from(new Set(collectExpand));
console.log(tree, 'tree');
return tree;
@ -145,18 +144,36 @@ const TreeCategorySelector: React.FC<Props> = ({
return { tree, expandKeys: Array.from(expand) };
};
// 1) 定义 eqArray放在组件函数体内、useEffect 之上)
const eqArray = (a: React.Key[] = [], b: React.Key[] = []) =>
a.length === b.length && a.every((x, i) => x === b[i]);
// Props.value 是单个或数组value?: React.Key[] | React.Key
// 2) 归一化成数组(一定放在 useEffect 之前,且只声明一次)
const valueArray = useMemo<React.Key[]>(() => {
if (Array.isArray(value)) return value;
return value == null ? [] : [value];
}, [value]);
// 输入框显示名称(缺的就是它)
const selectedNames = useMemo(
() =>
valueArray
.map((k) => keyTitleMapRef.current[String(k)] || String(k))
.filter(Boolean),
[valueArray, origin]
);
// 拉数据
useEffect(() => {
setLoading(true);
console.log(1);
categoryTree()
.then((res: any) => {
if (res?.code === 200) {
const tree = buildTreeOnce(res.data || []);
setOrigin(tree);
setDisplayTree(tree);
// 初始展开
// 初始展开 两级展开
setExpandedKeys(initialExpandedKeysRef.current);
setAutoExpandParent(true);
}
@ -164,18 +181,19 @@ const TreeCategorySelector: React.FC<Props> = ({
.finally(() => setLoading(false));
}, []);
// 打开时同步外部值 + 默认两级展开
useEffect(() => {
if (!open) return;
if (multiple) setCheckedAllKeys(Array.isArray(value) ? value : []);
else setSelectedKey(value && value.length ? value[0] : null);
if (multiple) {
if (!eqArray(checkedAllKeys, valueArray)) setCheckedAllKeys(valueArray);
} else {
const next = valueArray.length ? valueArray[0] : null;
console.log(valueArray,'valueArray',next, selectedKey);
if (selectedKey !== next) setSelectedKey(next);
}
setSearch('');
setDisplayTree(origin);
setExpandedKeys(initialExpandedKeysRef.current);
setAutoExpandParent(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, multiple]);
}, [open, multiple, valueArray, origin]);
// 清理搜索定时器
useEffect(() => {
@ -190,8 +208,8 @@ const TreeCategorySelector: React.FC<Props> = ({
// 当 disabledCategories 改变时,更新树数据的 disabled 状态
useEffect(() => {
if (!origin.length) return;
if(disabledCategories.length !== 0) {
const updateDisabled = (nodes: TreeNode[]): TreeNode[] =>
if (disabledCategories.length !== 0) {
const updateDisabled = (nodes: TreeNode[]): TreeNode[] =>
nodes.map((n) => ({
...n,
disabled: disabledCategories.includes(n.key),
@ -210,19 +228,15 @@ const TreeCategorySelector: React.FC<Props> = ({
setExpandedKeys(expandKeys);
}
}
}, [disabledCategories]);
// 输入框显示名称(缺的就是它)
const selectedNames = useMemo(
() => (value as React.Key[])
.map((k) => keyTitleMapRef.current[String(k)] || String(k))
.filter(Boolean),
[value]
);
// Props.value 是单个或数组value?: React.Key[] | React.Key
// 2) 归一化成数组(一定放在 useEffect 之前,且只声明一次)
// 搜索:逐字符匹配 + 只显示命中分支
const onSearchChange = (val: string) => {
const onSearchChange = (val: string) => {
const searchValue = val ?? '';
setSearch(searchValue);
@ -294,7 +308,7 @@ const TreeCategorySelector: React.FC<Props> = ({
}];
setSelectedKey(id);
onChange?.([id]);
onChange?.(id);
onChangeDetail?.({ categoryIds: [id], coscoAccessCategoryList: list });
setOpen(false);
@ -330,7 +344,7 @@ const TreeCategorySelector: React.FC<Props> = ({
};
const handleClear = () => {
onChange?.([]);
onChange?.(multiple ? [] : '');
onChangeDetail?.({ categoryIds: [], coscoAccessCategoryList: [] });
};
@ -358,7 +372,7 @@ const TreeCategorySelector: React.FC<Props> = ({
style={{ paddingRight: '30px', boxSizing: 'border-box' }}
// suffix={<DownOutlined style={{ color: 'rgba(0,0,0,.45)' }} />}
/>
{hover && (value?.length ?? 0) > 0 && (
{hover && valueArray.length > 0 && (
<CloseCircleOutlined
onClick={(e) => {
e.stopPropagation();
@ -395,8 +409,8 @@ const TreeCategorySelector: React.FC<Props> = ({
<Search
allowClear
placeholder="请输入品类名称"
onChange={(e) => setSearch(e.target.value)} // 只更新本地状态
onSearch={(val) => onSearchChange(val)} // 回车/按钮点击才执行原逻辑
onChange={(e) => setSearch(e.target.value)} // 只更新本地状态
onSearch={(val) => onSearchChange(val)} // 回车/按钮点击才执行原逻辑
value={search}
style={{ marginBottom: 8 }}
/>
@ -424,7 +438,8 @@ const TreeCategorySelector: React.FC<Props> = ({
checkable={multiple}
onCheck={multiple ? handleCheck : undefined}
checkedKeys={multiple ? checkedAllKeys : undefined}
selectedKeys={multiple ? [] : (selectedKey ? [selectedKey] : [])}
selectedKeys={multiple ? [] : (valueArray.length ? [valueArray[0]] : [])}
// selectedKeys={multiple ? [] : (selectedKey ? [selectedKey] : [])}
onSelect={multiple ? handleSelectExpand : handleSelect}
/>
</div>

View File

@ -6,7 +6,7 @@ import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import ViewModal from './components/ViewModal';
//发起准入 弹窗
import CreateModal from './components/CreateModal';
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import AdmissionTypeSelect from '@/components/CommonSelect/AdmissionTypeSelect';
import AccessDepartmentSelect from "@/components/AccessDepartmentSelect"
//接口
@ -147,7 +147,7 @@ const SupplierCategoryEntry: React.FC = () => {
<AccessDepartmentSelect placeholder={'请选择准入单位'} />
</Form.Item>
<Form.Item name="categoryId" label="准入品类">
<CategorySelector multiple={false} style={{ width: 150 }} />
<CategorySelector multiple={false} />
</Form.Item>
<Form.Item name="approveStatus" label="审批状态">
<Select style={{ width: 150 }} placeholder="请选择状态" allowClear>

View File

@ -4,7 +4,7 @@ import { SearchOutlined, DeleteOutlined } from '@ant-design/icons';
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
//查看弹窗
import ViewModal from './components/ViewModal';
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import AdmissionTypeSelect from '@/components/CommonSelect/AdmissionTypeSelect';
import AccessStatusSelect from '@/components/CommonSelect/AccessStatusSelect';
import AccessDepartmentSelect from "@/components/AccessDepartmentSelect"
@ -104,7 +104,7 @@ const SupplierCategoryEntryReview: React.FC = () => {
<AccessDepartmentSelect placeholder={'请选择准入单位'} />
</Form.Item>
<Form.Item name="categoryId" label="准入品类">
<CategorySelector multiple={false} style={{ width: 150 }} />
<CategorySelector multiple={false} />
</Form.Item>
<Form.Item name="approveStatus" label="审批状态">
<AccessStatusSelect />

View File

@ -228,7 +228,7 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
<Form.Item
label="标题名称"
name="accessWorkName"
rules={[{ required: false, message: '请输入标题名称' }]}
rules={[{ required: true, message: '请输入标题名称' }]}
>
<Input placeholder="请输入标题名称" allowClear
onChange={e => {
@ -242,7 +242,7 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
<Form.Item
label="准入部门"
name="deptId"
rules={[{ required: false, message: '请选择准入部门' }]}
rules={[{ required: true, message: '请选择准入部门' }]}
>
<AccessDepartmentSelect style={{ width: '100%' }} orgCategory='' onChange={onChangeDepartmentSelect} />
</Form.Item>
@ -253,7 +253,7 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
<Form.Item
label="准入方式"
name="accessType"
rules={[{ required: false, message: '请选择准入方式' }]}
rules={[{ required: true, message: '请选择准入方式' }]}
>
<Radio.Group onChange={onMethodChange} value={admissionMethod}>
<Radio value="online">线</Radio>
@ -267,7 +267,7 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
<Form.Item
label="品类选择"
name="categoryIds"
rules={[{ required: false, message: '请选择准入品类' }]}
rules={[{ required: true, message: '请选择准入品类' }]}
>
<TreeCategorySelector
onChange={(ids) => form.setFieldsValue({ categoryIds: ids })}
@ -282,7 +282,7 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
<Form.Item
label="选择供应商"
name="supplier"
rules={[{ required: false, message: '请选择供应商' }]}
rules={[{ required: true, message: '请选择供应商' }]}
>
<Button onClick={() => setSupplierModalVisible(true)}>
@ -379,7 +379,7 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
<Form.Item
label="评审时间"
name="rangePicker"
rules={[{ required: false, message: '请选择评审时间' }]}
rules={[{ required: true, message: '请选择评审时间' }]}
>
<RangePicker
style={{ width: '300px' }}
@ -391,7 +391,7 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
<Form.Item
label="选择评审人员"
name="reviewers"
rules={[{ required: false, message: '请选择评审人员' }]}
rules={[{ required: true, message: '请选择评审人员' }]}
>
<Button onClick={() => setReviewerModalVisible(true)}>
@ -436,7 +436,7 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
name="division"
rules={[
{
required: false,
required: true,
validator: (_, value) => {
if (!value || !Array.isArray(value) || value.length === 0) {
return Promise.reject(new Error('请设置评审分工'));
@ -473,7 +473,7 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
<Form.Item
label="准入说明"
name="accessDesc"
rules={[{ required: false, message: '请输入准入说明' }]}
rules={[{ required: true, message: '请输入准入说明' }]}
>
<Input.TextArea placeholder="请依照《集团供应商管理制度》填写准入说明" rows={2} maxLength={200} showCount />
</Form.Item>

View File

@ -8,7 +8,7 @@ import ViewModal from './components/ViewModal';
import ResultModal from './components/ResultModal';
//发起准入 弹窗
import CreateModal from './components/CreateModal';
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import AdmissionTypeSelect from '@/components/CommonSelect/AdmissionTypeSelect';
import AccessStatusSelect from '@/components/CommonSelect/AccessStatusSelect';
import AccessDepartmentSelect from "@/components/AccessDepartmentSelect"
@ -156,7 +156,7 @@ const AccessManagement: React.FC = () => {
<AccessDepartmentSelect placeholder={'请选择准入单位'} />
</Form.Item>
<Form.Item name="categoryId" label="准入品类">
<CategorySelector multiple={false} style={{ width: 150 }} />
<CategorySelector multiple={false} />
</Form.Item>
<Form.Item name="approveStatus" label="审批状态">
<AccessStatusSelect />

View File

@ -6,7 +6,7 @@ import { SearchOutlined, DeleteOutlined } from '@ant-design/icons';
//接口
import { getCategoryPage } from './services';
//组件
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import AccessDepartmentSelect from "@/components/AccessDepartmentSelect"
//统一列表分页
import tableProps from '@/utils/tableProps'
@ -142,7 +142,7 @@ const CooperateEnterprise: React.FC = () => {
<AccessDepartmentSelect placeholder={'请选择准入单位'} />
</Form.Item>
<Form.Item name="categoryId" label="准入品类">
<CategorySelector multiple={false} style={{ width: 200 }} />
<CategorySelector multiple={false} />
</Form.Item>
<Form.Item name="createTime" label="准入时间">
<DatePicker.RangePicker placeholder={['开始时间', '结束时间']} />

View File

@ -9,7 +9,7 @@ import { connect } from 'umi';
//本地组件、弹窗、业务逻辑
// import SupplierViewModal from './components/SupplierViewModal';
// import SupplierDetailModal from './components/SupplierDetailModal';
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import RegionTypeSelect from '@/components/CommonSelect/RegionTypeSelect'
import AccessStatusSelect from '@/components/CommonSelect/AccessStatusSelect'
//本地服务/接口
@ -219,7 +219,7 @@ const mySupplierInquiry: React.FC<mySupplierInquiryProps> = ({ dispatch }) => {
<Input placeholder="请输入供应商名称关键字" style={{ width: 180 }} allowClear maxLength={20} />
</Form.Item>
<Form.Item name="categoryId" label="准入品类">
<CategorySelector multiple={false} style={{ width: 140 }} />
<CategorySelector multiple={false} />
</Form.Item>
<Form.Item name="accessStatus" label="准入状态">
<AccessStatusSelect />

View File

@ -5,7 +5,7 @@ import {
//接口
import { getSupplierCategoryPage } from '../services';
//组件
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import { useSupplierDetailModal } from '@/components/SupplierDetailModalContext/SupplierDetailModalContext';
interface Supplier {
@ -187,7 +187,7 @@ const SupplierSelectModal: React.FC<SupplierSelectModalProps> = ({
<Input style={{ width: 140 }} allowClear maxLength={50} placeholder="请输入供应商名称" />
</Form.Item>
<Form.Item name="categoryId" label="准入品类">
<CategorySelector multiple={false} style={{ width: 140 }} />
<CategorySelector multiple={false} />
</Form.Item>
<Form.Item>
<Space>

View File

@ -7,7 +7,7 @@ import type { ColumnsType } from 'antd/es/table';
//接口
import { getSupplierCategoryPage } from '../services';
//组件
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import { useSupplierDetailModal } from '@/components/SupplierDetailModalContext/SupplierDetailModalContext';
@ -179,7 +179,7 @@ const SupplierSelectModal: React.FC<SupplierSelectModalProps> = ({
</Form.Item>
<Form.Item name="categoryId" label="准入品类">
<CategorySelector multiple={false} style={{ width: 200 }} />
<CategorySelector multiple={false} />
</Form.Item>
<Form.Item>
<Space>

View File

@ -3,7 +3,7 @@ import { Form, Input, Select, DatePicker, Row, Col, Card, Radio, message } from
import moment from 'moment';
import { useIntl, FormattedMessage } from 'umi';
import { getAllAnnualTemplates,getAnnualTemplateDetail } from '@/servers/api/supplierAnnual';
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import styles from '../supplierAnnualTaskManageAdd.less';
import { CategoryLimitationType } from '@/dicts/supplierTemplateDict';
import type { Dispatch } from 'umi';

View File

@ -18,7 +18,7 @@ import {
DeleteOutlined,
SearchOutlined,
} from '@ant-design/icons';
import CategorySelector from '@/components/CategorySelector/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import { getAnnualTemplateList, enableAnnualTemplate, disableAnnualTemplate } from '@/servers/api/supplierAnnual';
import { AnnualTemplateStatus, AnnualTemplateStatusText, AnnualTemplateStatusColor } from '@/dicts/supplierAnnualDict';
import AccessDepartmentSelect from '@/components/AccessDepartmentSelect';
@ -334,7 +334,7 @@ const SupplierAnnualTemplateManage: React.FC = () => {
<AccessDepartmentSelect placeholder={'请选择准入单位'} />
</Form.Item>
<Form.Item name="categoryId" label={intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.category' })}>
<CategorySelector multiple={false} style={{ width: 200 }} />
<CategorySelector multiple={false} />
</Form.Item>
<Form.Item className="filter-btns">
<Button type="primary" htmlType="submit" icon={<SearchOutlined />}>

View File

@ -20,7 +20,7 @@ import {
import { history, useLocation, useIntl, connect, FormattedMessage } from 'umi';
import type { ConnectProps, Dispatch } from 'umi';
import { ArrowLeftOutlined, SaveOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import type { BreadcrumbState } from '@/models/breadcrumb';
import { AnnualTemplateStatus, AnnualTemplateStatusText } from '@/dicts/supplierAnnualDict';
import {

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback, forwardRef, useImperativeHandl
import { Form, Input, Select, DatePicker, Row, Col, Card, Radio, message } from 'antd';
import moment from 'moment';
import { getAllTemplates, getTemplateDetail } from '@/servers/api/supplierEvaluate';
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import styles from '../supplierTaskManageAdd.less';
import { CategoryLimitationType } from '@/dicts/supplierTemplateDict';
import type { Dispatch } from 'umi';

View File

@ -9,7 +9,7 @@ import {
TemplateStatus,
} from '@/dicts/supplierTemplateDict';
import { getTemplateList, enableTemplate, disableTemplate } from '@/servers/api/supplierEvaluate';
import CategorySelector from '@/components/CategorySelector/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import AccessDepartmentSelect from '@/components/AccessDepartmentSelect';
const { Option } = Select;
@ -322,7 +322,7 @@ const handleDisableTemplate = (id: string) => {
name="categoryId"
label={intl.formatMessage({ id: 'supplierTemplateManage.column.category' })}
>
<CategorySelector multiple={false} style={{ width: 200 }} />
<CategorySelector multiple={false} />
</Form.Item>
<Form.Item className="filter-btns">
<Button type="primary" htmlType="submit" icon={<SearchOutlined />}>

View File

@ -22,7 +22,7 @@ import type { Dispatch, ConnectProps } from 'umi';
import type { BreadcrumbState } from '@/models/breadcrumb';
import { ArrowLeftOutlined, SaveOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import EvaluateTemplateTable from '@/components/EvaluateTemplateTable';
import CategorySelector from '@/components/CategorySelector';
import CategorySelector from '@/components/TreeCategorySelector';
import {
CategoryLimitationType,
CategoryLimitationTypeText,