1. 優化「出貨紀錄」介面,重新排列欄位優先級並移除冗餘的機台流水號。 2. 統一出貨紀錄中的商品與貨道樣式,與取貨碼模組視覺語言同步。 3. 修正出貨紀錄搜尋邏輯,支援商品名稱與貨道編號模糊搜尋。 4. 修正「訂單詳情」側滑面板中的金額顯示(修正 price/subtotal 映射)。 5. 補強訂單詳情的出貨狀態 (dispense_status) 標籤顯示邏輯。 6. 將出貨紀錄中的「數量」表頭修正為「金額」,確保與 MQTT 協議回傳資料語意一致。 7. 更新多語系檔,新增搜尋欄位 placeholder 與相關 UI 標籤翻譯。 8. 新增發票資料表 machine_time 欄位遷移檔,以完整記錄機台開立時間。
204 lines
8.4 KiB
PHP
204 lines
8.4 KiB
PHP
@extends('layouts.admin')
|
|
|
|
@section('content')
|
|
<div class="space-y-2 pb-20" x-data="salesCenter()"
|
|
@ajax:navigate.window.prevent="fetchTabData(activeTab, $event.detail.url)">
|
|
|
|
{{-- Page Header --}}
|
|
<x-page-header :title="$title" :subtitle="$description" />
|
|
|
|
{{-- Tab Navigation --}}
|
|
<x-tab-nav model="activeTab">
|
|
<x-tab-nav-item value="orders" :label="__('Transaction Orders')" model="activeTab" />
|
|
<x-tab-nav-item value="invoices" :label="__('Electronic Invoices')" model="activeTab" />
|
|
<x-tab-nav-item value="dispense" :label="__('Dispense Records')" model="activeTab" />
|
|
</x-tab-nav>
|
|
|
|
{{-- Tab Contents --}}
|
|
<div class="mt-6">
|
|
<div class="relative min-h-[400px]">
|
|
{{-- Orders Tab --}}
|
|
<div x-show="activeTab === 'orders'" class="relative"
|
|
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4"
|
|
x-transition:enter-end="opacity-100 translate-y-0">
|
|
<div class="luxury-card rounded-3xl p-8 relative overflow-hidden">
|
|
<x-luxury-spinner show="tabLoading === 'orders'" />
|
|
<div id="tab-orders-container" :class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': tabLoading === 'orders' }">
|
|
@include('admin.sales.partials.tab-orders')
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Invoices Tab --}}
|
|
<div x-show="activeTab === 'invoices'" class="relative"
|
|
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4"
|
|
x-transition:enter-end="opacity-100 translate-y-0" x-cloak>
|
|
<div class="luxury-card rounded-3xl p-8 relative overflow-hidden">
|
|
<x-luxury-spinner show="tabLoading === 'invoices'" />
|
|
<div id="tab-invoices-container" :class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': tabLoading === 'invoices' }">
|
|
@include('admin.sales.partials.tab-invoices')
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Dispense Records Tab --}}
|
|
<div x-show="activeTab === 'dispense'" class="relative"
|
|
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4"
|
|
x-transition:enter-end="opacity-100 translate-y-0" x-cloak>
|
|
<div class="luxury-card rounded-3xl p-8 relative overflow-hidden">
|
|
<x-luxury-spinner show="tabLoading === 'dispense'" />
|
|
<div id="tab-dispense-container" :class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': tabLoading === 'dispense' }">
|
|
@include('admin.sales.partials.tab-dispense')
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Slide-over Panel (Order Details) --}}
|
|
<div
|
|
class="fixed inset-0 z-[100] overflow-hidden"
|
|
x-show="showPanel"
|
|
x-cloak
|
|
@keydown.window.escape="showPanel = false"
|
|
>
|
|
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm transition-opacity"
|
|
x-show="showPanel"
|
|
x-transition:enter="ease-out duration-500"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="ease-in duration-500"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="showPanel = false"
|
|
></div>
|
|
|
|
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
|
|
<div
|
|
class="pointer-events-auto w-screen max-w-2xl transform transition ease-in-out duration-500 sm:duration-700"
|
|
x-show="showPanel"
|
|
x-transition:enter="transform transition ease-in-out duration-500 sm:duration-700"
|
|
x-transition:enter-start="translate-x-full"
|
|
x-transition:enter-end="translate-x-0"
|
|
x-transition:leave="transform transition ease-in-out duration-500 sm:duration-700"
|
|
x-transition:leave-start="translate-x-0"
|
|
x-transition:leave-end="translate-x-full"
|
|
>
|
|
<div class="flex h-full flex-col overflow-y-scroll bg-white dark:bg-slate-900 shadow-2xl border-l border-slate-100 dark:border-slate-800">
|
|
<div id="order-detail-content">
|
|
{{-- AJAX injected content --}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@section('scripts')
|
|
<script>
|
|
function salesCenter() {
|
|
return {
|
|
showPanel: false,
|
|
activeTab: '{{ $tab }}',
|
|
tabLoading: null,
|
|
|
|
init() {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const urlTab = params.get('tab');
|
|
if (urlTab && ['orders', 'invoices', 'dispense'].includes(urlTab)) {
|
|
this.activeTab = urlTab;
|
|
}
|
|
|
|
// 監聽頁籤切換以更新網址列 (不觸發重整)
|
|
this.$watch('activeTab', (newTab) => {
|
|
const url = new URL(window.location.origin + window.location.pathname);
|
|
url.searchParams.set('tab', newTab);
|
|
window.history.pushState({}, '', url);
|
|
});
|
|
},
|
|
|
|
switchTab(tab, url = null) {
|
|
if (this.activeTab === tab && !url) return;
|
|
this.activeTab = tab;
|
|
|
|
// 如果有提供 URL (例如點擊跳轉),則使用 AJAX 載入新內容
|
|
if (url) {
|
|
this.fetchTabData(tab, url);
|
|
return;
|
|
}
|
|
},
|
|
|
|
async fetchTabData(tab, url = null) {
|
|
if (this.tabLoading === tab) return;
|
|
this.tabLoading = tab;
|
|
const container = document.getElementById('tab-' + tab + '-container');
|
|
|
|
if (!url) {
|
|
// 從表單建構 URL
|
|
let params = new URLSearchParams();
|
|
params.set('tab', tab);
|
|
|
|
if (container) {
|
|
const form = container.querySelector('form');
|
|
if (form) {
|
|
const formData = new FormData(form);
|
|
formData.forEach((value, key) => {
|
|
if (value && value.trim() !== '') params.append(key, value);
|
|
});
|
|
}
|
|
}
|
|
url = `${window.location.pathname}?${params.toString()}`;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
const data = await response.json();
|
|
if (data.success && data.html) {
|
|
if (container) container.innerHTML = data.html;
|
|
|
|
// 更新網址列但不重整頁面
|
|
window.history.pushState({}, '', url);
|
|
|
|
// 重新初始化 Preline 組件
|
|
this.$nextTick(() => {
|
|
if (window.HSStaticMethods) window.HSStaticMethods.autoInit();
|
|
});
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to fetch tab data:', err);
|
|
} finally {
|
|
this.tabLoading = null;
|
|
}
|
|
},
|
|
|
|
openDetail(orderId) {
|
|
this.showPanel = true;
|
|
document.getElementById('order-detail-content').innerHTML = `<div class="flex items-center justify-center h-full py-20"><x-luxury-spinner show="true" /></div>`;
|
|
|
|
fetch(`/admin/sales/${orderId}`, {
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('order-detail-content').innerHTML = data.html;
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('Failed to load order detail:', err);
|
|
this.showPanel = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
@endsection
|