Created
June 3, 2025 09:03
-
-
Save sunmeat/3f914c2e37659b1d54e0161ef625c79d to your computer and use it in GitHub Desktop.
react + php + files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
создать папку files в папке react | |
======================================================================================= | |
App.jsx: | |
import { useState, useEffect } from 'react'; | |
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'; | |
import './App.css'; | |
const API_URL = 'http://sunmeat.atwebpages.com/react/api.php'; | |
const UPLOAD_URL = 'http://sunmeat.atwebpages.com/react/upload.php'; | |
const queryClient = new QueryClient(); | |
const truncateText = (text, maxLength) => | |
text.length > maxLength ? text.slice(0, maxLength) + '...' : text; | |
function ProductList({ addToCart }) { | |
const fetchProducts = async () => { | |
const response = await fetch('https://fakestoreapi.com/products?limit=12'); | |
if (!response.ok) throw new Error('Ошибка загрузки продуктов'); | |
return response.json(); | |
}; | |
const { data, error, isLoading } = useQuery({ | |
queryKey: ['products'], | |
queryFn: fetchProducts, | |
staleTime: 5 * 60 * 1000, | |
}); | |
if (isLoading) return <div className="loading">Загрузка...</div>; | |
if (error) return <div className="error">Ошибка: {error.message}</div>; | |
return ( | |
<div className="product-list"> | |
<h2>Наши товары:</h2> | |
<div className="products"> | |
{data.map((product) => ( | |
<div key={product.id} className="product-card"> | |
<img src={product.image} alt={product.title} className="product-image" /> | |
<h3>{truncateText(product.title, 30)}</h3> | |
<p className="price">${product.price}</p> | |
<button onClick={() => addToCart(product)} className="add-to-cart"> | |
Добавить в корзину | |
</button> | |
</div> | |
))} | |
</div> | |
</div> | |
); | |
} | |
function Cart({ cart, removeFromCart }) { | |
const total = cart.reduce((sum, item) => sum + Number(item.price), 0).toFixed(2); | |
return ( | |
<div className="cart"> | |
<h2>Корзина</h2> | |
{cart.length === 0 ? ( | |
<p>Корзина пуста</p> | |
) : ( | |
<div> | |
{cart.map((item) => ( | |
<div key={item.id} className="cart-item"> | |
<span>{truncateText(item.title, 30)}</span> | |
<span>${item.price}</span> | |
<button onClick={() => removeFromCart(item.id)} className="remove-from-cart"> | |
Удалить | |
</button> | |
</div> | |
))} | |
<p className="total">Итого: ${total}</p> | |
</div> | |
)} | |
</div> | |
); | |
} | |
function App() { | |
const [cart, setCart] = useState([]); | |
const [error, setError] = useState(null); | |
const [uploadMessage, setUploadMessage] = useState(null); | |
useEffect(() => { | |
const loadCart = async () => { | |
try { | |
const response = await fetch(API_URL); | |
if (!response.ok) throw new Error('Ошибка загрузки корзины'); | |
const data = await response.json(); | |
if (data.error) throw new Error(data.error); | |
setCart(data); | |
} catch (err) { | |
setError(err.message); | |
} | |
}; | |
loadCart(); | |
}, []); | |
const addToCart = async (product) => { | |
try { | |
const newItem = { | |
id: product.id, | |
title: product.title, | |
price: Number(product.price), | |
image: product.image, | |
}; | |
const response = await fetch(API_URL, { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify(newItem), | |
}); | |
if (!response.ok) throw new Error('Ошибка добавления в корзину'); | |
const result = await response.json(); | |
if (result.error) throw new Error(result.error); | |
setCart((prevCart) => [ | |
...prevCart, | |
{ | |
...newItem, | |
product_id: newItem.id, | |
id: result.id, | |
}, | |
]); | |
} catch (err) { | |
setError(err.message); | |
} | |
}; | |
const removeFromCart = async (cartItemId) => { | |
try { | |
const response = await fetch(API_URL, { | |
method: 'DELETE', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ id: cartItemId }), | |
}); | |
if (!response.ok) throw new Error('Ошибка удаления из корзины'); | |
const result = await response.json(); | |
if (result.error) throw new Error(result.error); | |
setCart((prevCart) => prevCart.filter((item) => item.id !== cartItemId)); | |
} catch (err) { | |
setError(err.message); | |
} | |
}; | |
const handleFileUpload = async (event) => { | |
const file = event.target.files[0]; | |
if (!file) return; | |
// Validate file type (text or image) | |
const allowedTypes = [ | |
'text/plain', | |
'image/jpeg', | |
'image/png', | |
'image/gif', | |
]; | |
if (!allowedTypes.includes(file.type)) { | |
setUploadMessage('Ошибка: Допустимы только текстовые файлы (.txt) или изображения (.jpg, .png, .gif)'); | |
return; | |
} | |
const maxSize = 5 * 1024 * 1024; // 5MB in bytes | |
if (file.size > maxSize) { | |
setUploadMessage('Ошибка: Файл слишком большой (максимум 5 МБ)'); | |
return; | |
} | |
const formData = new FormData(); | |
formData.append('file', file); | |
try { | |
const response = await fetch(UPLOAD_URL, { | |
method: 'POST', | |
body: formData, | |
}); | |
const result = await response.json(); | |
if (!response.ok || result.error) { | |
throw new Error(result.error || 'Ошибка загрузки файла'); | |
} | |
setUploadMessage(`Файл "${file.name}" успешно загружен!`); | |
} catch (err) { | |
setUploadMessage(`Ошибка: ${err.message}`); | |
} | |
}; | |
return ( | |
<QueryClientProvider client={queryClient}> | |
<div className="app"> | |
<h1>Интернет-магазин ReactExpress</h1> | |
{error && <div className="error">Ошибка: {error}</div>} | |
<div className="file-upload"> | |
<h2>Загрузка файла</h2> | |
<input | |
type="file" | |
accept=".txt,image/jpeg,image/png,image/gif" | |
onChange={handleFileUpload} | |
/> | |
{uploadMessage && <p className={uploadMessage.includes('Ошибка') ? 'error' : 'success'}>{uploadMessage}</p>} | |
</div> | |
<ProductList addToCart={addToCart} /> | |
<Cart cart={cart} removeFromCart={removeFromCart} /> | |
</div> | |
</QueryClientProvider> | |
); | |
} | |
export default App; | |
======================================================================================= | |
пересобрать пример, npm run build, заменить файлы index.html и 2 файла в папке assets | |
======================================================================================= | |
react/upload.php: | |
<?php | |
ini_set('display_errors', 1); | |
error_reporting(E_ALL); | |
header('Content-Type: application/json'); | |
header('Access-Control-Allow-Origin: *'); | |
header('Access-Control-Allow-Methods: POST, OPTIONS'); | |
header('Access-Control-Allow-Headers: Content-Type'); | |
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { | |
http_response_code(200); | |
exit; | |
} | |
function logMessage($message) { | |
$timestamp = date('Y-m-d H:i:s'); | |
$logEntry = "[$timestamp] $message\n"; | |
file_put_contents('log.txt', $logEntry, FILE_APPEND | LOCK_EX); | |
} | |
$uploadDir = 'files/'; | |
$baseUrl = 'http://sunmeat.atwebpages.com/react/files/'; | |
if (!is_dir($uploadDir)) { | |
if (!mkdir($uploadDir, 0755, true)) { | |
logMessage("Ошибка создания директории: $uploadDir"); | |
http_response_code(500); | |
echo json_encode(['error' => 'Ошибка создания директории для загрузок']); | |
exit; | |
} | |
} | |
if (!is_writable($uploadDir)) { | |
logMessage("Директория $uploadDir не доступна для записи"); | |
http_response_code(500); | |
echo json_encode(['error' => 'Директория недоступна для записи']); | |
exit; | |
} | |
if ($_SERVER['REQUEST_METHOD'] === 'POST') { | |
try { | |
if (!isset($_FILES['file'])) { | |
http_response_code(400); | |
echo json_encode(['error' => 'Файл не был загружен']); | |
exit; | |
} | |
$file = $_FILES['file']; | |
$allowedTypes = ['text/plain', 'image/jpeg', 'image/png', 'image/gif']; | |
$maxSize = 5 * 1024 * 1024; // 5MB | |
if (!in_array($file['type'], $allowedTypes)) { | |
http_response_code(400); | |
echo json_encode(['error' => 'Недопустимый тип файла. Допустимы: .txt, .jpg, .png, .gif']); | |
exit; | |
} | |
if ($file['size'] > $maxSize) { | |
http_response_code(400); | |
echo json_encode(['error' => 'Файл слишком большой (максимум 5 МБ)']); | |
exit; | |
} | |
if ($file['error'] !== UPLOAD_ERR_OK) { | |
logMessage("Ошибка загрузки файла: код ошибки " . $file['error']); | |
http_response_code(400); | |
echo json_encode(['error' => 'Ошибка загрузки файла']); | |
exit; | |
} | |
$fileName = uniqid() . '_' . basename($file['name']); | |
$filePath = $uploadDir . $fileName; | |
if (!move_uploaded_file($file['tmp_name'], $filePath)) { | |
logMessage("Ошибка перемещения файла в $filePath"); | |
http_response_code(500); | |
echo json_encode(['error' => 'Ошибка сохранения файла']); | |
exit; | |
} | |
logMessage("Файл успешно загружен: $fileName"); | |
echo json_encode([ | |
'success' => true, | |
'fileName' => $fileName, | |
'fileUrl' => $baseUrl . $fileName, | |
'message' => 'Файл успешно загружен' | |
]); | |
} catch (Exception $e) { | |
logMessage("Ошибка обработки загрузки: " . $e->getMessage()); | |
http_response_code(500); | |
echo json_encode(['error' => 'Ошибка сервера: ' . $e->getMessage()]); | |
} | |
} else { | |
logMessage("Неподдерживаемый метод: " . $_SERVER['REQUEST_METHOD']); | |
http_response_code(405); | |
echo json_encode(['error' => 'Метод не поддерживается']); | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment