供应商联系人

This commit is contained in:
孙景学
2025-07-17 15:17:49 +08:00
parent 8e921b73d4
commit 5c505150e5
7 changed files with 577 additions and 112 deletions

View File

@ -0,0 +1,157 @@
import React, { useEffect, useState } from 'react';
import { Modal, Form, Input, message, Row, Col, Descriptions } from 'antd';
import { getDictList } from '@/servers/api/dicts';
import { coscoSupplierUserView, coscoSupplierUserAdd, coscoSupplierUserEdit } from '../services';
import { getRegionTree } from '@/servers/api/register';
import type { DictItem } from '@/servers/api/dicts';
// 地区字段转换
function convertToCascaderOptions(data: any[]): any[] {
return data.map(item => ({
label: item.name,
value: item.id,
children: item.children && item.children.length > 0 ? convertToCascaderOptions(item.children) : undefined,
}));
}
interface props {
visible: boolean;
onOk: () => void;
onCancel: () => void;
initialValues?: any;
readOnly?: boolean;
}
interface viewDataData {
id?: string | null;
contactsName?: string;
contactsPhone?: string;
contactsEmail?: string;
}
const InvoiceFormModal: React.FC<props> = ({
visible,
onOk,
onCancel,
initialValues,
readOnly = false,
}) => {
const userId = sessionStorage.getItem('userId') || '';
// 新增与修改
const [form] = Form.useForm();
//查看
const [viewData, setViewData] = useState<viewDataData>({});
useEffect(() => {
if (visible) {
if (initialValues) {
coscoSupplierUserView(initialValues.id).then((res) => {
const { code, data } = res;
if (code === 200) {
const fields = {
...data,
id: data.id ? data.id : null,
address: [
Number(data.nation),
Number(data.province),
Number(data.city),
]
};
console.log(fields);
form.setFieldsValue(fields);
setViewData(fields);
}
});
} else {
form.resetFields();
}
}
}, [visible, initialValues]);
// 提交
const handleFinish = async () => {
try {
const values = await form.validateFields();
const payload = {
...values,
supplierId: userId,
};
if (!values.id) {
coscoSupplierUserAdd(payload).then((res) => {
if (res.code == 200) {
message.success('新增成功');
onOk();
}
})
} else {
coscoSupplierUserEdit(payload).then((res) => {
if (res.code == 200) {
message.success('修改成功');
onOk();
}
})
}
} catch (error) {
console.error('表单校验失败:', error);
}
};
return (
<Modal
title={readOnly ? '查看' : initialValues ? '修改' : '新增'}
visible={visible}
onCancel={onCancel}
onOk={readOnly ? onCancel : handleFinish}
footer={readOnly ? null : undefined}
destroyOnClose
width={600}
>
{readOnly ? (
<Descriptions
column={1}
bordered
size="middle"
labelStyle={{ width: 160 }}
>
<Descriptions.Item label="联系人姓名">{viewData.contactsName}</Descriptions.Item>
<Descriptions.Item label="联系人手机号">{viewData.contactsPhone}</Descriptions.Item>
<Descriptions.Item label="联系人邮箱">{viewData.contactsEmail}</Descriptions.Item>
</Descriptions>
) : (
<Form form={form} labelCol={{ flex: '120px' }} wrapperCol={{ flex: 1 }}>
<Row gutter={24}>
<Col span={24}>
<Form.Item name="contactsName" label="联系人姓名" rules={[{ required: true }]}>
<Input />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="contactsPhone" label="联系人手机号"
rules={[
{ required: true, message: '请输入联系人手机号码' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' },
]}>
<Input />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="contactsEmail" label="联系人邮箱"
rules={[
{ type: 'email', message: '请输入有效的电子邮箱' },
{ required: true, message: '请输入电子邮箱' },
]}>
<Input />
</Form.Item>
</Col>
<Form.Item name="id" noStyle>
<Input type="hidden" />
</Form.Item>
</Row>
</Form>
)}
</Modal>
);
};
export default InvoiceFormModal;

View File

@ -1,64 +1,113 @@
import React, { useEffect, useState } from 'react';
import { Table } from 'antd';
import { Table, Button, message, Switch, Popconfirm } from 'antd';
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import { coscoSupplierBase } from '../services';
import { getCoscoSupplierUserPage, editType, coscoSupplierUserDel } from '../services';
import { useIntl } from 'umi';
import ContactsInfoFormModal from './ContactsInfoFormModal';
// 联系人信息接口
interface Contact {
interface getCoscoSupplierUser {
id: string;
name: string;
department: string;
position: string;
mobile: string;
phone: string;
email: string;
updateTime: string;
attachmentsType?: string;
fileName?: string;
filePath?: string;
fileSize?: string;
fileType?: string;
fileUrl?: string;
supplierId?: string;
certificateUrl?: string;
delFlag: string;
type: string;
}
interface Props {
viewType?: boolean;
record?: string;
}
const ContactsInfoTab: React.FC<Props> = (props) => {
const OtherAttachmentsTab: React.FC<Props> = (props) => {
const userId = sessionStorage.getItem('userId') || '';
const { viewType = false, record = userId } = props;
//语言切换
const intl = useIntl();
const [data, setData] = useState<Contact[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
total: 0,
});
//列表渲染数据
const [data, setData] = useState<getCoscoSupplierUser[]>([]);
//列表加载
const [loading, setLoading] = useState(false);
//列表分页
const [pagination, setPagination] = useState<TablePaginationConfig>({ current: 1, pageSize: 10, total: 0 });
const fetchContacts = async (page: number = 1, pageSize: number = 10) => {
//列表方法
const getList = async (pageNo = 1, pageSize = 10) => {
setLoading(true);
try {
const { code, data } = await getCoscoSupplierUserPage({ pageNo, pageSize, supplierId: record });
if (code === 200) {
setData(data.records);
setPagination({ current: pageNo, pageSize, total: data.total });
}
} finally {
setLoading(false);
}
};
coscoSupplierBase(record).then((res) => {
//增改查
const [formVisible, setFormVisible] = useState(false);
const [editingRecord, setEditingRecord] = useState<getCoscoSupplierUser | null>(null);
const [isViewMode, setIsViewMode] = useState(false);
const handleFormSubmit = () => {
setFormVisible(false);
getList();
};
//新增
const handleAdd = () => {
setEditingRecord(null);
setIsViewMode(false);
setFormVisible(true);
};
// 修改
const handleEdit = (record: getCoscoSupplierUser) => {
setEditingRecord(record);
setIsViewMode(false);
setFormVisible(true);
};
// 查看
const handleView = (record: getCoscoSupplierUser) => {
setEditingRecord(record);
setIsViewMode(true);
setFormVisible(true);
};
//删除
const handleDel = (record: getCoscoSupplierUser) => {
coscoSupplierUserDel(record.id).then((res) => {
if (res.code == 200) {
setData([res.data.coscoSupplierBase])
message.success('删除成功');
getList();
}
})
};
//是否为主联系人
const handleObsoleteChange = async (checked: boolean, id: string) => {
// 调用你的作废接口
const res = await editType({ id, supplierId: record });
if (res.code === 200) {
message.success('操作成功');
getList(pagination.current, pagination.pageSize); // 刷新列表
} else {
message.error('操作失败');
}
}
//初始化
useEffect(() => {
if (record) {
fetchContacts();
getList();
}
}, [record]);
const handleTableChange = (pagination: TablePaginationConfig) => {
fetchContacts(pagination.current!, pagination.pageSize!);
};
const columns: ColumnsType<Contact> = [
const columns: ColumnsType<getCoscoSupplierUser> = [
{
title: intl.formatMessage({ id: 'page.workbench.contacts.index' }),
dataIndex: 'index',
key: 'index',
width: 80,
align: 'center',
render: (_: any, __: any, index: number) =>
(pagination.current! - 1) * pagination.pageSize! + index + 1,
title: intl.formatMessage({ id: 'page.workbench.attachments.index' }),
render: (_: any, __: any, index: number) => index + 1,
width: 60,
},
{
title: '联系人',
@ -75,22 +124,79 @@ const ContactsInfoTab: React.FC<Props> = (props) => {
dataIndex: 'contactsEmail',
key: 'contactsEmail',
},
];
{
title: '是否为主联系人',
dataIndex: 'type',
align: 'center',
width: 180,
render: (value, record: getCoscoSupplierUser) => {
let checkedType = value === '1' ? true : false;
return (
<Switch
checked={checkedType}
disabled={viewType}
onChange={(checked) => handleObsoleteChange(checked, record.id)}
/>
)
},
},
...(viewType ? [] : [
{
title: 'page.workbench.attachments.action',
dataIndex: 'option',
width: 140,
render: (_: any, record: getCoscoSupplierUser) => {
return (
<>
<a style={{ marginRight: 8 }} onClick={() => handleView(record)}></a>
<a style={{ marginRight: 8 }} onClick={() => handleEdit(record)}></a>
{record.type === '0' && (
<Popconfirm
title="确定要删除该联系人吗?"
onConfirm={() => handleDel(record)}
>
<a ></a>
</Popconfirm>
)}
</>
)
}
}
])
];
return (
<div style={{ padding: '0 30px 0 0' }}>
<div style={{ marginBottom: 16 }}>
{!viewType && (
<Button type="primary" onClick={handleAdd}></Button>
)}
</div>
<Table
className="custom-table"
columns={columns}
dataSource={data}
rowKey="id"
loading={loading}
columns={columns.map(column => ({
...column,
title: intl.formatMessage({ id: column.title as string })
}))}
dataSource={data}
pagination={pagination}
onChange={handleTableChange}
bordered
loading={loading}
onChange={(pagination) => getList(pagination.current!, pagination.pageSize!)}
/>
<ContactsInfoFormModal
visible={formVisible}
onOk={handleFormSubmit}
onCancel={() => setFormVisible(false)}
initialValues={editingRecord || undefined}
readOnly={isViewMode}
/>
</div>
);
};
export default ContactsInfoTab;
export default OtherAttachmentsTab;

View File

@ -231,8 +231,49 @@ export const updateSupplierBase = (data: updateSupplierBase) => request.post('/c
/**
* 联系人分页列表
*/
interface getCoscoSupplierUserPage {
pageNo: number;
pageSize: number;
supplierId?: string;
}
export const getCoscoSupplierUserPage = (data: getCoscoSupplierUserPage) => request.post('/coscoSupplierUser/getPage', { data });
/**
* 附件详情
*/
export const coscoSupplierUserView = (id: string) => request.get(`/coscoSupplierUser/${id}`);
/**
* 附件新增
*/
interface coscoSupplierUserAdd {
id?: string;
attachmentsType?: string;
fileName?: string;
filePath?: string;
fileSize?: string;
fileType?: string;
fileUrl?: string;
supplierId?: string;
delFlag?: string;
[property: string]: any;
}
export const coscoSupplierUserAdd = (data: coscoSupplierUserAdd) => request.post('/coscoSupplierUser/add', { data });
/**
* @param data
* @returns
* 附件修改
*/
export const coscoSupplierUserEdit = (data: coscoSupplierUserAdd) => request.put('/coscoSupplierUser/edit', { data });
interface editType {
supplierId:string;
id:string;
}
export const editType = (data: editType) => request.post('/coscoSupplierUser/editType', { data });
export const coscoSupplierUserDel = (id: string) => request.delete(`/coscoSupplierUser/${id}`);

View File

@ -4,7 +4,7 @@ import { UploadOutlined } from '@ant-design/icons';
import { categoryTree, uploadFile, superiorLockList, getSupplierPage, library, libraryData } from '../services';
import type { UploadFile } from 'antd/es/upload/interface';
import AccessDepartmentSelect from '@/components/AccessDepartmentSelect';
import { getregionInternational } from '@/servers/api/register';
import { getregionInternational, getAllAreaList } from '@/servers/api/register';
const { Option } = Select;
const approveTypeOptions = [
@ -136,7 +136,7 @@ const CategoryAddModal: React.FC<Props> = ({ visible, onCancel, onSuccess }) =>
form.resetFields();
}
init(visible)
getregionInternational().then(res => {
getAllAreaList().then(res => {
if (res.code === 200) {
setRegionOptions(res.data);
}
@ -282,7 +282,7 @@ const CategoryAddModal: React.FC<Props> = ({ visible, onCancel, onSuccess }) =>
<Select placeholder="请选择区域">
{
regionOptions.map((item) => {
return <Option value={item.id}>{item.name}</Option>
return <Option value={item.id}>{item.dicName}</Option>
})
}
</Select>
@ -365,7 +365,7 @@ const CategoryAddModal: React.FC<Props> = ({ visible, onCancel, onSuccess }) =>
</Form.Item>
);
}}
</Form.Item> {/* ✅ 外层 Form.Item 正确闭合 */}
</Form.Item>

View File

@ -1,7 +1,18 @@
import React, { useEffect, useState } from 'react';
import { Modal, Table, Button, Checkbox, Popconfirm, message, Descriptions, Spin } from 'antd';
import { getSupplierPage, detail, apply } from "../services";
import { Modal, Table, Button, Checkbox, Form, Select, message, Descriptions, Tooltip, Upload } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import type { UploadFile } from 'antd/es/upload/interface';
import { UploadOutlined } from '@ant-design/icons';
//接口
import { getSupplierPage, detail, apply, uploadFile } from "../services";
//统一列表分页
import tableProps from '@/utils/tableProps'
import { useSupplierDetailModal } from '@/components/SupplierDetailModalContext/SupplierDetailModalContext';
const approveTypeOptions = [
{ label: '是', value: 'online' },
{ label: '否', value: 'offline' },
];
// 供应商类型
interface Supplier {
id: string;
@ -31,6 +42,7 @@ const SupplierAddModal: React.FC<{
onCancel: () => void;
onSuccess: () => void;
}> = ({ visible, storeId, onCancel, onSuccess }) => {
const [form] = Form.useForm();
// 供应商数据
const [suppliers, setSuppliers] = useState<Supplier[]>([]);
// 已勾选的供应商 id
@ -40,6 +52,9 @@ const SupplierAddModal: React.FC<{
// loading
const [loading, setLoading] = useState(false);
const supplierDetailModal = useSupplierDetailModal();
//供应商符合性审查
const [fileList, setFileList] = useState<UploadFile<any>[]>([]);
// 分页
const [pagination, setPagination] = useState({
current: 1,
@ -99,31 +114,89 @@ const SupplierAddModal: React.FC<{
};
// “移除”操作
const handleRemove = (id: string) => {
setSuppliers(suppliers.map(s =>
s.id === id ? { ...s, inStore: false } : s
));
message.success('移除成功');
};
// const handleRemove = (id: string) => {
// setSuppliers(suppliers.map(s =>
// s.id === id ? { ...s, inStore: false } : s
// ));
// message.success('移除成功');
// };
// 确认入库
const handleOk = () => {
const handleOk = async () => {
if (selectedIds.length === 0) {
message.warning('请选择入库供应商');
return
}
apply({ categoryLibraryId: storeId ,supplierIds: selectedIds })
const values = await form.validateFields();
if(values.approveType === 'offline') {
if (values.attachments.length === 0) {
message.warning('请上传附件');
return
}
} else if(!values.approveType) {
message.warning('请选择签报');
return
}
//
if(values.attachments && values.attachments.length > 0) {
values.coscoCategoryLibrarAttachments = {
...values.attachments[0].response,
fileUrl: values.attachments[0].response.url,
attachmentsType: 'accessory'
}
}
apply({ categoryLibraryId: storeId, supplierIds: selectedIds, ...values })
setSelectedIds([]);
onSuccess && onSuccess();
};
// 自定义上传
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 columns: ColumnsType<Supplier> = [
{ title: '序号', dataIndex: 'id', align: 'center', width: 60, render: (t: any, r: any, i: number) => (pagination.current - 1) * pagination.pageSize + i + 1 },
{ title: '供应商名称', dataIndex: 'name', align: 'center', ellipsis: true },
{ title: '境内/境外', dataIndex: 'supplierType', align: 'center',
render: (ext: any, record: any) => (<span>{`${record.supplierCategory === 'dvs'? '境内企业':'境外企业'}`}</span>) },
{ title: '统一社会信用代码', dataIndex: 'socialCreditCode', align: 'center', ellipsis: true },
{
title: "供应商名称", dataIndex: "name", align: "left",
width: 200,
ellipsis: true,
render: (dom, record) => {
return (
<Tooltip>
<a onClick={() => supplierDetailModal?.(record.id)}>{record.name || ''}</a>
</Tooltip>
)
}
},
{
title: '境内/境外', dataIndex: 'supplierType', align: 'center',
render: (ext: any, record: any) => (<span>{`${record.supplierCategory === 'dvs' ? '境内企业' : '境外企业'}`}</span>)
},
{ title: '统一社会信用代码', dataIndex: 'unifiedCode', align: 'center', ellipsis: true },
{ title: '企业类型', dataIndex: 'categoryName', align: 'center', ellipsis: true },
{
@ -134,14 +207,6 @@ const SupplierAddModal: React.FC<{
return (
<>
<span style={{ color: '#aaa' }}></span>
{/* <Popconfirm
title="确认移除该供应商?"
onConfirm={() => handleRemove(record.id)}
okText="确认"
cancelText="取消"
>
<Button size="small" type="link" danger style={{ marginLeft: 6 }}>移除</Button>
</Popconfirm> */}
</>
);
}
@ -197,6 +262,100 @@ const SupplierAddModal: React.FC<{
</Descriptions.Item>
</Descriptions>
</div>
{/* 供应商表格 */}
<div style={{
display: 'flex',
alignItems: 'center',
padding: '8px 0 8px 0',
fontSize: 16,
fontWeight: 500,
}}>
<span></span>
</div>
<Form
form={form}
layout="horizontal"
colon={false}
onValuesChange={(changedValues) => {
if ('approveType' in changedValues) {
form.validateFields(['attachments']); // 手动校验附件
}
}}
>
<Form.Item
label="是否已走OA签报"
name="approveType"
rules={[{ required: true, message: '请选择否已走OA签报' }]}
required
>
<Select
placeholder="请选择否已走OA签报"
style={{ width: 260 }}
options={approveTypeOptions}
/>
</Form.Item>
<Form.Item shouldUpdate={(prev, curr) => prev.approveType !== curr.approveType}>
{() => {
const isAttachmentRequired = form.getFieldValue('approveType') === 'offline';
return (
<Form.Item
label={
<span>
{isAttachmentRequired && <span style={{ color: 'red' }}>*</span>}
OA签报证明文件
</span>
}
name="attachments"
rules={[
{
validator: (_, value) => {
if (isAttachmentRequired && (!value || value.length === 0)) {
return Promise.reject('请上传OA签报附件');
}
return Promise.resolve();
},
},
]}
valuePropName="fileList"
getValueFromEvent={(e) => (Array.isArray(e) ? e : e?.fileList)}
>
<Upload
fileList={fileList}
customRequest={handleCustomRequest}
beforeUpload={(file) => {
if (fileList.length >= 1) {
message.error('只能上传一个文件');
return Upload.LIST_IGNORE;
}
return true;
}}
onChange={({ fileList: newFileList }) => {
setFileList(newFileList);
form.setFieldsValue({
supplierCompliance: newFileList,
attachments: newFileList,
});
}}
onRemove={(file) => {
const newList = fileList.filter(item => item.uid !== file.uid);
setFileList(newList);
form.setFieldsValue({
supplierCompliance: newList,
attachments: newList,
});
}}
accept=".pdf,.doc,.docx"
maxCount={1}
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
</Form.Item>
);
}}
</Form.Item>
</Form>
{/* 供应商表格 */}
<div style={{
display: 'flex',
@ -207,23 +366,18 @@ const SupplierAddModal: React.FC<{
}}>
<span></span>
</div>
<Spin spinning={loading}>
<Table
columns={columns}
dataSource={suppliers}
loading={loading}
rowKey="id"
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
showSizeChanger: true,
showQuickJumper: true,
onChange: (page, pageSize) => handleTableChange({ current: page, pageSize }),
pagination={{ ...tableProps.pagination, total: pagination.total }}
onChange={(pagination) => {
handleTableChange({ current: pagination.current!, pageSize: pagination.pageSize! })
}}
style={{ flex: 1, minHeight: 0 }}
scroll={{ y: 'calc(100vh - 650px)' }}
/>
</Spin>
</Modal>
);
};

View File

@ -35,3 +35,9 @@ export async function getregionInternational(){
method: 'GET',
})
}
export async function getAllAreaList(){
return request('/api/cosco/dictProject/getAllList/area', {
method: 'GET',
})
}

View File

@ -17,7 +17,8 @@ declare namespace API {
label: string;
value: string;
id: string;
name: string;
name?: string;
dicName?: string;
children?: RegionOption[];
}