diff --git a/app/Http/Controllers/Admin/AdvertisementController.php b/app/Http/Controllers/Admin/AdvertisementController.php index 3b7ad67..b327127 100644 --- a/app/Http/Controllers/Admin/AdvertisementController.php +++ b/app/Http/Controllers/Admin/AdvertisementController.php @@ -102,6 +102,7 @@ class AdvertisementController extends AdminController ]); if ($request->wantsJson()) { + session()->flash('success', __('Advertisement created successfully.')); return response()->json([ 'success' => true, 'message' => __('Advertisement created successfully.'), @@ -163,6 +164,7 @@ class AdvertisementController extends AdminController $advertisement->update($data); if ($request->wantsJson()) { + session()->flash('success', __('Advertisement updated successfully.')); return response()->json([ 'success' => true, 'message' => __('Advertisement updated successfully.'), diff --git a/resources/views/admin/ads/index.blade.php b/resources/views/admin/ads/index.blade.php index ede43b7..75e6801 100644 --- a/resources/views/admin/ads/index.blade.php +++ b/resources/views/admin/ads/index.blade.php @@ -728,6 +728,8 @@ $baseRoute = 'admin.data-config.advertisements'; document.addEventListener('alpine:init', () => { Alpine.data('adManager', () => ({ isLoading: false, + isSubmitting: false, + uploadProgress: 0, activeTab: 'list', selectedMachineId: null, machines: [], @@ -1035,7 +1037,7 @@ $baseRoute = 'admin.data-config.advertisements'; const form = this.$refs.adFormEl; const formData = new FormData(form); - // Basic validation + // 基本驗證 if (!this.adForm.name) { window.showToast?.('{{ __("Please enter a name") }}', 'error'); return; @@ -1045,31 +1047,88 @@ $baseRoute = 'admin.data-config.advertisements'; return; } - try { - const url = this.adFormMode === 'add' ? this.urls.store : this.urls.update.replace(':id', this.adForm.id); - if (this.adFormMode === 'edit') formData.append('_method', 'PUT'); + // 防止重複提交 + if (this.isSubmitting) return; + this.isSubmitting = true; + this.uploadProgress = 0; - const response = await fetch(url, { - method: 'POST', - body: formData, - headers: { - 'X-CSRF-TOKEN': '{{ csrf_token() }}', - 'Accept': 'application/json' - } - }); + const url = this.adFormMode === 'add' ? this.urls.store : this.urls.update.replace(':id', this.adForm.id); + if (this.adFormMode === 'edit') formData.append('_method', 'PUT'); - const result = await response.json(); - if (result.success) { - window.showToast?.(result.message, 'success'); - this.isAdModalOpen = false; - location.reload(); // Reload to refresh the list - } else { - window.showToast?.(result.message || 'Error', 'error'); - } - } catch (e) { - console.error('Failed to submit ad', e); - window.showToast?.('System Error', 'error'); + // 取得頂部進度條並切換為真實進度模式 + const bar = document.getElementById('top-loading-bar'); + if (bar) { + bar.style.animation = 'none'; + bar.style.width = '0%'; + bar.style.opacity = '1'; + bar.classList.add('loading'); } + + const xhr = new XMLHttpRequest(); + + // 監聽上傳進度 + xhr.upload.addEventListener('progress', (e) => { + if (e.lengthComputable) { + const pct = Math.round((e.loaded / e.total) * 100); + this.uploadProgress = pct; + if (bar) { + // 最多到 95%,完成時才跳到 100% + bar.style.width = Math.min(pct, 95) + '%'; + } + } + }); + + xhr.addEventListener('load', () => { + try { + const result = JSON.parse(xhr.responseText); + if (xhr.status >= 200 && xhr.status < 300 && result.success) { + // 進度跳到 100% + if (bar) bar.style.width = '100%'; + this.uploadProgress = 100; + this.isAdModalOpen = false; + // 延遲 300ms 讓進度條動畫完成後再重整 + setTimeout(() => { + location.reload(); + }, 300); + } else { + window.showToast?.(result.message || 'Error', 'error'); + this.isSubmitting = false; + this.uploadProgress = 0; + if (bar) { + bar.style.width = '0%'; + bar.style.opacity = '0'; + bar.classList.remove('loading'); + } + } + } catch (e) { + console.error('Failed to parse response', e); + window.showToast?.('System Error', 'error'); + this.isSubmitting = false; + this.uploadProgress = 0; + if (bar) { + bar.style.width = '0%'; + bar.style.opacity = '0'; + bar.classList.remove('loading'); + } + } + }); + + xhr.addEventListener('error', () => { + console.error('Upload failed'); + window.showToast?.('{{ __("Upload failed, please try again") }}', 'error'); + this.isSubmitting = false; + this.uploadProgress = 0; + if (bar) { + bar.style.width = '0%'; + bar.style.opacity = '0'; + bar.classList.remove('loading'); + } + }); + + xhr.open('POST', url); + xhr.setRequestHeader('X-CSRF-TOKEN', '{{ csrf_token() }}'); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.send(formData); }, formatDateForInput(dateStr) { diff --git a/resources/views/admin/ads/partials/ad-modal.blade.php b/resources/views/admin/ads/partials/ad-modal.blade.php index f49f685..488db21 100644 --- a/resources/views/admin/ads/partials/ad-modal.blade.php +++ b/resources/views/admin/ads/partials/ad-modal.blade.php @@ -220,11 +220,19 @@ class="px-6 py-3 text-sm font-black text-slate-500 hover:text-slate-700 dark:hover:text-slate-300 transition-colors uppercase tracking-widest"> {{ __('Cancel') }} -