Files
fe_supplier_frontend/src/pages/supplier/admission/admissionManagement/components/CreateModal.tsx
2025-07-16 16:20:52 +08:00

550 lines
23 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Modal, Form, Select, Button, Tree, message, DatePicker, Radio, Upload, Input } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd/es/upload/interface';
//组件
import SupplierSelector from './SupplierSelector';
import ReviewerSelector from './ReviewerSelector';
import DivisionModal from './DivisionModal';
// 请求
import { categoryTree, add, uploadFile } from '../services';
import AccessDepartmentSelect from '@/components/AccessDepartmentSelect';
const { Option } = Select;
const { RangePicker } = DatePicker;
//selected 类型
interface Reviewer {
userId: string;
name: string;
id: string;
deptId: string;
orgName: string;
}
// 传入的人接口
interface ReviewerSelectorData {
selected: Reviewer[];
leader: Reviewer | null;
}
// 主体
const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ visible, onCancel }) => {
const [form] = Form.useForm();
//品类选择
const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
//供应商
const [selectedSuppliers, setSelectedSuppliers] = useState<any[]>([]);
// 选择评审人员
const [selectedReviewers, setSelectedReviewers] = useState<ReviewerSelectorData>({
selected: [],
leader: null,
});
//评审分工
const [divisionData, setDivisionData] = useState<any[]>([]);
//准入方式
const [admissionMethod, setAdmissionMethod] = useState<string>('online');
//供应商符合性审查
const [fileList, setFileList] = useState<UploadFile<any>[]>([]);
//供应商弹出
const [supplierModalVisible, setSupplierModalVisible] = useState(false);
const [reviewerModalVisible, setReviewerModalVisible] = useState(false);
const [divisionModalVisible, setDivisionModalVisible] = useState(false);
//品类选择渲染数据
const [categoriesTreeData, setCategoriesTreeData] = useState([]);
//品类选择数据中字段转换
const convertTreeData = (data: any) => {
return data.map((item: any) => ({
...item,
title: item.categoryName,
key: item.id,
children: item.children ? convertTreeData(item.children) : undefined,
}));
}
function findLeafKeys(treeData: any[]): string[] {
let leafKeys: string[] = [];
function dfs(nodes: any[]) {
nodes.forEach(node => {
if (!node.children || node.children.length === 0) {
leafKeys.push(node.key);
} else {
dfs(node.children);
}
});
}
dfs(treeData);
return leafKeys;
}
//品类选择
const onCheck = (
checkedKeysValue:
| React.Key[]
| { checked: React.Key[]; halfChecked: React.Key[] }
) => {
const keys = Array.isArray(checkedKeysValue)
? checkedKeysValue
: checkedKeysValue.checked;
// 只取叶子节点 key
const leafKeys = findLeafKeys(convertTreeData(categoriesTreeData));
const onlyLeafChecked = keys.filter(key => leafKeys.includes(String(key)));
setCheckedKeys(keys); // UI 显示用,还是全量
form.setFieldsValue({ categoryIds: onlyLeafChecked }); // 只存叶子到表单
// setCheckedKeys(keys);
// form.setFieldsValue({ categoryIds: keys });
};
// 选择准入方式
const onMethodChange = (e: any) => {
setAdmissionMethod(e.target.value);
form.setFieldsValue({ method: e.target.value });
// 如果切换到线上准入,清空下面相关字段
if (e.target.value === 'online') {
form.setFieldsValue({
rangePicker: undefined,
reviewers: undefined,
division: undefined,
supplierCompliance: undefined,
});
setSelectedReviewers({ selected: [], leader: null, });
setDivisionData([]);
}
};
// 自定义上传
const handleCustomRequest = async (options: any) => {
const { file, onSuccess, onError } = options;
try {
// 1. 调用你自己写的上传接口
const res = await uploadFile(file);
// 2. 可根据后端返回结构,给 fileList 项增加 url 或其他字段
const uploadedFile: UploadFile = {
...file,
status: 'done',
url: res?.data?.url || res?.url, // 按后端返回实际字段
name: res?.data?.name || file.name,
coscoAccessWorkAttachments: res
};
setFileList([uploadedFile]);
// 同步到 Form
form.setFieldsValue({ supplierCompliance: [uploadedFile] });
onSuccess && onSuccess(res, file);
message.success('上传成功');
} catch (e) {
onError && onError(e);
message.error('上传失败');
}
};
// 提交
const onFinish = async (values: any) => {
const finalPayload: {
coscoAccessWork: {
startTime: string;
endTime: string;
accessType: string;
accessWorkName: string;
deptId: string;
accessDesc: string;
};
categoryIds: string[];
supplierIds: string[];
coscoAccessUserls: { userId: string; deptId: string; isLeader: number }[];
coscoAccessItems: { itemName: string; reviewBy: string[] }[];
coscoAccessWorkAttachments: any;
attachmentsType: any;
} = {
coscoAccessWork: {
startTime: '',
endTime: '',
accessType: '',
accessWorkName: '',
deptId: '',
accessDesc: '',
},
categoryIds: [],
supplierIds: [],
coscoAccessUserls: [],
coscoAccessItems: [],
coscoAccessWorkAttachments: {},
attachmentsType: {},
};
//标题名称
finalPayload.coscoAccessWork.accessWorkName = values.accessWorkName;
//准入方式
finalPayload.coscoAccessWork.accessType = values.accessType;
//准入部门
finalPayload.coscoAccessWork.deptId = values.deptId;
// 准入说明
finalPayload.coscoAccessWork.accessDesc = values.accessDesc;
//品类选择
finalPayload.categoryIds = values.categoryIds;
//选择供应商
values.supplier.forEach((item: { id: string }) => {
finalPayload.supplierIds.push(item.id)
})
if (values.accessType === 'online') {
// 评审时间
if (values.rangePicker && values.rangePicker.length === 2) {
const [start, end] = values.rangePicker;
finalPayload.coscoAccessWork.startTime = start.format('YYYY-MM-DD HH:mm');
finalPayload.coscoAccessWork.endTime = end.format('YYYY-MM-DD HH:mm');
}
//选择评审人员
finalPayload.coscoAccessUserls = values.reviewers.selected.map((item: { userId: string, deptId: string, isLeader: number }) => {
return { userId: item.userId, deptId: item.deptId, isLeader: item.isLeader }
});
//评审分工
values.division.forEach((item: any) => {
const dataJson = {
itemName: item.itemName,
reviewBy: [] as string[],
};
Object.entries(item.reviewerChecks || {}).forEach(([key, value]) => {
dataJson.reviewBy.push(key);
});
finalPayload.coscoAccessItems.push(dataJson);
});
} else {
// 供应商符合性审查
finalPayload.coscoAccessWorkAttachments = values.supplierCompliance[0].response;
finalPayload.coscoAccessWorkAttachments.fileUrl = values.supplierCompliance[0].response.url;
}
const res = await add(finalPayload);
if (res?.success) {
message.success('创建成功');
form.resetFields();
setCheckedKeys([]);
setSelectedSuppliers([]);
setSelectedReviewers({ selected: [], leader: null, });
setDivisionData([]);
setAdmissionMethod('online');
onCancel();
} else {
message.error('创建失败');
}
};
//初始化
useEffect(() => {
if(visible) {
categoryTree().then((res) => {
const { code, data } = res;
if (code == 200) {
setCategoriesTreeData(data)
}
})
form.setFieldsValue({ method: 'online' });
}
}, [visible, form]);
return (
<Modal
title="发起准入"
visible={visible}
footer={null}
onCancel={() => {
form.resetFields();
setCheckedKeys([]);
setSelectedSuppliers([]);
setSelectedReviewers({ selected: [], leader: null, });
setDivisionData([]);
setAdmissionMethod('online');
onCancel();
}}
width="700px"
destroyOnClose
>
<Form
form={form}
onFinish={onFinish}
labelCol={{ span: 5 }}
wrapperCol={{ span: 19 }}
initialValues={{ accessType: 'online' }}
>
<Form.Item
label="标题名称"
name="accessWorkName"
rules={[{ required: true, message: '请输入标题名称' }]}
>
<Input placeholder="请输入标题名称" allowClear />
</Form.Item>
<Form.Item
label="准入部门"
name="deptId"
rules={[{ required: true, message: '请选择准入部门' }]}
>
<AccessDepartmentSelect />
</Form.Item>
<Form.Item
label="准入方式"
name="accessType"
rules={[{ required: true, message: '请选择准入方式' }]}
>
<Radio.Group onChange={onMethodChange} value={admissionMethod}>
<Radio value="online">线</Radio>
<Radio value="offline">线</Radio>
<Radio value="scattered">//</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="品类选择"
name="categoryIds"
rules={[{ required: true, message: '请选择准入品类' }]}
>
<Tree
checkable
selectable={false}
treeData={convertTreeData(categoriesTreeData)}
checkedKeys={checkedKeys}
onCheck={onCheck}
defaultExpandAll
style={{
maxHeight: 200,
overflowY: 'auto',
border: '1px solid #d9d9d9',
padding: 8,
borderRadius: 4,
}}
/>
</Form.Item>
<Form.Item
label="选择供应商"
name="supplier"
rules={[{ required: true, message: '请选择供应商' }]}
>
<Button onClick={() => setSupplierModalVisible(true)}>
</Button>
</Form.Item>
{admissionMethod === 'offline' && (
<>
<Form.Item
label="供应商符合性审查"
name="supplierCompliance"
required
rules={[
{
validator: (_, value) => {
if (admissionMethod === 'offline') {
if (!value || value.length === 0) {
return Promise.reject(new Error('请上传供应商符合性审查文件'));
}
}
return Promise.resolve();
},
},
]}
valuePropName="fileList"
getValueFromEvent={e => (Array.isArray(e) ? e : e && e.fileList)}
>
<Upload
fileList={fileList}
customRequest={handleCustomRequest}
beforeUpload={(file) => {
if (fileList.length >= 1) {
message.error('只能上传一个文件');
return Upload.LIST_IGNORE;
}
return true; // 允许进入 customRequest
}}
onChange={({ fileList: newFileList }) => {
setFileList(newFileList);
form.setFieldsValue({ supplierCompliance: newFileList });
}}
onRemove={(file) => {
const newList = fileList.filter(item => item.uid !== file.uid);
setFileList(newList);
form.setFieldsValue({ supplierCompliance: newList });
}}
accept=".pdf,.doc,.docx" // 可选,限制文件类型
maxCount={1} // 只允许一个
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
</Form.Item>
</>
)}
{admissionMethod === 'online' && (
<>
<Form.Item
label="评审时间"
name="rangePicker"
rules={[{ required: true, message: '请选择评审时间' }]}
>
<RangePicker
style={{ width: '300px' }}
showTime={{ format: 'HH:mm' }}
format="YYYY-MM-DD HH:mm"
/>
</Form.Item>
<Form.Item
label="选择评审人员"
name="reviewers"
rules={[{ required: true, message: '请选择评审人员' }]}
>
<Button onClick={() => setReviewerModalVisible(true)}>
</Button>
</Form.Item>
<Form.Item
label="评审分工"
name="division"
rules={[
{
required: true,
message: '请设置评审分工',
},
{
validator: (_, value) => {
if (!value || !Array.isArray(value) || value.length === 0) {
return Promise.reject(new Error('请设置评审分工'));
}
// 判断所有分工的checkbox数量总和是否≥1
let checkedCount = 0;
for (const row of value) {
checkedCount += Object.values(row.reviewerChecks || {}).filter(Boolean).length;
}
if (checkedCount < 1) {
return Promise.reject(new Error('至少分配一名评审人'));
}
return Promise.resolve();
}
}
]}
>
<Button onClick={() => {
if (selectedReviewers.selected.length !== 0) {
setDivisionModalVisible(true);
} else {
message.warning('请选择评审人员');
}
}}>
</Button>
</Form.Item>
</>
)}
{admissionMethod === 'scattered' && (
<>
<Form.Item
label="准入说明"
name="accessDesc"
rules={[{ required: true, message: '请输入准入说明' }]}
>
<Input.TextArea placeholder="请依照《集团供应商管理制度》填写准入说明" rows={2} maxLength={200} showCount />
</Form.Item>
<Form.Item
label="附件"
name="supplierCompliance"
valuePropName="fileList"
getValueFromEvent={e => (Array.isArray(e) ? e : e && e.fileList)}
>
<Upload
fileList={fileList}
customRequest={handleCustomRequest}
beforeUpload={(file) => {
if (fileList.length >= 1) {
message.error('只能上传一个文件');
return Upload.LIST_IGNORE;
}
return true; // 允许进入 customRequest
}}
onChange={({ fileList: newFileList }) => {
setFileList(newFileList);
form.setFieldsValue({ supplierCompliance: newFileList });
}}
onRemove={(file) => {
const newList = fileList.filter(item => item.uid !== file.uid);
setFileList(newList);
form.setFieldsValue({ supplierCompliance: newList });
}}
accept=".pdf,.doc,.docx" // 可选,限制文件类型
maxCount={1} // 只允许一个
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
</Form.Item>
</>
)}
<Form.Item wrapperCol={{ offset: 6 }}>
<Button type="primary" htmlType="submit" style={{ marginRight: 8 }}>
{admissionMethod === 'online' ? '确认' : admissionMethod === 'offline' ? '提交审核' : '提交'}
</Button>
<Button
onClick={() => {
form.resetFields();
setCheckedKeys([]);
setSelectedSuppliers([]);
setSelectedReviewers({ selected: [], leader: null, });
setDivisionData([]);
setAdmissionMethod('online');
}}
>
</Button>
</Form.Item>
</Form>
{/* 供应商选择 */}
<SupplierSelector
visible={supplierModalVisible}
onCancel={() => setSupplierModalVisible(false)}
onSelect={(selected) => {
setSelectedSuppliers(selected);
form.setFieldsValue({ supplier: selected });
setSupplierModalVisible(false);
const accessWorkName = form.getFieldValue('accessWorkName');
if(!accessWorkName) {
if(selected.length > 0 ) {
console.log(selected,'accessWorkName');
const suppliersName = `${selected[0].supplierType === 'ovs'? selected[0].nameEn:selected[0].name}${selected.length > 1? '等':'' }`
form.setFieldsValue({ 'accessWorkName': suppliersName });
}
}
}}
/>
{/* 评审人员选择 */}
<ReviewerSelector
visible={reviewerModalVisible}
onCancel={() => setReviewerModalVisible(false)}
selected={selectedReviewers.selected.map(i => i.userId)}
leader={selectedReviewers.leader?.userId}
onSelect={(result) => {
setSelectedReviewers(result);
form.setFieldsValue({ reviewers: result });
setReviewerModalVisible(false);
}}
/>
{/* 评审分工选择 */}
<DivisionModal
reviewerSelector={selectedReviewers}
visible={divisionModalVisible}
onCancel={() => setDivisionModalVisible(false)}
onSelect={(data) => {
setDivisionData(data);
form.setFieldsValue({ division: data });
setDivisionModalVisible(false);
}}
/>
</Modal>
);
};
export default CreateModal;