更改相册背景效果
This commit is contained in:
@@ -14,6 +14,7 @@ class ImageController extends Controller
|
||||
$image = request()->file('image');
|
||||
$pid = request()->post('pid');
|
||||
|
||||
// return $image;
|
||||
return new ImageService()->processImage($image, $pid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,872 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>图片压缩工具</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #3498db;
|
||||
--secondary-color: #2ecc71;
|
||||
--warning-color: #e74c3c;
|
||||
--dark-color: #2c3e50;
|
||||
--light-color: #ecf0f1;
|
||||
--gray-color: #bdc3c7;
|
||||
--accent-color: #9b59b6;
|
||||
--transition: all 0.3s ease;
|
||||
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
--card-radius: 10px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #1a2980, #26d0ce);
|
||||
color: #333;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
header {
|
||||
background: var(--dark-color);
|
||||
color: white;
|
||||
padding: 25px 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.logo i {
|
||||
font-size: 2.2rem;
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
font-weight: 600;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.logo span {
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-columns: 300px 1fr;
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background: var(--dark-color);
|
||||
color: var(--light-color);
|
||||
padding: 25px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.file-browser {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: var(--card-radius);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.directory-path {
|
||||
font-size: 0.9rem;
|
||||
color: var(--gray-color);
|
||||
margin-bottom: 10px;
|
||||
padding: 8px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 6px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: var(--card-radius);
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background: rgba(44, 62, 80, 0.8);
|
||||
}
|
||||
|
||||
.file-item.selected {
|
||||
background: rgba(52, 152, 219, 0.7);
|
||||
}
|
||||
|
||||
.file-item i {
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.file-item span {
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
color: var(--gray-color);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.control-group h3 {
|
||||
margin-bottom: 12px;
|
||||
color: var(--secondary-color);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.quality-slider {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
-webkit-appearance: none;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.quality-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-color);
|
||||
cursor: pointer;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.quality-value {
|
||||
text-align: center;
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
margin-top: 5px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px 25px;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
transition: var(--transition);
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.btn i {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn-compress {
|
||||
background: var(--secondary-color);
|
||||
}
|
||||
|
||||
.btn-compress-all {
|
||||
background: var(--accent-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 30px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.preview-card {
|
||||
background: white;
|
||||
border-radius: var(--card-radius);
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: var(--dark-color);
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--gray-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
background: rgba(189, 195, 199, 0.2);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.before .info-value {
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
.after .info-value {
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.comparison {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.savings {
|
||||
font-weight: bold;
|
||||
color: var(--secondary-color);
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
font-size: 1.1rem;
|
||||
background: rgba(46, 204, 113, 0.1);
|
||||
border-radius: var(--card-radius);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.no-image {
|
||||
color: #7f8c8d;
|
||||
font-size: 1.1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-image i {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
color: var(--gray-color);
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 12px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--secondary-color);
|
||||
width: 0%;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid rgba(52, 152, 219, 0.2);
|
||||
border-top-color: var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.completed {
|
||||
display: none;
|
||||
color: var(--secondary-color);
|
||||
font-size: 1.3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.completed i {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.overlay.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.overlay-content {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
transform: translateY(-30px);
|
||||
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
.overlay.active .overlay-content {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.overlay-title {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 20px;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
.overlay-text {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.confirm-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.overlay-btn {
|
||||
min-width: 120px;
|
||||
padding: 12px 20px;
|
||||
border-radius: 50px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
background: var(--secondary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-confirm:hover {
|
||||
background: #27ae60;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: transparent;
|
||||
color: var(--dark-color);
|
||||
border-color: var(--gray-color);
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
main {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
padding: 20px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="logo">
|
||||
<i class="fas fa-compress-alt"></i>
|
||||
<h1>图片<span>压缩</span>工具</h1>
|
||||
</div>
|
||||
<div>
|
||||
<p>批量压缩并替换图片文件</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<aside class="sidebar">
|
||||
<div class="file-browser">
|
||||
<h3>选择目录</h3>
|
||||
<div class="directory-path">
|
||||
storage/app/public/images/compressed
|
||||
</div>
|
||||
|
||||
<div class="file-list">
|
||||
<!-- File items will be added dynamically -->
|
||||
</div>
|
||||
|
||||
<button class="btn" id="refreshBtn">
|
||||
<i class="fas fa-sync-alt"></i> 刷新文件列表
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="control-group">
|
||||
<h3>压缩质量设置</h3>
|
||||
<input type="range" min="40" max="95" value="75" class="quality-slider" id="qualitySlider">
|
||||
<div class="quality-value" id="qualityValue">75%</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<h3>操作</h3>
|
||||
<button class="btn btn-compress" id="compressBtn">
|
||||
<i class="fas fa-file-image"></i> 压缩当前图片
|
||||
</button>
|
||||
<button class="btn btn-compress-all" id="compressAllBtn">
|
||||
<i class="fas fa-layer-group"></i> 批量压缩目录
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="content">
|
||||
<div class="preview-card">
|
||||
<div class="card-header">
|
||||
<h2>原始图片</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="preview-container" id="originalPreview">
|
||||
<div class="no-image">
|
||||
<i class="fas fa-image"></i>
|
||||
<p>请从左侧选择一张图片</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-info before">
|
||||
<div class="info-item">
|
||||
<div class="info-label">文件大小</div>
|
||||
<div class="info-value" id="originalSize">0 KB</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">分辨率</div>
|
||||
<div class="info-value" id="originalDims">0×0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-card">
|
||||
<div class="card-header">
|
||||
<h2>压缩后图片</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="preview-container" id="compressedPreview">
|
||||
<div class="no-image">
|
||||
<i class="fas fa-compress-arrows-alt"></i>
|
||||
<p>压缩后图片将显示在这里</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-info after">
|
||||
<div class="info-item">
|
||||
<div class="info-label">文件大小</div>
|
||||
<div class="info-value" id="compressedSize">0 KB</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">分辨率</div>
|
||||
<div class="info-value" id="compressedDims">0×0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="savings" id="savingsText">
|
||||
尺寸减少:0%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress card for batch processing -->
|
||||
<div class="preview-card">
|
||||
<div class="card-header">
|
||||
<h2>批量压缩进度</h2>
|
||||
</div>
|
||||
<div class="card-body" style="align-items: center;">
|
||||
<div class="no-image" id="batchPlaceholder">
|
||||
<i class="fas fa-layer-group"></i>
|
||||
<p>批量压缩状态将显示在这里</p>
|
||||
</div>
|
||||
|
||||
<div class="progress-container" id="progressContainer" style="display: none;">
|
||||
<div class="progress-header">
|
||||
<span>处理中...</span>
|
||||
<span id="progressText">0/0</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="completed" id="completedMsg">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<p>压缩完成!所有文件已更新</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Confirmation overlay -->
|
||||
<div class="overlay" id="confirmOverlay">
|
||||
<div class="overlay-content">
|
||||
<h2 class="overlay-title">确认批量操作</h2>
|
||||
<p class="overlay-text">
|
||||
您将压缩 <strong id="fileCount">0</strong> 个文件,覆盖其原始文件。<br>
|
||||
此操作不可逆,请确保您已备份重要文件。
|
||||
</p>
|
||||
<div class="confirm-buttons">
|
||||
<button class="overlay-btn btn-confirm" id="confirmBatch">开始压缩</button>
|
||||
<button class="overlay-btn btn-cancel" id="cancelBatch">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Mock data for demonstration (in a real app this would come from server)
|
||||
const directoryData = [
|
||||
{ name: 'engagement1.jpg', size: '2.8MB', path: 'engagement1.jpg', type: 'image/jpeg' },
|
||||
{ name: 'wedding_prep.png', size: '4.2MB', path: 'wedding_prep.png', type: 'image/png' },
|
||||
{ name: 'ceremony_moment.jpg', size: '3.1MB', path: 'ceremony_moment.jpg', type: 'image/jpeg' },
|
||||
{ name: 'family_group.jpg', size: '5.4MB', path: 'family_group.jpg', type: 'image/jpeg' },
|
||||
{ name: 'reception_dance.jpg', size: '3.7MB', path: 'reception_dance.jpg', type: 'image/jpeg' },
|
||||
{ name: 'bridal_portrait.jpg', size: '4.8MB', path: 'bridal_portrait.jpg', type: 'image/jpeg' },
|
||||
{ name: 'groom_portrait.png', size: '3.9MB', path: 'groom_portrait.png', type: 'image/png' },
|
||||
{ name: 'venue_overview.jpg', size: '6.2MB', path: 'venue_overview.jpg', type: 'image/jpeg' },
|
||||
];
|
||||
|
||||
// DOM Elements
|
||||
const fileList = document.querySelector('.file-list');
|
||||
const qualitySlider = document.getElementById('qualitySlider');
|
||||
const qualityValue = document.getElementById('qualityValue');
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
const compressBtn = document.getElementById('compressBtn');
|
||||
const compressAllBtn = document.getElementById('compressAllBtn');
|
||||
const originalPreview = document.getElementById('originalPreview');
|
||||
const compressedPreview = document.getElementById('compressedPreview');
|
||||
const originalSize = document.getElementById('originalSize');
|
||||
const compressedSize = document.getElementById('compressedSize');
|
||||
const originalDims = document.getElementById('originalDims');
|
||||
const compressedDims = document.getElementById('compressedDims');
|
||||
const savingsText = document.getElementById('savingsText');
|
||||
const batchPlaceholder = document.getElementById('batchPlaceholder');
|
||||
const progressContainer = document.getElementById('progressContainer');
|
||||
const progressFill = document.getElementById('progressFill');
|
||||
const progressText = document.getElementById('progressText');
|
||||
const completedMsg = document.getElementById('completedMsg');
|
||||
const confirmOverlay = document.getElementById('confirmOverlay');
|
||||
const fileCount = document.getElementById('fileCount');
|
||||
const confirmBatch = document.getElementById('confirmBatch');
|
||||
const cancelBatch = document.getElementById('cancelBatch');
|
||||
|
||||
// Current state
|
||||
let currentFile = null;
|
||||
let compressedImage = null;
|
||||
let currentQuality = 75;
|
||||
|
||||
// Initialize file browser
|
||||
function initFileBrowser() {
|
||||
fileList.innerHTML = '';
|
||||
|
||||
directoryData.forEach(file => {
|
||||
const fileItem = document.createElement('div');
|
||||
fileItem.className = 'file-item';
|
||||
fileItem.dataset.path = file.path;
|
||||
fileItem.dataset.size = file.size;
|
||||
fileItem.dataset.type = file.type;
|
||||
|
||||
fileItem.innerHTML = `
|
||||
<i class="fas fa-file-image"></i>
|
||||
<span>${file.name}</span>
|
||||
<span class="file-size">${file.size}</span>
|
||||
`;
|
||||
|
||||
fileItem.addEventListener('click', () => selectFile(fileItem, file));
|
||||
fileList.appendChild(fileItem);
|
||||
});
|
||||
}
|
||||
|
||||
// Select file handler
|
||||
function selectFile(item, file) {
|
||||
// Clear previous selection
|
||||
document.querySelectorAll('.file-item').forEach(el => {
|
||||
el.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Select current
|
||||
item.classList.add('selected');
|
||||
currentFile = file;
|
||||
|
||||
// Preview the image
|
||||
previewImage(file);
|
||||
}
|
||||
|
||||
// Preview image function
|
||||
function previewImage(file) {
|
||||
// For demonstration, we'll use placeholder images
|
||||
const placeholderImage = 'https://picsum.photos/600/400';
|
||||
|
||||
// Clear previous previews
|
||||
originalPreview.innerHTML = '';
|
||||
compressedPreview.innerHTML = '<div class="no-image"><i class="fas fa-compress-arrows-alt"></i><p>压缩后图片将显示在这里</p></div>';
|
||||
|
||||
// Create image element
|
||||
const img = document.createElement('img');
|
||||
img.className = 'preview-image';
|
||||
img.src = placeholderImage;
|
||||
|
||||
originalPreview.appendChild(img);
|
||||
|
||||
// Simulate file info
|
||||
const sizeParts = file.size.match(/(\d+\.?\d*)(\w+)/);
|
||||
if (sizeParts) {
|
||||
const sizeNum = parseFloat(sizeParts[1]);
|
||||
originalSize.textContent = file.size;
|
||||
originalDims.textContent = '1920×1080'; // Fixed for demo
|
||||
|
||||
// Simulate compression for demo
|
||||
setTimeout(() => {
|
||||
simulateCompression(sizeNum, sizeParts[2]);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate compression (in a real app, real compression would happen)
|
||||
function simulateCompression(size, unit) {
|
||||
// Calculate compression for demo
|
||||
const compressionRatio = 1 - (currentQuality / 100);
|
||||
const compressedSize = (size * compressionRatio * 0.9).toFixed(1);
|
||||
const savings = Math.round((1 - (compressedSize / size)) * 100);
|
||||
|
||||
// Update compressed panel
|
||||
compressedSize.textContent = compressedSize + unit;
|
||||
compressedDims.textContent = '1920×1080';
|
||||
savingsText.innerHTML = `尺寸减少:<strong>${savings}%</strong>`;
|
||||
|
||||
// Show compressed image (same for demo)
|
||||
compressedPreview.innerHTML = '';
|
||||
const img = document.createElement('img');
|
||||
img.className = 'preview-image';
|
||||
img.src = 'https://picsum.photos/600/400';
|
||||
compressedPreview.appendChild(img);
|
||||
}
|
||||
|
||||
// Compress current image
|
||||
function compressImage() {
|
||||
if (!currentFile) {
|
||||
alert('请先选择一张图片');
|
||||
return;
|
||||
}
|
||||
|
||||
// In a real implementation, this would send the image to the server for processing
|
||||
// Here we just simulate the compression
|
||||
const sizeParts = currentFile.size.match(/(\d+\.?\d*)(\w+)/);
|
||||
if (sizeParts) {
|
||||
const sizeNum = parseFloat(sizeParts[1]);
|
||||
simulateCompression(sizeNum, sizeParts[2]);
|
||||
|
||||
// Show success message
|
||||
savingsText.innerHTML = `压缩成功!保存为原始文件`;
|
||||
savingsText.style.backgroundColor = 'rgba(46, 204, 113, 0.15)';
|
||||
|
||||
setTimeout(() => {
|
||||
savingsText.style.backgroundColor = 'rgba(46, 204, 113, 0.1)';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// Show batch compression confirmation
|
||||
function showBatchConfirm() {
|
||||
fileCount.textContent = directoryData.length;
|
||||
confirmOverlay.classList.add('active');
|
||||
}
|
||||
|
||||
// Execute batch compression
|
||||
function executeBatchCompression() {
|
||||
confirmOverlay.classList.remove('active');
|
||||
|
||||
// Hide placeholder and show progress
|
||||
batchPlaceholder.style.display = 'none';
|
||||
progressContainer.style.display = 'block';
|
||||
completedMsg.style.display = 'none';
|
||||
|
||||
const totalFiles = directoryData.length;
|
||||
let processed = 0;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
processed++;
|
||||
const progress = (processed / totalFiles) * 100;
|
||||
|
||||
progressFill.style.width = `${progress}%`;
|
||||
progressText.textContent = `${processed}/${totalFiles}`;
|
||||
|
||||
if (processed >= totalFiles) {
|
||||
clearInterval(interval);
|
||||
|
||||
// Show completion message
|
||||
setTimeout(() => {
|
||||
progressContainer.style.display = 'none';
|
||||
completedMsg.style.display = 'block';
|
||||
}, 1000);
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
qualitySlider.addEventListener('input', () => {
|
||||
currentQuality = parseInt(qualitySlider.value);
|
||||
qualityValue.textContent = currentQuality + '%';
|
||||
|
||||
if (currentFile) {
|
||||
const sizeParts = currentFile.size.match(/(\d+\.?\d*)(\w+)/);
|
||||
if (sizeParts) {
|
||||
const sizeNum = parseFloat(sizeParts[1]);
|
||||
simulateCompression(sizeNum, sizeParts[2]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
refreshBtn.addEventListener('click', initFileBrowser);
|
||||
compressBtn.addEventListener('click', compressImage);
|
||||
compressAllBtn.addEventListener('click', showBatchConfirm);
|
||||
confirmBatch.addEventListener('click', executeBatchCompression);
|
||||
cancelBatch.addEventListener('click', () => {
|
||||
confirmOverlay.classList.remove('active');
|
||||
});
|
||||
|
||||
// Initialize the app
|
||||
initFileBrowser();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
384
resources/views/upload.blade.php
Normal file
384
resources/views/upload.blade.php
Normal file
@@ -0,0 +1,384 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>图片上传 | Laravel相册 - 暗色主题</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.fade-enter-from, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.dropzone {
|
||||
border: 2px dashed #4b5563;
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.dropzone.active {
|
||||
border-color: #818cf8;
|
||||
background-color: #1e293b;
|
||||
}
|
||||
.sort-handle {
|
||||
cursor: move;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.sort-handle:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.sortable-ghost {
|
||||
opacity: 0.5;
|
||||
background: #4f46e5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-900 min-h-screen text-gray-200">
|
||||
<div id="app" class="max-w-4xl mx-auto p-4 sm:p-6">
|
||||
<!-- 顶部导航 -->
|
||||
<nav class="flex justify-between items-center mb-8">
|
||||
<h1 class="text-2xl font-bold text-indigo-400">
|
||||
<i class="fas fa-images mr-2"></i>图片上传
|
||||
</h1>
|
||||
<div>
|
||||
<a href="#" class="bg-indigo-700 hover:bg-indigo-600 text-white px-4 py-2 rounded-lg transition">
|
||||
返回相册
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 主卡片 -->
|
||||
<div class="bg-gray-800 rounded-xl shadow-lg overflow-hidden border border-gray-700">
|
||||
<!-- 标题区域 -->
|
||||
<div class="bg-gradient-to-r from-indigo-800 to-purple-800 p-6 text-white">
|
||||
<h2 class="text-xl font-bold">上传新图片</h2>
|
||||
<p class="mt-1 opacity-90">选择相册并上传图片(支持拖拽排序)</p>
|
||||
</div>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<div class="p-6">
|
||||
<!-- 全局加载指示器 -->
|
||||
<div v-if="isLoading" class="fixed inset-0 bg-black bg-opacity-70 z-50 flex items-center justify-center">
|
||||
<div class="text-center bg-gray-800 rounded-xl p-8 shadow-xl border border-gray-700">
|
||||
<div class="animate-spin rounded-full h-16 w-16 border-b-2 border-indigo-600 mx-auto mb-4"></div>
|
||||
<h3 class="text-xl font-medium text-gray-200"> @{{ current }} / @{{ totalUpload }}上传中...</h3>
|
||||
<p class="mt-2 text-gray-400">请稍候,正在上传图片</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 相册选择 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">选择相册</label>
|
||||
<select v-model="selectedAlbum" class="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<option value="" disabled selected>请选择相册</option>
|
||||
<option v-for="album in albums" :value="album.id">@{{ album.title }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 文件上传区域 -->
|
||||
<div class="mb-6">
|
||||
<div
|
||||
@dragover.prevent="dragActive = true"
|
||||
@dragleave="dragActive = false"
|
||||
@drop.prevent="handleDrop"
|
||||
:class="{'active': dragActive}"
|
||||
class="dropzone p-8 text-center cursor-pointer transition">
|
||||
<input type="file" multiple @change="handleFileSelect" class="hidden" ref="fileInput">
|
||||
|
||||
<div v-if="!images.length">
|
||||
<i class="fas fa-cloud-upload-alt text-5xl text-indigo-500 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-300 mb-2">拖放图片到这里</h3>
|
||||
<p class="text-gray-500 mb-4">或</p>
|
||||
<button @click="$refs.fileInput.click()" class="bg-indigo-700 text-white px-6 py-2 rounded-lg hover:bg-indigo-600 transition">
|
||||
选择图片
|
||||
</button>
|
||||
<p class="mt-3 text-gray-500 text-sm">支持 JPG, PNG 格式,最大 10MB</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button @click="$refs.fileInput.click()" class="bg-indigo-700 text-white px-6 py-2 rounded-lg hover:bg-indigo-600 transition mb-4">
|
||||
<i class="fas fa-plus mr-2"></i>添加更多图片
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片预览区域 -->
|
||||
<div v-if="images.length" class="mb-6 transition-all">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="font-medium text-gray-300">已选图片 <span class="text-indigo-400">@{{ images.length }}</span> 张</h3>
|
||||
<div>
|
||||
<button @click="clearAll" class="text-red-400 hover:text-red-300 mr-3">
|
||||
<i class="fas fa-trash mr-1"></i>清空
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-3" ref="gridContainer">
|
||||
<div
|
||||
v-for="(image, index) in images"
|
||||
:key="image.id"
|
||||
class="relative group overflow-hidden rounded-lg border border-gray-700 bg-gray-700 shadow-sm transition-transform duration-300 hover:shadow-md hover:-translate-y-0.5">
|
||||
|
||||
<!-- 排序手柄 -->
|
||||
<div class="absolute top-2 left-2 text-indigo-400 sort-handle">
|
||||
<i class="fas fa-arrows-alt"></i>
|
||||
</div>
|
||||
|
||||
<!-- 删除按钮 -->
|
||||
<button @click="removeImage(index)" class="absolute top-2 right-2 bg-gray-800 text-red-400 rounded-full w-6 h-6 flex items-center justify-center opacity-0 group-hover:opacity-100 transition">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
|
||||
<!-- 缩略图 -->
|
||||
|
||||
<img :src="image.preview" class="w-full h-full object-cover">
|
||||
|
||||
<!-- 图片信息 -->
|
||||
<div class="p-2 text-center">
|
||||
<p class="text-xs truncate text-gray-300">@{{ image.file.name }}</p>
|
||||
<p class="text-xs text-gray-500">@{{ formatFileSize(image.file.size) }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 图片编号 -->
|
||||
<div class="absolute bottom-10 right-2 bg-indigo-600 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs">
|
||||
@{{ index + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex justify-between">
|
||||
<div class="text-sm text-gray-500">
|
||||
提示:拖拽图片左上角的 <i class="fas fa-arrows-alt text-indigo-400 mx-1"></i> 可以调整顺序
|
||||
</div>
|
||||
<button
|
||||
@click="uploadImages"
|
||||
:disabled="!selectedAlbum || !images.length || isLoading"
|
||||
:class="{'opacity-70 cursor-not-allowed': !selectedAlbum || !images.length || isLoading, 'hover:bg-indigo-600': selectedAlbum && images.length}"
|
||||
class="bg-indigo-700 text-white px-6 py-2 rounded-lg transition flex items-center">
|
||||
<template v-if="isLoading">
|
||||
<i class="fas fa-spinner animate-spin mr-2"></i>
|
||||
上传中...
|
||||
</template>
|
||||
<template v-else>
|
||||
<i class="fas fa-cloud-upload-alt mr-2"></i>
|
||||
开始上传
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 上传成功提示 -->
|
||||
<transition name="fade">
|
||||
<div v-if="showSuccess" class="fixed bottom-6 right-6 bg-green-700 text-white py-3 px-6 rounded-lg shadow-lg flex items-center z-50">
|
||||
<i class="fas fa-check-circle text-xl mr-3"></i>
|
||||
<div>
|
||||
<div class="font-medium">上传成功!</div>
|
||||
<div class="text-sm">成功上传 @{{ uploadedCount }} 张图片</div>
|
||||
</div>
|
||||
<button @click="showSuccess = false" class="ml-6 text-green-300 hover:text-white">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="mt-12 text-center text-gray-500 text-sm">
|
||||
<p>Laravel 12.x 图片上传系统 © 2023 | 暗色主题</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { createApp, ref, onMounted, nextTick } = Vue;
|
||||
|
||||
createApp({
|
||||
setup() {
|
||||
// 基础变量
|
||||
const images = ref([]);
|
||||
const fileInput = ref(null);
|
||||
const dragActive = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const selectedAlbum = ref('');
|
||||
const showSuccess = ref(false);
|
||||
const uploadedCount = ref(0);
|
||||
const gridContainer = ref(null);
|
||||
let sortableInstance = null;
|
||||
const totalUpload = ref(0);
|
||||
const current = ref(0);
|
||||
|
||||
// 相册数据(模拟)
|
||||
const albums = <?php echo json_encode($photo_album); ?>;
|
||||
|
||||
// 处理文件选择
|
||||
const handleFileSelect = (e) => {
|
||||
addFiles(e.target.files);
|
||||
// 重置文件输入以允许重复选择相同文件
|
||||
e.target.value = null;
|
||||
};
|
||||
|
||||
// 处理拖放文件
|
||||
const handleDrop = (e) => {
|
||||
dragActive.value = false;
|
||||
if (e.dataTransfer.files.length) {
|
||||
addFiles(e.dataTransfer.files);
|
||||
}
|
||||
};
|
||||
|
||||
// 添加文件到列表
|
||||
const addFiles = (files) => {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
// 限制为图片格式
|
||||
if (file && file.type.includes('image/')) {
|
||||
images.value.push({
|
||||
id: Date.now() + Math.random(),
|
||||
file: file,
|
||||
preview: URL.createObjectURL(file)
|
||||
});
|
||||
}
|
||||
}
|
||||
// 添加图片后重新初始化Sortable
|
||||
initSortable();
|
||||
};
|
||||
|
||||
// 移除图片
|
||||
const removeImage = (index) => {
|
||||
URL.revokeObjectURL(images.value[index].preview);
|
||||
images.value.splice(index, 1);
|
||||
// 移除图片后重新初始化Sortable
|
||||
initSortable();
|
||||
};
|
||||
|
||||
// 清空所有图片
|
||||
const clearAll = () => {
|
||||
images.value.forEach(img => URL.revokeObjectURL(img.preview));
|
||||
images.value = [];
|
||||
// 清空后销毁Sortable实例
|
||||
destroySortable();
|
||||
};
|
||||
|
||||
// 格式化文件大小
|
||||
const formatFileSize = (bytes) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
// 图片上传
|
||||
const uploadImages = async () => {
|
||||
// 调试代码可以保留,但正式使用时建议注释掉
|
||||
console.log(images.value, '上传前的图片列表');
|
||||
|
||||
totalUpload.value = images.value.length;
|
||||
// 检查条件:相册未选择、无图片、或正在上传中
|
||||
if (!selectedAlbum.value || !images.value.length || isLoading.value) return;
|
||||
|
||||
isLoading.value = true;
|
||||
let successCount = 0;
|
||||
|
||||
try {
|
||||
// 使用 for...of 循环逐个上传
|
||||
for (const image of images.value) {
|
||||
successCount++;
|
||||
current.value = successCount;
|
||||
const formData = new FormData();
|
||||
formData.append('pid', selectedAlbum.value);
|
||||
formData.append('image', image.file);
|
||||
|
||||
// 等待当前图片上传完成
|
||||
await axios.post('/api/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`成功上传第 ${successCount} 张图片`);
|
||||
}
|
||||
|
||||
// 全部上传完成后清空列表并显示成功消息
|
||||
clearAll();
|
||||
uploadedCount.value = successCount;
|
||||
showSuccess.value = true;
|
||||
|
||||
// 5秒后自动隐藏成功消息
|
||||
setTimeout(() => {
|
||||
showSuccess.value = false;
|
||||
}, 5000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('上传过程中出错:', error);
|
||||
// 出错时不清空列表,允许用户重试
|
||||
} finally {
|
||||
isLoading.value = false; // 无论成功或失败,最终关闭加载状态
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化Sortable拖拽功能
|
||||
const initSortable = () => {
|
||||
// 确保DOM已经更新
|
||||
nextTick(() => {
|
||||
// 销毁旧的Sortable实例
|
||||
if (sortableInstance) {
|
||||
sortableInstance.destroy();
|
||||
sortableInstance = null;
|
||||
}
|
||||
|
||||
// 确保容器存在且有图片
|
||||
if (gridContainer.value && images.value.length > 0) {
|
||||
sortableInstance = new Sortable(gridContainer.value, {
|
||||
handle: '.sort-handle',
|
||||
animation: 150,
|
||||
ghostClass: 'sortable-ghost',
|
||||
onEnd: (e) => {
|
||||
// 更新数组顺序
|
||||
const movedItem = images.value.splice(e.oldIndex, 1)[0];
|
||||
images.value.splice(e.newIndex, 0, movedItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 销毁Sortable实例
|
||||
const destroySortable = () => {
|
||||
if (sortableInstance) {
|
||||
sortableInstance.destroy();
|
||||
sortableInstance = null;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 初始加载完成后初始化Sortable
|
||||
initSortable();
|
||||
});
|
||||
|
||||
return {
|
||||
images,
|
||||
albums,
|
||||
selectedAlbum,
|
||||
fileInput,
|
||||
dragActive,
|
||||
isLoading,
|
||||
showSuccess,
|
||||
uploadedCount,
|
||||
gridContainer,
|
||||
handleFileSelect,
|
||||
handleDrop,
|
||||
removeImage,
|
||||
clearAll,
|
||||
formatFileSize,
|
||||
uploadImages
|
||||
};
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -24,11 +24,7 @@ Route::get('/o', function () {
|
||||
foreach ($photoAlbum as $album) {
|
||||
$album->url = \Illuminate\Support\Facades\Storage::url($album->url);
|
||||
$album->created_at = date('Y-m-d', $album->created_at);
|
||||
foreach ($album['photos'] as &$photo) {
|
||||
$photo->original = \Illuminate\Support\Facades\Storage::url($photo->original);
|
||||
$photo->compressed = \Illuminate\Support\Facades\Storage::url($photo->compressed);
|
||||
$photo->watermarked = \Illuminate\Support\Facades\Storage::url($photo->watermarked);
|
||||
}
|
||||
$album->count = count($album['photos']);
|
||||
}
|
||||
// exit(json_encode($photoAlbum));
|
||||
return view('o-index', [
|
||||
@@ -43,10 +39,6 @@ Route::get('/detail', function () {
|
||||
$photoAlbum->url = \Illuminate\Support\Facades\Storage::url($photoAlbum->url);
|
||||
$photoAlbum->created_at = date('Y-m-d', $photoAlbum->created_at);
|
||||
foreach ($photoAlbum['photos'] as $index => &$photo) {
|
||||
// if ($index > 5) {
|
||||
// unset($photoAlbum['photos'][$index]);
|
||||
// continue;
|
||||
// }
|
||||
$photo->original = \Illuminate\Support\Facades\Storage::url($photo->original);
|
||||
$photo->compressed = \Illuminate\Support\Facades\Storage::url($photo->compressed);
|
||||
$photo->watermarked = \Illuminate\Support\Facades\Storage::url($photo->watermarked);
|
||||
@@ -57,7 +49,10 @@ Route::get('/detail', function () {
|
||||
'photo_album' => $photoAlbum,
|
||||
]);
|
||||
});
|
||||
Route::get('/images/compress', function() {
|
||||
return view('compress');
|
||||
Route::get('/upload', function() {
|
||||
$photoAlbum = \App\Models\PhotoAlbumModel::with('photos')->get();
|
||||
return view('upload',[
|
||||
'photo_album' => $photoAlbum,
|
||||
]);
|
||||
});
|
||||
Route::post('/upload', [\App\Http\Controllers\ImageController::class, 'upload']);
|
||||
|
||||
Reference in New Issue
Block a user