[FEAT] 重構機台狀態判定邏輯並優化全站多語系支援
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m18s
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m18s
1. 重構機台在線狀態判定機制:移除資料庫 status 欄位,改由 Model 根據心跳時間動態計算。 2. 修正儀表板 (Dashboard) 與機台管理頁面的多語系顯示問題,解決換行導致翻譯失效的 Bug。 3. 修正個人檔案頁面的麵包屑 (Breadcrumbs) 導航,補齊「個人設定」層級。 4. 更新 IoT API (B010, B600) 的認證機制與日誌處理邏輯。 5. 同步更新繁中、英文、日文語言檔,確保 UI 標籤一致性。
This commit is contained in:
@@ -3,84 +3,93 @@
|
||||
|
||||
@section('content')
|
||||
<script>
|
||||
window.machineApp = function() {
|
||||
return {
|
||||
showLogPanel: false,
|
||||
activeTab: 'status',
|
||||
currentMachineId: '',
|
||||
currentMachineSn: '',
|
||||
currentMachineName: '',
|
||||
logs: [],
|
||||
loading: false,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
tab: 'list',
|
||||
viewMode: 'fleet',
|
||||
selectedMachine: null,
|
||||
slots: [],
|
||||
window.machineApp = function () {
|
||||
return {
|
||||
showLogPanel: false,
|
||||
showEditModal: false,
|
||||
editMachineId: '',
|
||||
editMachineName: '',
|
||||
activeTab: 'status',
|
||||
currentMachineId: '',
|
||||
currentMachineSn: '',
|
||||
currentMachineName: '',
|
||||
logs: [],
|
||||
loading: false,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
tab: 'list',
|
||||
viewMode: 'fleet',
|
||||
selectedMachine: null,
|
||||
slots: [],
|
||||
|
||||
init() {
|
||||
const d = new Date();
|
||||
const today = [
|
||||
d.getFullYear(),
|
||||
String(d.getMonth() + 1).padStart(2, '0'),
|
||||
String(d.getDate()).padStart(2, '0')
|
||||
].join('-');
|
||||
this.startDate = today;
|
||||
this.endDate = today;
|
||||
this.$watch('activeTab', () => this.fetchLogs());
|
||||
},
|
||||
init() {
|
||||
const d = new Date();
|
||||
const today = [
|
||||
d.getFullYear(),
|
||||
String(d.getMonth() + 1).padStart(2, '0'),
|
||||
String(d.getDate()).padStart(2, '0')
|
||||
].join('-');
|
||||
this.startDate = today;
|
||||
this.endDate = today;
|
||||
this.$watch('activeTab', () => this.fetchLogs());
|
||||
},
|
||||
|
||||
async openLogPanel(id, sn, name) {
|
||||
this.currentMachineId = id;
|
||||
this.currentMachineSn = sn;
|
||||
this.currentMachineName = name;
|
||||
this.slots = [];
|
||||
this.showLogPanel = true;
|
||||
this.activeTab = 'status';
|
||||
await this.fetchLogs();
|
||||
},
|
||||
async openLogPanel(id, sn, name) {
|
||||
this.currentMachineId = id;
|
||||
this.currentMachineSn = sn;
|
||||
this.currentMachineName = name;
|
||||
this.slots = [];
|
||||
this.showLogPanel = true;
|
||||
this.activeTab = 'status';
|
||||
await this.fetchLogs();
|
||||
},
|
||||
|
||||
openEditModal(id, name) {
|
||||
this.editMachineId = id;
|
||||
this.editMachineName = name;
|
||||
this.showEditModal = true;
|
||||
},
|
||||
|
||||
|
||||
async fetchLogs() {
|
||||
this.loading = true;
|
||||
try {
|
||||
let url = '/admin/machines/' + this.currentMachineId + '/logs-ajax?type=' + this.activeTab;
|
||||
if (this.startDate) url += '&start_date=' + this.startDate;
|
||||
if (this.endDate) url += '&end_date=' + this.endDate;
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
if (data.success) this.logs = data.data.data || data.data || [];
|
||||
} catch(e) { console.error('fetchLogs error:', e); }
|
||||
finally { this.loading = false; }
|
||||
},
|
||||
async fetchLogs() {
|
||||
this.loading = true;
|
||||
try {
|
||||
let url = '/admin/machines/' + this.currentMachineId + '/logs-ajax?type=' + this.activeTab;
|
||||
if (this.startDate) url += '&start_date=' + this.startDate;
|
||||
if (this.endDate) url += '&end_date=' + this.endDate;
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
if (data.success) this.logs = data.data.data || data.data || [];
|
||||
} catch (e) { console.error('fetchLogs error:', e); }
|
||||
finally { this.loading = false; }
|
||||
},
|
||||
|
||||
async openCabinet(id) {
|
||||
this.loading = true;
|
||||
this.viewMode = 'cabinet';
|
||||
try {
|
||||
const res = await fetch('/admin/machines/' + id + '/slots-ajax');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
this.selectedMachine = data.machine;
|
||||
this.slots = data.slots;
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
} catch(e) { console.error('openCabinet error:', e); }
|
||||
finally { this.loading = false; }
|
||||
},
|
||||
async openCabinet(id) {
|
||||
this.loading = true;
|
||||
this.viewMode = 'cabinet';
|
||||
try {
|
||||
const res = await fetch('/admin/machines/' + id + '/slots-ajax');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
this.selectedMachine = data.machine;
|
||||
this.slots = data.slots;
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
} catch (e) { console.error('openCabinet error:', e); }
|
||||
finally { this.loading = false; }
|
||||
},
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="space-y-4 pb-20 mt-4" x-data="machineApp()"
|
||||
@keydown.escape.window="showLogPanel = false">
|
||||
<div class="space-y-4 pb-20 mt-4" x-data="machineApp()" @keydown.escape.window="showLogPanel = false">
|
||||
<!-- Top Header & Actions -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display transition-all duration-300">
|
||||
<h1
|
||||
class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display transition-all duration-300">
|
||||
{{ __('Machine List') }}
|
||||
</h1>
|
||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">
|
||||
@@ -167,9 +176,13 @@ window.machineApp = function() {
|
||||
</td>
|
||||
<td class="px-6 py-6 text-center">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
@if($machine->status === 'online')
|
||||
<div
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20">
|
||||
@php
|
||||
$cStatus = $machine->calculated_status;
|
||||
@endphp
|
||||
|
||||
@if($cStatus === 'online')
|
||||
<div class="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 tooltip"
|
||||
title="{{ __('Machine is heartbeat normal') }}">
|
||||
<div class="relative flex h-2 w-2">
|
||||
<span
|
||||
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
@@ -179,17 +192,17 @@ window.machineApp = function() {
|
||||
class="text-xs font-black text-emerald-600 dark:text-emerald-400 tracking-widest uppercase">{{
|
||||
__('Online') }}</span>
|
||||
</div>
|
||||
@elseif($machine->status === 'error')
|
||||
<div
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-rose-500/10 border border-rose-500/20">
|
||||
@elseif($cStatus === 'error')
|
||||
<div class="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-rose-500/10 border border-rose-500/20 tooltip"
|
||||
title="{{ __('Recently reported errors or warnings in logs') }}">
|
||||
<div class="h-2 w-2 rounded-full bg-rose-500 animate-pulse"></div>
|
||||
<span
|
||||
class="text-xs font-black text-rose-600 dark:text-rose-400 tracking-widest uppercase">{{
|
||||
__('Error') }}</span>
|
||||
</div>
|
||||
@else
|
||||
<div
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-slate-500/10 border border-slate-500/20">
|
||||
<div class="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-slate-500/10 border border-slate-500/20 tooltip"
|
||||
title="{{ __('No heartbeat for over 30 seconds') }}">
|
||||
<div class="h-2 w-2 rounded-full bg-slate-400"></div>
|
||||
<span
|
||||
class="text-xs font-black text-slate-500 dark:text-slate-400 tracking-widest uppercase">{{
|
||||
@@ -230,15 +243,16 @@ window.machineApp = function() {
|
||||
</td>
|
||||
<td class="px-6 py-6 text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<a href="{{ route('admin.machines.edit', $machine->id) }}"
|
||||
<button type="button"
|
||||
@click="openEditModal('{{ $machine->id }}', '{{ addslashes($machine->name) }}')"
|
||||
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 tooltip"
|
||||
title="{{ __('Edit') }}">
|
||||
title="{{ __('Edit Name') }}">
|
||||
<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="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||
</svg>
|
||||
</a>
|
||||
</button>
|
||||
<button type="button"
|
||||
@click="openLogPanel('{{ $machine->id }}', '{{ $machine->serial_no }}', '{{ addslashes($machine->name) }}')"
|
||||
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 tooltip"
|
||||
@@ -504,4 +518,57 @@ window.machineApp = function() {
|
||||
</div>
|
||||
</div><!-- /Offcanvas -->
|
||||
|
||||
@endsection
|
||||
<!-- Edit Machine Name Modal -->
|
||||
<div x-show="showEditModal" class="fixed inset-0 z-[100] overflow-y-auto" style="display: none;" role="dialog" aria-modal="true">
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<!-- Background Backdrop -->
|
||||
<div x-show="showEditModal" 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"
|
||||
class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm transition-opacity" @click="showEditModal = false">
|
||||
</div>
|
||||
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:min-h-screen" aria-hidden="true">​</span>
|
||||
|
||||
<!-- Modal Panel -->
|
||||
<div x-show="showEditModal"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
class="inline-block align-bottom bg-white dark:bg-slate-900 rounded-[2.5rem] p-10 text-left overflow-hidden shadow-2xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full border border-slate-100 dark:border-slate-800">
|
||||
|
||||
<div>
|
||||
<h3 class="text-2xl font-black text-slate-800 dark:text-white font-display tracking-tight leading-none mb-2">{{ __('Edit Machine Name') }}</h3>
|
||||
<p class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Update identification for your asset') }}</p>
|
||||
|
||||
<form :action="'/admin/machines/' + editMachineId" method="POST" class="mt-8 space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="space-y-4">
|
||||
<label class="block text-xs font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.1em]">{{ __('New Machine Name') }}</label>
|
||||
<input type="text" name="name" x-model="editMachineName" required
|
||||
class="luxury-input block w-full px-6 py-4 text-base font-bold text-slate-800 dark:text-white bg-slate-50/50 dark:bg-slate-900/50"
|
||||
placeholder="{{ __('Enter machine name...') }}">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 pt-4">
|
||||
<button type="button" @click="showEditModal = false"
|
||||
class="px-8 py-4 bg-slate-50 dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-black rounded-2xl border border-slate-200 dark:border-slate-700 hover:bg-slate-100 dark:hover:bg-slate-700 transition-all">
|
||||
{{ __('Cancel') }}
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="flex-1 bg-cyan-500 hover:bg-cyan-600 text-white font-black py-4 rounded-2xl shadow-lg shadow-cyan-500/30 transition-all duration-300 transform hover:-translate-y-0.5 active:scale-95">
|
||||
{{ __('Save Changes') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /Edit Modal -->
|
||||
|
||||
@endsection
|
||||
Reference in New Issue
Block a user