You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

651 lines
18 KiB

import {getDetailList, delDetail, getDetail, getPointByUuid, getPointsById, getAlignPointById} from '../../../services/measure/analysis'
import { useState, useEffect, useRef } from 'react'
import {message, Button, type TableColumnsType, type TableProps, Modal, Table, Pagination, Input, Select, Switch, Progress, Flex, Spin } from 'antd';
import type { AnalysisReport, DetailTable, SearchParams } from "../../../services/measure/type";
import { ExclamationCircleFilled, CheckCircleOutlined, WarningOutlined } from '@ant-design/icons';
import { AnalysisData, BenchmarkShape, MeasurementCanvasRef } from './konva/MeasurementCanvas';
import MeasurementCanvas from "./konva/MeasurementCanvas";
import {
dictionaryListService,
kljUpload
} from "../../../services/ktj/org";
import {
getReport
} from "../../../services/measure/analysis";
import { getBaseRecordPointSetByCode } from "../../../services/track/trackShape"
import { extraDescType, KTJ_BASE_TYPE } from '../../../services/ktjTypes';
import { GX_CODE } from '../../../constant';
import { exportFile, padNumber } from '../../../utils';
export default function MeasureDetail() {
useEffect(()=>{
queryDictionaryList()
const params = {
pageSize,
pageNum,
}
getDetailDataList(params)
}, [])
const [selectRows, setSelectedRow] = useState<DetailTable[]>([])
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const rowSelection = {
selectedRowKeys,
onChange: (selectedRowKeys: React.Key[], selectedRows: DetailTable[]) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setSelectedRow(selectedRows)
setSelectedRowKeys(selectedRowKeys)
},
getCheckboxProps: (record: DetailTable) => ({
name: record.name,
}),
};
const columns: TableColumnsType<DetailTable> = [
{
title: '序号',
dataIndex: 'seq',
render:(_, record, index)=>{
return index + 1
}
},
{
title: '测量名称',
dataIndex: 'name',
},
{
title: '数据来源',
dataIndex: 'dataSource',
render:(_, record)=>{
const dataSource = record.dataSource
return dataSource === 'DCDC' ? '道岔调查' : '线路调查'
}
},
{
title: '轨型',
dataIndex: 'railSize',
},
{
title: '当天测量序号',
dataIndex: 'todayNumber',
render: (_, record) => (
padNumber(record.todayNumber, 4)
)
},
{
title: '创建者',
dataIndex: 'operator',
},
{
title: '时间',
dataIndex: 'createTime',
},
{
title: '操作',
dataIndex: 'op',
width:180,
align:'center',
render:(_, record)=>{
return <div>
<Button type="link" onClick={()=>onShowDetail(record)}></Button>
<Button type="link" onClick={()=>onDownloadRecord(record)}></Button>
</div>
}
},
];
const [tableData, setTableData] = useState<DetailTable[]>([])
const [total, setTotal] = useState(0)
const getDetailDataList = (params:SearchParams) => {
setLoading(true)
getDetailList(params).then(res => {
if(res.success){
setLoading(false)
//@ts-ignore
setTableData(res.data.list)
setTotal(res.data.total)
}else{
}
}).catch(e=>{
}).finally(()=>{
setLoading(false)
})
}
const [selectionType, setSelectionType] = useState<'checkbox'>('checkbox');
const { confirm } = Modal;
const onBatchDel = () => {
confirm({
title: '提示',
icon: <ExclamationCircleFilled />,
content: '请确认是否删除选中的数据',
okText:'确认',
cancelText:'取消',
onOk() {
onHandelDelData()
},
onCancel() {
console.log('Cancel');
},
});
}
let [idList, setIdList] = useState<number[]>([]);
let [isModaUploadlOpen, setIsModaUploadlOpen] = useState(false)
const onUploadData = () => {
setIdList([])
let list = [...selectRows]
let Ids:number[] = []
list.map(item => {
Ids.push(item.id)
})
setIdList(Ids)
handleUpload(Ids)
setIsModaUploadlOpen(true)
}
const onDownloadData = async () => {
// 1. 准备 id 列表
const Ids = selectRows.map(r => r.id);
// 2. 发起请求
const response = await fetch(`/api/measurement-data/downloads-zip/${Ids.join(',')}`);
// 3. 拿到 Blob
const blob = await response.blob();
// 4. 解析后端文件名
const contentDisp = response.headers.get('content-disposition');
const filename = parseFilename(contentDisp) || 'download.zip';
// 5. 触发下载
exportFile(blob, filename);
};
const [percent, setPercent] = useState(0)
const [failRecordNames, setFailRecordName] = useState<string[]>([])
const [successRecordNames, setSuccessRecordName] = useState<string[]>([])
const handleUpload = (Ids:number[]) => {
if(Ids && Ids.length){
kljUpload({
ids:Ids
}).then(res => {
if(res.status !== 0){
message.error('道岔数据上传失败')
}else{
message.success('上传成功')
if(res.data.successList && res.data.successList.length){
let list:string[] = []
res.data.successList.map((item:string) => {
let recordName = item.split(":")[0]
list.push(recordName)
})
setSuccessRecordName(list)
}
if(res.data.failList && res.data.failList.length){
let list:string[] = []
res.data.failList.map((item:string) => {
let recordName = item.split(":")[0]
list.push(recordName)
})
setFailRecordName(list)
}
setPercent(100)
}
}).catch(e=>{
})
}
}
//关闭上传
const handleCloseUpload = () => {
}
const [currentRecord, setCurrentRecord] = useState<Partial<DetailTable>>({})
const onShowDetail = async (item:DetailTable)=> {
//获取基线
setIsModalOpen(true)
setCurrentRecord(item)
setshowCalibration(false)
let res = await getBaseRecordPointSetByCode(item.railSize)
if (res.success && res.data && res.data.points) {
const benchmarkShapes = JSON.parse(res.data.points) as BenchmarkShape[];
if(!benchmarkShapes || !benchmarkShapes.length){
message.error('选择的测量记录没有数据')
return;
}
setTimeout(()=>{
if (canvasRef.current) {
console.log("解析后的基础图形数据:", benchmarkShapes);
canvasRef.current.setBenchmarkData(benchmarkShapes);
}
},100)
}
let resData = await getDetail({id:item.id})
if(resData){
// navigate(`/measure/detail/${item.id}`)
//@ts-ignore
// getRecordByUuid(resData.data.uuid)
//@ts-ignore
onDetaiResult(resData.data.uuid)
}
//获取测量的坐标点
getMeasurePoints(item)
}
function parseFilename(disposition: string | null): string | null {
if (!disposition) return null;
// 匹配 filename*=UTF-8''xxx 或 filename=xxx(带不带引号都能捕获)
const match = disposition.match(/filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i);
return match ? decodeURIComponent(match[1]) : null;
}
//导出
const onDownloadRecord = async (item: DetailTable) => {
// 1. 发请求
const response = await fetch(`/api/measurement-data/download/${item.id}`);
// 2. 拿到 Blob
const blob = await response.blob();
// 3. 解析头里的文件名
const contentDisp = response.headers.get('content-disposition');
// fallback 到 item.name + .txt
const filename = parseFilename(contentDisp) || `${item.name}.txt`;
// 4. 触发下载
exportFile(blob, filename);
};
const getMeasurePoints = (recordItem:DetailTable) => {
getPointsById({id:recordItem.id}).then(res=>{
if (canvasRef.current) {
// canvasRef.current.setMeasurementDataLeft(res.data.leftPoints)
// canvasRef.current.setMeasurementDataRight(res.data.rightPoints)
setshowCalibration(true)
canvasRef.current?.setMeasurementCalibrationData(res.data.alignPoints)
}
})
}
//初始化科天健机构数据
let [SJLY_List, setSJLYList] = useState<KTJ_BASE_TYPE[]>([]);
const queryDictionaryList = () => {
dictionaryListService().then((res) => {
if (res && res.data) {
const SJLY:KTJ_BASE_TYPE[] = res.data.SJLY
if(SJLY && SJLY.length){
let sjlylist = SJLY.filter(item => item.key === 'XLDC' || item.key === 'DCDC')
setSJLYList(sjlylist);
}
}
});
};
const onCancelModal = () => {
setIsModaUploadlOpen(false)
setPercent(0)
setFailRecordName([])
setSuccessRecordName([])
}
//法线
const [analysisReport, setAnalysisReport] = useState<AnalysisReport>();
const onDetaiResult = (uuid:string) => {
const gxValue= currentRecord.railSize || GX_CODE
const params = {
uuid,
code:gxValue
}
getReport(params).then(res=> {
if (res.success) {
const report: AnalysisReport = res.data;
console.log(report);
// 更新 canvas 的分析数据
if (report && report.angleAnalysisList) {
// 先过滤掉 distance 为 null 的数据
const validItems = report.angleAnalysisList.filter(item => item.distance !== null);
const analysisData: AnalysisData[] = validItems.map(item => ({
pointA: { x: parseFloat(item.pointA ? item.pointA.x : '0'), y: parseFloat(item.pointA ? item.pointA.y : '0') },
pointB: { x: parseFloat(item.pointB ? item.pointB.x : '0'), y: parseFloat(item.pointB ? item.pointB.y : '0') },
base: { x: parseFloat(item.pointA ? item.pointA.x : '0'), y: parseFloat(item.pointA ? item.pointA.y : '0') },
measure: { x: parseFloat(item.pointB ? item.pointB.x : '0'), y: parseFloat(item.pointB ? item.pointB.y : '0') },
distance: parseFloat(item.distance),
describe: item.describe,
}));
canvasRef.current?.setAnalysisData(analysisData);
}
setAnalysisReport(report);
} else {
message.error("分析报告请求失败: " + res.data.info);
}
})
}
const getRecordByUuid = (uuid:string) => {
getPointByUuid({uuid}).then(res=>{
if(res.data && res.data.points){
canvasRef.current?.setMeasurementData(res.data.points);
}
})
}
type DelParams = {
ids: string | number;
}
const onHandelDelData = () =>{
let list = [...selectRows]
const ids = list.map(item => item.id)
if(!ids || !ids.length){
return;
}
const params = {
ids:ids.join(',')
}
doDel(params)
}
const doDel = (params:DelParams) => {
delDetail(params).then(res => {
if(res.success){
message.success('删除成功')
const params = {
pageSize,
pageNum,
}
getDetailDataList(params)
setSelectedRowKeys([])
setSelectedRow([])
}
}).catch(e=> {
})
}
const [pageNum, setPageNum] = useState(1)
const [pageSize, setPageSize] = useState(8)
const onPageChange = (pageNumValue:number, pageSizeValue:number) => {
setPageNum(pageNumValue)
setPageSize(pageSizeValue)
searchParams = {
...searchParams,
pageSize:pageSizeValue,
pageNum:pageNumValue,
}
getDetailDataList(searchParams)
}
const [name, setName] = useState<string>()
const [dataSource, setDataSource] = useState<string>()
let searchParams:SearchParams = {
pageNum,
pageSize,
}
const onSearch = ()=> {
setPageNum(1)
setPageSize(5)
searchParams = {
pageSize,
pageNum: 1,
name,
dataSource
}
getDetailDataList(searchParams)
}
let [loading, setLoading] = useState(false)
let [isModalOpen, setIsModalOpen] = useState(false)
const canvasRef = useRef<MeasurementCanvasRef>(null);
const [showGrid, setShowGrid] = useState(true);
const [angleMarkBackup, setAngleMarkBackup] = useState(true);
const [showStandard, setShowStandard] = useState(true);
const [showMark, setShowMark] = useState(true);
const handleCancel = () => {
canvasRef.current?.setAnalysisData([]);
canvasRef.current?.setMeasurementData([]);
canvasRef.current?.setBenchmarkData([])
//@ts-ignore
setAnalysisReport({})
setIsModalOpen(false)
}
//校准
const [showCalibration, setshowCalibration] = useState(false)
const [caloading, setCaLoading] = useState(false)
const handleCalibration = () => {
setCaLoading(true)
if(currentRecord && currentRecord.id){
getAlignPointById({id:currentRecord.id}).then(res=>{
if(res.success){
setshowCalibration(true)
canvasRef.current?.setMeasurementCalibrationData(res.data)
}else{
message.error('校准失败!')
}
setCaLoading(false)
}).catch(e=>{
message.error('校准失败!')
})
}
}
useEffect(() => {
const intervalId = setInterval(() => {
if (percent < 100) {
const randomIncrement = Math.floor(Math.random() * 3) + 1;
const newProgress = Math.min(percent + randomIncrement, 100);
setPercent(newProgress);
} else {
clearInterval(intervalId);
}
}, 1000);
if(percent >= 90){
clearInterval(intervalId);
}
return () => {
clearInterval(intervalId);
};
}, [isModaUploadlOpen,percent]);
return (
<div>
{isModalOpen ?
<div className="pt-[5px]">
<div className="flex">
<div className="ml-[2rem]">
<Switch size="small" defaultChecked onChange={checked => setShowGrid(checked)} />
<span>线</span>
</div>
<div className="ml-[1rem] ">
<Switch
size="small"
checked={showStandard}
onChange={checked => {
setShowStandard(checked);
if (!checked) {
setAngleMarkBackup(showMark);
setShowMark(false);
} else {
setShowMark(angleMarkBackup);
}
}}
/>
<span>线</span>
</div>
<div className="ml-[1rem]">
<Switch
size="small"
checked={showMark}
onChange={checked => {
setShowMark(checked);
setAngleMarkBackup(checked);
}}
/>
<span>线</span>
</div>
</div>
<div className="flex ml-[2rem] mt-[5px]">
<MeasurementCanvas
width={800}
height={700}
logicalExtent={{ minX: -50, maxX: 50, minY: -20, maxY: 60 }}
gridStep={1}
origin={{ x: 0, y: 20 }}
pixelPerMm={8}
maxZoom={10}
showGrid={showGrid}
showBenchmark={showStandard}
showAnalysis={showMark}
showScale={false}
scaleInterval={1}
showCoordinates={true}
showCalibration={showCalibration}
ref={canvasRef}
/>
{analysisReport &&
<div className=" h-[550px] ml-[1rem]">
<header className="bg-[#e8f0ff] w-[300px] text-center text-lg font-medium py-2 text-primary border border-[#c1c6d4]">
</header>
<div className="analysis-table">
<table
style={{
width: "100%",
borderCollapse: "collapse",
border: "1px solid #ccc",
textAlign: "center",
}}>
<tbody>
<tr style={{ height: "40px", fontSize: "18px", color: "#9E9E9E" }}>
<td style={{ padding: "8px", border: "1px solid #ccc" }}>W1垂直磨耗</td>
<td style={{ padding: "8px", border: "1px solid #ccc" }}>{analysisReport.w1}</td>
</tr>
<tr style={{ height: "40px", fontSize: "18px", color: "#9E9E9E" }}>
<td style={{ padding: "8px", border: "1px solid #ccc" }}></td>
<td style={{ padding: "8px", border: "1px solid #ccc" }}>
{analysisReport.railHeadWidth}
</td>
</tr>
{analysisReport.angleAnalysisList ? analysisReport.angleAnalysisList.map((item, index) => (
<tr key={index} style={{ height: "40px", fontSize: "18px", color: "#9E9E9E" }}>
<td style={{ padding: "8px", border: "1px solid #ccc" }}>{item.describe}</td>
<td style={{ padding: "8px", border: "1px solid #ccc" }}>{item.distance}</td>
</tr>
))
:
<div className="h-[28rem]"></div>
}
</tbody>
</table>
</div>
{/* <div className="flex justify-center mt-[1.5rem]" >
<Button type="primary" onClick={handleCalibration} className='w-[150px]'>
校准
</Button>
</div> */}
<div className="flex justify-center mt-[1.5rem]" >
<Button type="primary" onClick={handleCancel} className='w-[150px]'>
</Button>
</div>
</div>
}
</div>
</div>
:
<>
<div className="p-[15px]">
<Button
disabled={!selectRows.length}
type="primary"
onClick={onBatchDel}>
</Button>
<Button
className='ml-[2rem]'
disabled={!selectRows.length}
type="primary"
onClick={onUploadData}>
</Button>
<Button
className='ml-[2rem]'
disabled={!selectRows.length}
type="primary"
onClick={onDownloadData}>
</Button>
<Input className='ml-[2rem]' allowClear placeholder="测量名称" onChange={(e)=>setName(e.target.value)} style={{ width: 200 }}/>
<Select
placeholder="请选择数据来源"
className='ml-[2rem] w-[150px]'
allowClear
onChange={setDataSource}
options={SJLY_List.map((item) => ({
label: item.value,
value: item.key,
}))}
></Select>
<Button type="primary" className='ml-[2rem]' onClick={onSearch}></Button>
</div>
<div>
<Table<DetailTable>
style={{height:"70vh"}}
locale={{
emptyText: '无数据',
}}
loading={loading}
rowSelection={{ type: selectionType, ...rowSelection }}
columns={columns}
rowKey="id"
dataSource={tableData && tableData.map(item => ({ ...item, key: item.name }))}
pagination={false}
scroll={{ y: 500 }}
/>
<div className="float-right mt-[10px] mr-[1rem]">
<Pagination onChange={onPageChange} current={pageNum} pageSizeOptions={[5,10,20,30]} defaultCurrent={pageNum} defaultPageSize={pageSize} total={total}/>
</div>
<Modal title="上传状态" open={isModaUploadlOpen} footer={null} onCancel={onCancelModal}>
{/* {idList.length > 0 && <div className='modal_upload'>选择的道岔调查数据:<span style={{color:"green"}}>{idList.length}</span>&nbsp;&nbsp;个</div>} */}
<Progress percent={percent} size="small" />
{selectRows.length}
{percent === 100 &&
<div >
<div style={{marginTop:"10px"}}>
<CheckCircleOutlined style={{color:'#52C41A', fontSize:"20px"}}/>
<span style={{paddingLeft:"20px"}}>{successRecordNames.length} {successRecordNames.join(',')}</span>
</div>
<div style={{marginTop:"10px"}}>
<WarningOutlined style={{color:'#ff4d4f', fontSize:"20px"}}/>
<span style={{paddingLeft:"20px"}}>{failRecordNames.length} {failRecordNames.join(',')}</span>
</div>
</div>
}
<br/>
<div style={{marginTop:"20px",marginLeft:"28vw"}}>
<Button type="primary" onClick={onCancelModal}></Button>
</div>
</Modal>
</div>
</>
}
</div>
);
}