parent
bf15dbb29d
commit
9d69f174c6
13 changed files with 814 additions and 298 deletions
@ -1,9 +1,24 @@ |
||||
import axios from '../axios'; |
||||
|
||||
export function getList(obj: any){ |
||||
console.log("1",obj) |
||||
return axios({ |
||||
url: "/ex/statistics/page", |
||||
url: "/ex/statistics/page?page=" + obj.page + "&num=" + obj.num, |
||||
method: "post", |
||||
data: obj |
||||
}) |
||||
} |
||||
|
||||
export function getCustomerRetPage(obj: any, page: number, num: number){ |
||||
return axios({ |
||||
url: "/ex/statistics/customerRetPage?page=" + page + "&num=" + num, |
||||
method: "post", |
||||
data: obj |
||||
}) |
||||
} |
||||
|
||||
export function saveCustomerRet(obj: any){ |
||||
return axios({ |
||||
url: "/ex/statistics/submitCustomerRet", |
||||
method: "post", |
||||
data: obj |
||||
}) |
||||
|
@ -0,0 +1,318 @@ |
||||
import React, { useState, useEffect } from 'react'; |
||||
import {Table, Button, Spin, Space, Modal, Form, Upload, Image, notification} from 'antd'; |
||||
import TextArea from "antd/es/input/TextArea"; |
||||
import {getCustomerRetPage, saveCustomerRet} from "../../api/statistical"; |
||||
import {serverUrl} from "../../api/axios"; |
||||
|
||||
type NotificationPlacement = 'top' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; |
||||
type NotificationType = 'success' | 'info' | 'warning' | 'error'; |
||||
|
||||
interface CustomerRetentionProps { |
||||
customer: any; |
||||
} |
||||
|
||||
interface Enterprise { |
||||
key: string; |
||||
uploadTime: string; |
||||
description: string; |
||||
fileUid: string; |
||||
} |
||||
|
||||
interface FormData { |
||||
num: number; |
||||
page: number; |
||||
} |
||||
|
||||
interface FileRes { |
||||
fileId: string; |
||||
fileName: string; |
||||
} |
||||
|
||||
const CustomerRetention: React.FC<CustomerRetentionProps> = ({ customer }) => { |
||||
|
||||
// if (!customer) {
|
||||
// // @ts-ignore
|
||||
// const { history } = this.props;
|
||||
// history.push({
|
||||
// pathname: '/customer'
|
||||
// });
|
||||
// return (<div/>);
|
||||
// }
|
||||
|
||||
if (!customer) { |
||||
customer = { |
||||
customerId: 0, |
||||
customerName: 'asda' |
||||
} |
||||
} |
||||
|
||||
const [data, setData] = useState<Enterprise[]>([]); |
||||
const [loading, setLoading] = useState<boolean>(false); |
||||
const [query, setQuery] = useState<FormData | null>(null); |
||||
const [total, setTotal] = useState<number | null>(0); |
||||
const [fileList, setFileList] = useState<any[]>([]); |
||||
const [fileUrl, setFileUrl] = useState<string | null>(null); |
||||
|
||||
const [fileRes, setFileRes] = useState<FileRes | null>(null); |
||||
|
||||
const [form] = Form.useForm(); |
||||
|
||||
// Simulate data fetching on component mount
|
||||
useEffect(() => { |
||||
const query = {page: 1, num: 20}; |
||||
setQuery(query); |
||||
getListApi({}, query.page, query.num) |
||||
}, []); |
||||
|
||||
const columns = [ |
||||
{ |
||||
title: '序号', |
||||
dataIndex: 'key', |
||||
render: (text: string, record: any, index: number) => index + 1 |
||||
}, |
||||
{ |
||||
title: '说明描述', |
||||
dataIndex: 'description', |
||||
key: 'description', |
||||
}, |
||||
{ |
||||
title: '上传时间', |
||||
dataIndex: 'uploadTime', |
||||
key: 'uploadTime', |
||||
}, |
||||
{ |
||||
title: '附件', |
||||
dataIndex: 'file', |
||||
key: 'file', |
||||
sorter: true, |
||||
render: (text: any, record: { fileUid: string; }) => ( |
||||
<Space size="middle"> |
||||
<a onClick={() => showModal2(record.fileUid)}>服务声明扫描图片文件链接</a> |
||||
</Space> |
||||
), |
||||
}, |
||||
]; |
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false); |
||||
|
||||
const showModal = () => { |
||||
setIsModalOpen(true); |
||||
}; |
||||
|
||||
const handleOk = () => { |
||||
setLoading(true); |
||||
if (fileList.length == 0) { |
||||
openNotification('top', '请上传文件', 'info'); |
||||
return; |
||||
} |
||||
var formData = { |
||||
customerId: customer.customerId, |
||||
fileId: fileRes?.fileId, |
||||
fileName: fileRes?.fileName, |
||||
description: form.getFieldValue('description') |
||||
} |
||||
saveCustomerRet(formData).then(res => { |
||||
openNotification('top', '保存成功', 'success'); |
||||
const query = {page: 1, num: 20}; |
||||
setQuery(query); |
||||
getListApi({}, query.page, query.num) |
||||
setFileList([]); // 清空文件列表
|
||||
form.resetFields(['description']); // 重置description字段
|
||||
setIsModalOpen(false); |
||||
}).catch(() => { }) |
||||
}; |
||||
|
||||
const handleCancel = () => { |
||||
setFileList([]); // 清空文件列表
|
||||
form.resetFields(['description']); // 重置description字段
|
||||
setIsModalOpen(false); |
||||
}; |
||||
|
||||
const [isModalOpen2, setIsModalOpen2] = useState(false); |
||||
|
||||
const showModal2 = (fileUid: string) => { |
||||
console.log(fileUid) |
||||
setFileUrl('./ex/file/download?fileName=' + fileUid); |
||||
setIsModalOpen2(true); |
||||
}; |
||||
|
||||
const handleOk2 = () => { |
||||
debugger |
||||
const url = serverUrl + fileUrl; |
||||
const link = document.createElement('a'); |
||||
link.href = url; |
||||
// link.download = 'file.png';
|
||||
link.click(); |
||||
setFileUrl(null); |
||||
setIsModalOpen2(false); |
||||
}; |
||||
|
||||
const handleCancel2 = () => { |
||||
setFileUrl(null); |
||||
setIsModalOpen2(false); |
||||
}; |
||||
|
||||
// 多少每页
|
||||
const selectChange = (page: number, num: number) => { |
||||
const query = { page, num } |
||||
setQuery(query); |
||||
getListApi({}, query.page, query.num) |
||||
} |
||||
|
||||
// 条数切换
|
||||
const changePage = (current: number, pageSize?: number) => { |
||||
debugger |
||||
const query = { page: current, num: pageSize || 20 } |
||||
setTimeout(() => { |
||||
setQuery(query); |
||||
getListApi({}, query.page, query.num) |
||||
}, 0) |
||||
} |
||||
|
||||
// 列表接口
|
||||
const getListApi = (formData: object, page: number, num: number) => { |
||||
setLoading(true); |
||||
var formObj = JSON.parse(JSON.stringify(formData)); |
||||
formObj.customerId = customer.customerId; |
||||
getCustomerRetPage(formObj, page, num).then(res => { |
||||
setData(res.data.records); |
||||
setTotal(res.data.total); |
||||
setLoading(false); |
||||
}).catch(() => { }) |
||||
} |
||||
|
||||
const clickUpload = () => { |
||||
const uploadButton = document.querySelector('.ant-upload-drag-container') as HTMLDivElement; |
||||
if (uploadButton) { |
||||
uploadButton.click(); |
||||
} |
||||
} |
||||
|
||||
const [api, contextHolder] = notification.useNotification(); |
||||
|
||||
const openNotification = (placement: NotificationPlacement, msg: string, type: NotificationType) => { |
||||
api[type]({ |
||||
message: '提示', |
||||
description: msg, |
||||
placement, |
||||
}); |
||||
return false; |
||||
}; |
||||
|
||||
const beforeUpload = (file: File) => { |
||||
}; |
||||
|
||||
const handleChange = (info: any) => { |
||||
var file = info.file; |
||||
// 限制文件类型
|
||||
const isImage = file.type === 'image/jpeg' || file.type === 'image/png'; |
||||
if (!isImage) { |
||||
openNotification('top', '只支持上传JPG或PNG格式的文件','info'); |
||||
return; |
||||
} |
||||
setFileList(info.fileList); |
||||
}; |
||||
|
||||
// 自定义字段,包含 fileType
|
||||
const customRequest = async (options: any) => { |
||||
const { onSuccess, onError, file } = options; |
||||
const formData = new FormData(); |
||||
formData.append('file', file); |
||||
formData.append('fileType', 'CUSTOMER_RETENTION'); |
||||
try { |
||||
const response = await fetch('/ex/file/upload', { |
||||
method: 'POST', |
||||
body: formData, |
||||
}); |
||||
if (response.ok) { |
||||
const res = await response.json(); |
||||
setFileRes({ |
||||
fileId: res.data.id, |
||||
fileName: res.data.name |
||||
}) |
||||
onSuccess?.({}, file); |
||||
} else { |
||||
throw new Error('上传失败'); |
||||
} |
||||
} catch (error) { |
||||
onError?.(error); |
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<div className="container"> |
||||
{contextHolder} |
||||
<div> |
||||
<div> |
||||
<span style={{fontWeight: 600, fontSize: 16}}>{customer.customerName}</span> |
||||
<Button type="primary" style={{ marginBottom: 20, float: 'right' }} onClick={showModal}>新增留档</Button> |
||||
</div> |
||||
{loading ? ( |
||||
<Spin tip="Loading..." /> |
||||
) : ( |
||||
<Table |
||||
dataSource={data} |
||||
columns={columns} |
||||
rowKey="key" |
||||
loading={loading} |
||||
pagination={{ |
||||
total: total || 0, |
||||
current: query?.page || 1, |
||||
pageSize: query?.num || 10, |
||||
showQuickJumper: true, |
||||
showSizeChanger: true, |
||||
showTotal: total => `共 ${total} 条`, |
||||
onShowSizeChange: selectChange, |
||||
onChange: changePage |
||||
}} |
||||
/> |
||||
)} |
||||
</div> |
||||
<Modal title="信息留存" open={isModalOpen} onOk={handleOk} onCancel={handleCancel} centered okText="保存"> |
||||
<Form |
||||
form={form} |
||||
name="wrap" |
||||
labelCol={{ flex: '110px' }} |
||||
labelAlign="left" |
||||
labelWrap |
||||
wrapperCol={{ flex: 1 }} |
||||
colon={false} |
||||
style={{ maxWidth: 600 }} |
||||
> |
||||
<Form.Item label="信息上传" name="fileUpload" rules={[{ required: false }]}> |
||||
<Button type="primary" onClick={clickUpload}> |
||||
上传附件 |
||||
</Button> |
||||
<span style={{color: 'rgba(0, 0, 0, 0.45)'}}><span style={{color: 'red', marginLeft: 10}}>*</span>上传格式为jpg、png文件</span> |
||||
</Form.Item> |
||||
<div style={{marginBottom: 40, height: 120}}> |
||||
<Upload.Dragger |
||||
name="files" |
||||
beforeUpload={beforeUpload} |
||||
customRequest={customRequest} |
||||
onChange={handleChange} |
||||
fileList={fileList} |
||||
maxCount={1} |
||||
accept=".jpg,.jpeg,.png"> |
||||
<p className="ant-upload-hint">点击上方"选择文件"或将文件拖拽到此区域</p> |
||||
</Upload.Dragger> |
||||
</div> |
||||
<Form.Item label="说明描述" name="description" rules={[{ required: false }]}> |
||||
<TextArea rows={4} /> |
||||
</Form.Item> |
||||
</Form> |
||||
</Modal> |
||||
<Modal title="查看留存" open={isModalOpen2} onOk={handleOk2} onCancel={handleCancel2} centered okText="下载" width={350}> |
||||
<Image |
||||
width={300} height={320} |
||||
src={fileUrl ?? ''} |
||||
preview={{ |
||||
src: fileUrl ?? '', |
||||
}} |
||||
/> |
||||
</Modal> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default CustomerRetention; |
@ -1,34 +1,67 @@ |
||||
import React from 'react' |
||||
import React from 'react'; |
||||
import { Tabs } from 'antd'; |
||||
import type { TabsProps } from 'antd'; |
||||
import EnterpriseFile from 'views/statistical/enterpriseFile'; |
||||
import CustomerRetention from 'views/statistical/customerRetention'; |
||||
|
||||
import { useLocation } from 'react-router-dom'; |
||||
import {Breadcrumb, Descriptions} from 'antd'; |
||||
|
||||
const Detail = () => { |
||||
const onChange = (key: string) => { |
||||
console.log(key); |
||||
}; |
||||
|
||||
const App: React.FC = () => { |
||||
const location = useLocation(); |
||||
// @ts-ignore
|
||||
const { record } = location.state || {}; // Access the record passed from the previous page
|
||||
|
||||
if (!record) { |
||||
return <div>No record found!</div>; |
||||
} |
||||
|
||||
|
||||
return ( |
||||
<div> |
||||
{/*<Breadcrumb style={{ margin: '16px 16px' }}>*/} |
||||
{/* {breadcrumb.map((item, index) => (*/} |
||||
{/* <Breadcrumb.Item key={index}>{item}</Breadcrumb.Item>*/} |
||||
{/* ))}*/} |
||||
{/*</Breadcrumb>*/} |
||||
<h1>客户详情</h1> |
||||
<Descriptions title="客户信息"> |
||||
<Descriptions.Item label="客户编号">{record.customerNo}</Descriptions.Item> |
||||
<Descriptions.Item label="企业名称">{record.customerName}</Descriptions.Item> |
||||
<Descriptions.Item label="行业分类">{record.typePname}</Descriptions.Item> |
||||
<Descriptions.Item label="企业联系人">{record.contacts}</Descriptions.Item> |
||||
<Descriptions.Item label="联系电话">{record.contactsPhone}</Descriptions.Item> |
||||
</Descriptions> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
export default Detail; |
||||
|
||||
// 提取 record 数据,若没有则设为默认值
|
||||
const { record } = location.state as { record: any } || {}; |
||||
|
||||
console.log("record", record) |
||||
|
||||
const items: TabsProps['items'] = [ |
||||
{ |
||||
key: '1', |
||||
label: '基本信息', |
||||
children: <span style={{fontWeight: 600, fontSize: 16}}>基本信息</span>, |
||||
}, |
||||
{ |
||||
key: '2', |
||||
label: '保单', |
||||
children: '保单', |
||||
}, |
||||
{ |
||||
key: '3', |
||||
label: '事故预防服务', |
||||
children: '事故预防服务', |
||||
}, |
||||
{ |
||||
key: '4', |
||||
label: '风险辨识', |
||||
children: '风险辨识', |
||||
}, |
||||
{ |
||||
key: '5', |
||||
label: '隐患排查', |
||||
children: '隐患排查', |
||||
}, |
||||
{ |
||||
key: '6', |
||||
label: '理赔案件', |
||||
children: '理赔案件', |
||||
}, |
||||
{ |
||||
key: '7', |
||||
label: '一企一档', |
||||
children: <EnterpriseFile />, |
||||
}, |
||||
{ |
||||
key: '8', |
||||
label: '客户留存', |
||||
children: <CustomerRetention customer={record} />, |
||||
}, |
||||
]; |
||||
|
||||
return <Tabs defaultActiveKey="1" items={items} onChange={onChange} />; |
||||
}; |
||||
|
||||
export default App; |
||||
|
@ -0,0 +1,156 @@ |
||||
import React, {useState, useEffect} from 'react'; |
||||
import {Table, Button, Spin, Descriptions} from 'antd'; |
||||
import html2canvas from 'html2canvas'; |
||||
import { jsPDF } from 'jspdf'; |
||||
|
||||
interface Enterprise { |
||||
key: string; |
||||
customerNo: string; |
||||
customerName: string; |
||||
industry: string; |
||||
contacts: string; |
||||
phone: string; |
||||
} |
||||
|
||||
const EnterpriseFile: React.FC = () => { |
||||
// State to store data and loading state
|
||||
const [data, setData] = useState<Enterprise[]>([]); |
||||
const [loading, setLoading] = useState<boolean>(false); |
||||
|
||||
// Simulate data fetching on component mount
|
||||
useEffect(() => { |
||||
setLoading(true); |
||||
setTimeout(() => { |
||||
// Simulated response data
|
||||
const fetchedData: Enterprise[] = [ |
||||
{ key: '1', customerNo: '001', customerName: 'Company A', industry: 'Technology', contacts: 'John Doe', phone: '123-456-789' }, |
||||
{ key: '2', customerNo: '002', customerName: 'Company B', industry: 'Finance', contacts: 'Jane Smith', phone: '987-654-321' }, |
||||
// Add more data as needed
|
||||
]; |
||||
setData(fetchedData); |
||||
setLoading(false); |
||||
}, 1000); // Simulate a delay of 2 seconds
|
||||
}, []); |
||||
|
||||
const columns = [ |
||||
{ |
||||
title: '客户编号', |
||||
dataIndex: 'customerNo', |
||||
key: 'customerNo', |
||||
}, |
||||
{ |
||||
title: '企业名称', |
||||
dataIndex: 'customerName', |
||||
key: 'customerName', |
||||
}, |
||||
{ |
||||
title: '行业分类', |
||||
dataIndex: 'industry', |
||||
key: 'industry', |
||||
}, |
||||
{ |
||||
title: '企业联系人', |
||||
dataIndex: 'contacts', |
||||
key: 'contacts', |
||||
}, |
||||
{ |
||||
title: '联系电话', |
||||
dataIndex: 'phone', |
||||
key: 'phone', |
||||
}, |
||||
]; |
||||
|
||||
const items = [ |
||||
{ |
||||
key: '1', |
||||
label: 'UserName', |
||||
children: 'Zhou Maomao', |
||||
}, |
||||
{ |
||||
key: '2', |
||||
label: 'Telephone', |
||||
children: '1810000000', |
||||
}, |
||||
{ |
||||
key: '3', |
||||
label: 'Live', |
||||
children: 'Hangzhou, Zhejiang', |
||||
}, |
||||
{ |
||||
key: '4', |
||||
label: 'Address', |
||||
span: 2, |
||||
children: 'No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China', |
||||
}, |
||||
{ |
||||
key: '5', |
||||
label: 'Remark', |
||||
children: 'empty', |
||||
}, |
||||
]; |
||||
|
||||
const handleCapture = () => { |
||||
const element = document.querySelector('.main-customer-htm'); |
||||
if (element) { |
||||
html2canvas(element as HTMLElement, { |
||||
useCORS: true, // Allow CORS for cross-origin resources
|
||||
}).then((canvas) => { |
||||
// Convert the canvas to image data
|
||||
const imgData = canvas.toDataURL('image/png'); |
||||
|
||||
// Create a new jsPDF instance
|
||||
const pdf = new jsPDF(); |
||||
|
||||
// Add the image to the PDF (at position (10, 10) with size (pdf width - 20, auto))
|
||||
pdf.addImage(imgData, 'PNG', 10, 10, pdf.internal.pageSize.width - 20, canvas.height * (pdf.internal.pageSize.width - 20) / canvas.width); |
||||
|
||||
// Save the PDF
|
||||
pdf.save('企业档案.pdf'); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<div className="container"> |
||||
<div> |
||||
<Button type="primary" style={{ marginBottom: 20, float: 'right' }} onClick={handleCapture}>PDF下载</Button> |
||||
</div> |
||||
<div className="main-customer-htm"> |
||||
<Descriptions title="企业基本信息" layout="vertical" bordered> |
||||
{items.map(item => ( |
||||
<Descriptions.Item label={item.label} key={item.key}> |
||||
{item.children} |
||||
</Descriptions.Item> |
||||
))} |
||||
</Descriptions> |
||||
<br /> |
||||
{loading ? ( |
||||
<Spin tip="Loading..." /> |
||||
) : ( |
||||
<Table |
||||
dataSource={data} |
||||
columns={columns} |
||||
rowKey="key" |
||||
pagination={false} |
||||
/> |
||||
)} |
||||
<br /> |
||||
<Table |
||||
dataSource={data} |
||||
columns={columns} |
||||
rowKey="key" |
||||
pagination={false} |
||||
/> |
||||
<br /> |
||||
<Table |
||||
dataSource={data} |
||||
columns={columns} |
||||
rowKey="key" |
||||
pagination={false} |
||||
/> |
||||
</div> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default EnterpriseFile; |
Loading…
Reference in new issue