[FEAT] 實作維修管理模組與 RBAC 權限整合、多語系支援及 UI 優化
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m3s
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m3s
This commit is contained in:
@@ -15,6 +15,15 @@
|
||||
deletedPhotos: [false, false, false],
|
||||
showImageLightbox: false,
|
||||
lightboxImageUrl: '',
|
||||
showMaintenanceQrModal: false,
|
||||
maintenanceQrMachineName: '',
|
||||
maintenanceQrUrl: '',
|
||||
openMaintenanceQr(machine) {
|
||||
this.maintenanceQrMachineName = machine.name;
|
||||
const baseUrl = '{{ route('admin.maintenance.create', ['serial_no' => 'SERIAL_NO']) }}';
|
||||
this.maintenanceQrUrl = baseUrl.replace('SERIAL_NO', machine.serial_no);
|
||||
this.showMaintenanceQrModal = true;
|
||||
},
|
||||
openDetail(machine) {
|
||||
this.currentMachine = machine;
|
||||
this.showDetailDrawer = true;
|
||||
@@ -207,6 +216,14 @@
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-6 text-right flex items-center justify-end gap-2">
|
||||
<button @click="openMaintenanceQr(@js($machine->only(['name', 'serial_no'])))"
|
||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-emerald-500 hover:bg-emerald-500/5 dark:hover:bg-emerald-500/10 border border-transparent hover:border-emerald-500/20 transition-all inline-flex group/btn"
|
||||
title="{{ __('Maintenance QR Code') }}">
|
||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 6.75h.75v.75h-.75v-.75zM6.75 16.5h.75v.75h-.75v-.75zM16.5 6.75h.75v.75h-.75v-.75zM13.5 13.5h.75v.75h-.75v-.75zM13.5 19.5h.75v.75h-.75v-.75zM19.5 13.5h.75v.75h-.75v-.75zM19.5 19.5h.75v.75h-.75v-.75zM16.5 16.5h.75v.75h-.75v-.75z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button @click="openPhotoModal(@js($machine->only(['id', 'name', 'image_urls'])))"
|
||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all inline-flex group/btn"
|
||||
title="{{ __('Machine Images') }}">
|
||||
@@ -689,28 +706,90 @@
|
||||
|
||||
<!-- 4.1 Image Lightbox Modal -->
|
||||
<template x-teleport="body">
|
||||
<div x-show="showImageLightbox" class="fixed inset-0 z-[200] flex items-center justify-center p-4 sm:p-10" x-cloak>
|
||||
<div x-show="showImageLightbox" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" @click="showImageLightbox = false"
|
||||
class="absolute inset-0 bg-slate-900/90 backdrop-blur-md transition-opacity">
|
||||
<div x-show="showImageLightbox"
|
||||
class="fixed inset-0 z-[200] flex items-center justify-center p-4 md:p-12"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
x-cloak>
|
||||
|
||||
<!-- Backdrop -->
|
||||
<div class="absolute inset-0 bg-slate-950/90 backdrop-blur-xl" @click="showImageLightbox = false"></div>
|
||||
|
||||
<!-- Close Button -->
|
||||
<button @click="showImageLightbox = false"
|
||||
class="absolute top-6 right-6 p-3 rounded-full bg-white/10 hover:bg-white/20 text-white backdrop-blur-md transition-all duration-300 z-10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Image Container -->
|
||||
<div class="relative max-w-5xl w-full max-h-full flex items-center justify-center p-4 animate-luxury-in"
|
||||
@click.away="showImageLightbox = false">
|
||||
<img :src="lightboxImageUrl"
|
||||
class="max-w-full max-h-[85vh] rounded-3xl shadow-2xl border border-white/10 ring-1 ring-white/5 object-contain"
|
||||
x-show="showImageLightbox"
|
||||
x-transition:enter="transition ease-out duration-500 delay-100"
|
||||
x-transition:enter-start="scale-95 opacity-0"
|
||||
x-transition:enter-end="scale-100 opacity-100">
|
||||
</div>
|
||||
|
||||
<!-- Helper text -->
|
||||
<div class="absolute bottom-8 left-1/2 -translate-x-1/2 text-white/40 text-[10px] font-bold uppercase tracking-[0.3em] pointer-events-none">
|
||||
{{ __('Click anywhere to close') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div x-show="showImageLightbox" x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="relative max-w-5xl w-full max-h-full flex items-center justify-center z-[210]">
|
||||
|
||||
<button @click="showImageLightbox = false"
|
||||
class="absolute -top-12 right-0 p-2 text-white/50 hover:text-white transition-colors">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<img :src="lightboxImageUrl"
|
||||
class="max-w-full max-h-[85vh] rounded-2xl shadow-2xl border border-white/10 object-contain">
|
||||
<!-- 4.2 Maintenance QR Modal -->
|
||||
<template x-teleport="body">
|
||||
<div x-show="showMaintenanceQrModal" class="fixed inset-0 z-[200] overflow-y-auto" x-cloak
|
||||
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0">
|
||||
<div class="flex items-center justify-center min-h-screen px-4">
|
||||
<div class="fixed inset-0 transition-opacity" @click="showMaintenanceQrModal = false">
|
||||
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm"></div>
|
||||
</div>
|
||||
|
||||
<div class="relative bg-white dark:bg-slate-900 rounded-[2.5rem] shadow-2xl border border-slate-100 dark:border-slate-800 w-full max-w-sm overflow-hidden animate-luxury-in">
|
||||
<div class="px-8 py-6 border-b border-slate-50 dark:border-slate-800/50 flex justify-between items-center">
|
||||
<div>
|
||||
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Maintenance QR') }}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1" x-text="maintenanceQrMachineName"></p>
|
||||
</div>
|
||||
<button @click="showMaintenanceQrModal = false" class="text-slate-400 hover:text-slate-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-10 flex flex-col items-center gap-6">
|
||||
<div class="p-4 bg-white rounded-3xl shadow-xl border border-slate-100">
|
||||
<img :src="'https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=' + encodeURIComponent(maintenanceQrUrl)"
|
||||
class="w-48 h-48"
|
||||
alt="{{ __('Maintenance QR Code') }}">
|
||||
</div>
|
||||
<div class="text-center space-y-2">
|
||||
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 leading-relaxed px-4">
|
||||
{{ __('Scan this code to quickly access the maintenance form for this device.') }}
|
||||
</p>
|
||||
<div class="mt-4 p-3 bg-slate-50 dark:bg-slate-800 rounded-xl border border-slate-100 dark:border-slate-700">
|
||||
<code class="text-[10px] break-all text-cyan-600 dark:text-cyan-400 font-bold" x-text="maintenanceQrUrl"></code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-8 py-6 bg-slate-50 dark:bg-slate-900/50 flex justify-center border-t border-slate-100 dark:border-slate-800">
|
||||
<button @click="showMaintenanceQrModal = false" class="btn-luxury-primary w-full py-4 rounded-2xl">{{ __('Close') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -755,16 +834,12 @@
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<template x-for="(url, index) in currentMachine.image_urls" :key="index">
|
||||
<div @click="lightboxImageUrl = url; showImageLightbox = true"
|
||||
class="relative group aspect-square rounded-2xl overflow-hidden border border-slate-100 dark:border-slate-800 shadow-sm bg-slate-50 dark:bg-slate-800/50 cursor-pointer">
|
||||
class="relative group aspect-square rounded-2xl overflow-hidden border border-slate-100 dark:border-slate-800 shadow-sm bg-slate-50 dark:bg-slate-800/50 cursor-zoom-in hover:ring-2 hover:ring-cyan-500/50 transition-all duration-300 group/img">
|
||||
<img :src="url"
|
||||
class="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-110">
|
||||
<div
|
||||
class="absolute inset-0 bg-slate-900/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" />
|
||||
class="absolute inset-0 w-full h-full object-cover group-hover/img:scale-105 transition-transform duration-500">
|
||||
<div class="absolute inset-0 bg-slate-900/0 group-hover/img:bg-slate-900/20 flex items-center justify-center opacity-0 group-hover/img:opacity-100 transition-all duration-300">
|
||||
<svg class="w-6 h-6 text-white drop-shadow-md" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user