454 lines
18 KiB
TypeScript
454 lines
18 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 moment from 'moment';
|
||
|
//组件
|
||
|
import SupplierSelector from './SupplierSelector';
|
||
|
import ReviewerSelector from './ReviewerSelector';
|
||
|
import DivisionModal from './DivisionModal';
|
||
|
// 请求
|
||
|
import { categoryTree, add, uploadFile } from '../services';
|
||
|
const { Option } = Select;
|
||
|
const { RangePicker } = DatePicker;
|
||
|
//selected 类型
|
||
|
interface Reviewer {
|
||
|
key: string;
|
||
|
name: string;
|
||
|
id: string;
|
||
|
dept: 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(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: '',
|
||
|
endTime: '',
|
||
|
accessType: '',
|
||
|
accessWorkName: '',
|
||
|
deptId: '',
|
||
|
},
|
||
|
categoryIds: [],
|
||
|
supplierIds: [],
|
||
|
coscoAccessUserls: [],
|
||
|
coscoAccessItems: [],
|
||
|
coscoAccessWorkAttachments: {},
|
||
|
};
|
||
|
|
||
|
//标题名称
|
||
|
finalPayload.coscoAccessWork.accessWorkName = values.accessWorkName;
|
||
|
//准入方式
|
||
|
finalPayload.coscoAccessWork.accessType = values.accessType;
|
||
|
//准入部门
|
||
|
finalPayload.coscoAccessWork.deptId = values.deptId;
|
||
|
//品类选择
|
||
|
finalPayload.categoryIds = values.categoryIds;
|
||
|
//选择供应商
|
||
|
if(values.supplier.length != 0) {
|
||
|
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
|
||
|
}
|
||
|
|
||
|
console.log( finalPayload, values);
|
||
|
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(() => {
|
||
|
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 }}
|
||
|
>
|
||
|
<Form.Item
|
||
|
label="标题名称"
|
||
|
name="accessWorkName"
|
||
|
rules={[{ required: true, message: '请输入标题名称' }]}
|
||
|
>
|
||
|
<Input placeholder="请输入标题名称" allowClear />
|
||
|
</Form.Item>
|
||
|
<Form.Item
|
||
|
label="准入部门"
|
||
|
name="deptId"
|
||
|
rules={[{ required: true, message: '请选择准入部门' }]}
|
||
|
>
|
||
|
<Select placeholder="请选择部门">
|
||
|
<Option value="DEPT001">部门A</Option>
|
||
|
<Option value="DEPT001">部门B</Option>
|
||
|
</Select>
|
||
|
</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.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 !== 'online' && (
|
||
|
<>
|
||
|
<Form.Item
|
||
|
label="供应商符合性审查"
|
||
|
name="supplierCompliance"
|
||
|
rules={[
|
||
|
{
|
||
|
validator: (_, value) => {
|
||
|
if (admissionMethod === 'online') {
|
||
|
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: '请设置评审分工' }]}
|
||
|
>
|
||
|
<Button onClick={() => {
|
||
|
if (selectedReviewers.selected.length != 0) {
|
||
|
setDivisionModalVisible(true)
|
||
|
} else {
|
||
|
message.warning('请选择评审人员');
|
||
|
}
|
||
|
}}>
|
||
|
设置评审分工
|
||
|
</Button>
|
||
|
</Form.Item>
|
||
|
</>
|
||
|
)}
|
||
|
|
||
|
<Form.Item wrapperCol={{ offset: 6 }}>
|
||
|
<Button type="primary" htmlType="submit" style={{ marginRight: 8 }}>
|
||
|
提交
|
||
|
</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);
|
||
|
}}
|
||
|
/>
|
||
|
|
||
|
<ReviewerSelector
|
||
|
visible={reviewerModalVisible}
|
||
|
onCancel={() => setReviewerModalVisible(false)}
|
||
|
onSelect={(result) => {
|
||
|
console.log(result,'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;
|