mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-05 03:12:34 -06:00
0302-отрефакторил в нормальный вид на мобилу и десктоп
сразу выкинул пути в импортах и добавил алиас для корня
This commit is contained in:
206
rmser-view/src/shared/ui/ExcelPreviewModal.tsx
Normal file
206
rmser-view/src/shared/ui/ExcelPreviewModal.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Modal, Button, message } from "antd";
|
||||
import {
|
||||
ZoomInOutlined,
|
||||
ZoomOutOutlined,
|
||||
UndoOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import * as XLSX from "xlsx";
|
||||
import { apiClient } from "../api";
|
||||
|
||||
interface ExcelPreviewModalProps {
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
fileUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Компонент для предпросмотра Excel файлов
|
||||
* Позволяет просматривать содержимое Excel файлов с возможностью масштабирования
|
||||
*/
|
||||
const ExcelPreviewModal: React.FC<ExcelPreviewModalProps> = ({
|
||||
visible,
|
||||
onCancel,
|
||||
fileUrl,
|
||||
}) => {
|
||||
// Данные таблицы из Excel файла
|
||||
const [data, setData] = useState<
|
||||
(string | number | boolean | null | undefined)[][]
|
||||
>([]);
|
||||
// Масштаб отображения таблицы
|
||||
const [scale, setScale] = useState<number>(1);
|
||||
|
||||
/**
|
||||
* Загрузка и парсинг Excel файла при изменении видимости или URL файла
|
||||
*/
|
||||
useEffect(() => {
|
||||
const loadExcelFile = async () => {
|
||||
if (!visible || !fileUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("ExcelPreviewModal: Start loading", fileUrl);
|
||||
|
||||
try {
|
||||
// Загрузка файла через apiClient с авторизацией
|
||||
const response = await apiClient.get(fileUrl, {
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
console.log(
|
||||
"ExcelPreviewModal: Got response",
|
||||
response.status,
|
||||
response.data.byteLength
|
||||
);
|
||||
const arrayBuffer = response.data;
|
||||
|
||||
// Чтение Excel файла
|
||||
const workbook = XLSX.read(arrayBuffer, { type: "array" });
|
||||
console.log("ExcelPreviewModal: Workbook parsed", workbook.SheetNames);
|
||||
|
||||
// Получение первого листа
|
||||
const firstSheetName = workbook.SheetNames[0];
|
||||
const sheet = workbook.Sheets[firstSheetName];
|
||||
|
||||
// Преобразование листа в JSON-массив массивов
|
||||
const jsonData = XLSX.utils.sheet_to_json(sheet, {
|
||||
header: 1,
|
||||
}) as (string | number | boolean | null | undefined)[][];
|
||||
|
||||
setData(jsonData);
|
||||
console.log("ExcelPreviewModal: Data set, rows:", jsonData.length);
|
||||
// Сброс масштаба при загрузке нового файла
|
||||
setScale(1);
|
||||
} catch (error) {
|
||||
console.error("ExcelPreviewModal Error:", error);
|
||||
|
||||
// Обработка ошибок авторизации (401) обрабатывается в интерсепторе apiClient
|
||||
if (error && typeof error === "object" && "response" in error) {
|
||||
const axiosError = error as { response?: { status?: number } };
|
||||
if (axiosError.response?.status === 401) {
|
||||
message.error(
|
||||
"Ошибка авторизации. Необходима повторная авторизация."
|
||||
);
|
||||
} else {
|
||||
message.error("Не удалось загрузить Excel файл");
|
||||
}
|
||||
} else {
|
||||
message.error("Не удалось загрузить Excel файл");
|
||||
}
|
||||
|
||||
setData([]);
|
||||
}
|
||||
};
|
||||
|
||||
loadExcelFile();
|
||||
}, [visible, fileUrl]);
|
||||
|
||||
/**
|
||||
* Увеличение масштаба
|
||||
*/
|
||||
const handleZoomIn = () => {
|
||||
setScale((prev) => Math.min(prev + 0.1, 3));
|
||||
};
|
||||
|
||||
/**
|
||||
* Уменьшение масштаба
|
||||
*/
|
||||
const handleZoomOut = () => {
|
||||
setScale((prev) => Math.max(prev - 0.1, 0.5));
|
||||
};
|
||||
|
||||
/**
|
||||
* Сброс масштаба до исходного значения
|
||||
*/
|
||||
const handleReset = () => {
|
||||
setScale(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Обработчик закрытия модалки
|
||||
*/
|
||||
const handleCancel = () => {
|
||||
setData([]);
|
||||
onCancel();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={visible}
|
||||
onCancel={handleCancel}
|
||||
width="90%"
|
||||
footer={null}
|
||||
title="Предпросмотр Excel"
|
||||
style={{ top: 20, zIndex: 10000 }}
|
||||
>
|
||||
{/* Панель инструментов для управления масштабом */}
|
||||
<div style={{ marginBottom: 16, display: "flex", gap: 8 }}>
|
||||
<Button
|
||||
icon={<ZoomInOutlined />}
|
||||
onClick={handleZoomIn}
|
||||
disabled={scale >= 3}
|
||||
>
|
||||
Увеличить (+)
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ZoomOutOutlined />}
|
||||
onClick={handleZoomOut}
|
||||
disabled={scale <= 0.5}
|
||||
>
|
||||
Уменьшить (-)
|
||||
</Button>
|
||||
<Button
|
||||
icon={<UndoOutlined />}
|
||||
onClick={handleReset}
|
||||
disabled={scale === 1}
|
||||
>
|
||||
Сброс
|
||||
</Button>
|
||||
<span style={{ marginLeft: "auto", lineHeight: "32px" }}>
|
||||
Масштаб: {Math.round(scale * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Контейнер с прокруткой для таблицы */}
|
||||
<div
|
||||
style={{
|
||||
height: 600,
|
||||
overflow: "auto",
|
||||
border: "1px solid #d9d9d9",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
{/* Таблица с данными Excel */}
|
||||
<table
|
||||
style={{
|
||||
borderCollapse: "collapse",
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: "top left",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<tbody>
|
||||
{data.map((row, rowIndex) => (
|
||||
<tr key={rowIndex}>
|
||||
{row.map((cell, cellIndex) => (
|
||||
<td
|
||||
key={cellIndex}
|
||||
style={{
|
||||
border: "1px solid #ccc",
|
||||
padding: "8px",
|
||||
minWidth: 100,
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{cell !== undefined && cell !== null ? String(cell) : ""}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExcelPreviewModal;
|
||||
Reference in New Issue
Block a user