541 lines
22 KiB
TypeScript
541 lines
22 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Modal, Form, 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 AccessDepartmentSelect from '@/components/AccessDepartmentSelect';
|
|
// 请求
|
|
import { categoryTree, add, uploadFile } from '../services';
|
|
|
|
|
|
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 [accessWorkNameEdited, setAccessWorkNameEdited] = useState(false);
|
|
//品类选择
|
|
const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
|
|
// 选择评审人员
|
|
const [selectedReviewers, setSelectedReviewers] = useState<ReviewerSelectorData>({
|
|
selected: [],
|
|
leader: null,
|
|
});
|
|
//准入方式
|
|
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 }); // 只存叶子到表单
|
|
};
|
|
// 选择准入方式
|
|
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, });
|
|
}
|
|
};
|
|
|
|
// 自定义上传
|
|
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([]);
|
|
setSelectedReviewers({ selected: [], leader: null, });
|
|
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([]);
|
|
setSelectedReviewers({ selected: [], leader: null, });
|
|
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
|
|
onChange={e => {
|
|
if (e.target.value) {
|
|
setAccessWorkNameEdited(true);
|
|
} else {
|
|
setAccessWorkNameEdited(false);
|
|
}
|
|
}} />
|
|
</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([]);
|
|
setSelectedReviewers({ selected: [], leader: null, });
|
|
setAdmissionMethod('online');
|
|
}}
|
|
>
|
|
重置
|
|
</Button>
|
|
</Form.Item>
|
|
</Form>
|
|
{/* 供应商选择 */}
|
|
<SupplierSelector
|
|
visible={supplierModalVisible}
|
|
onCancel={() => setSupplierModalVisible(false)}
|
|
onSelect={(selected) => {
|
|
form.setFieldsValue({ supplier: selected });
|
|
setSupplierModalVisible(false);
|
|
if (!accessWorkNameEdited) {
|
|
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 });
|
|
} else {
|
|
form.setFieldsValue({ 'accessWorkName': '' });
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
{/* 评审人员选择 */}
|
|
<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) => {
|
|
form.setFieldsValue({ division: data });
|
|
setDivisionModalVisible(false);
|
|
}}
|
|
/>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default CreateModal;
|