From 72f166eb1496b24ff4e840dc05f541cd59419eb6 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Wed, 27 May 2026 15:46:31 +0800 Subject: [PATCH 1/4] =?UTF-8?q?[STYLE]=20=E6=94=B9=E5=96=84=20APK=20?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=96=B0=E5=A2=9E=E9=A0=81=E9=9D=A2=20UI=20?= =?UTF-8?q?=E8=88=87=E6=8F=90=E7=A4=BA=E7=B5=B1=E4=B8=80=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 版本代碼欄位改為 ±1 Stepper 風格(`type="text" inputmode="numeric"`),移除瀏覽器原生上下箭頭 2. 機台風味下拉選單改為 `x-searchable-select` 搜尋式選單,支援關鍵字篩選並統一 UI 風格 3. 移除 index 頁面的 session success 畫面提示框,改由全域 toast 統一處理 4. 移除 create 頁面的 $errors 畫面錯誤列表,改由全域 toast 統一顯示錯誤訊息 --- .../apk-versions/create.blade.php | 54 +++++++++++-------- .../apk-versions/index.blade.php | 7 --- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/resources/views/admin/basic-settings/apk-versions/create.blade.php b/resources/views/admin/basic-settings/apk-versions/create.blade.php index fc4170f..887adc7 100644 --- a/resources/views/admin/basic-settings/apk-versions/create.blade.php +++ b/resources/views/admin/basic-settings/apk-versions/create.blade.php @@ -24,6 +24,13 @@ fileName: '', isDragging: false, isForced: false, + versionCode: '{{ old('version_code', '') }}', + + adjustVersionCode(delta) { + const current = parseInt(this.versionCode) || 0; + const next = Math.max(1, current + delta); + this.versionCode = next.toString(); + }, submitForm() { const form = this.$el; @@ -90,23 +97,6 @@ novalidate> @csrf - {{-- 錯誤提示 --}} - @if ($errors->any()) -
-
- -
-
-

{{ __('Please check the following errors:') }}

-
    - @foreach ($errors->all() as $error) -
  • {{ $error }}
  • - @endforeach -
-
-
- @endif -
{{-- 左欄:版本基本資訊 --}}
@@ -132,7 +122,21 @@ {{-- 版本代碼 --}}
- +
+ +
+ +
+ +

{{ __('Must be strictly greater than previous version') }}

@@ -140,10 +144,16 @@ {{-- 機台風味 --}}
- + + {{-- S號 / Android 12 / XY主板 × 客戶專案 --}} + + + +

{{ __('Target hardware flavor for this APK') }}

diff --git a/resources/views/admin/basic-settings/apk-versions/index.blade.php b/resources/views/admin/basic-settings/apk-versions/index.blade.php index cdf921d..543a404 100644 --- a/resources/views/admin/basic-settings/apk-versions/index.blade.php +++ b/resources/views/admin/basic-settings/apk-versions/index.blade.php @@ -135,13 +135,6 @@

{{ __('Loading Data') }}...

- @if(session('success')) -
- - {{ session('success') }} -
- @endif -
From dd5e78a9ff0dded96f36479e77888a6dec6de038 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Wed, 27 May 2026 17:17:26 +0800 Subject: [PATCH 2/4] =?UTF-8?q?[FIX]=20=E4=BF=AE=E6=AD=A3=20APK=20?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=BC=B7=E5=88=B6=E6=9B=B4=E6=96=B0=E9=96=8B?= =?UTF-8?q?=E9=97=9C=E6=B0=B8=E9=81=A0=E7=82=BA=E9=96=8B=E5=95=9F=E7=9A=84?= =?UTF-8?q?=20Bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修正 ApkVersionController::store() 中 is_forced 欄位的判斷邏輯 2. 原邏輯使用 $request->has('is_forced') 判斷,但因表單含 hidden input (value=0),導致 key 永遠存在,has() 永遠回傳 true 3. 改用 $request->boolean('is_forced') 直接讀取欄位值,正確將字串 '0' 轉為 false、'1' 轉為 true --- .../Controllers/Admin/BasicSettings/ApkVersionController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/BasicSettings/ApkVersionController.php b/app/Http/Controllers/Admin/BasicSettings/ApkVersionController.php index e284cd0..660c204 100644 --- a/app/Http/Controllers/Admin/BasicSettings/ApkVersionController.php +++ b/app/Http/Controllers/Admin/BasicSettings/ApkVersionController.php @@ -50,7 +50,7 @@ class ApkVersionController extends AdminController 'file_path' => $filePath, 'download_url' => $request->download_url, 'release_notes' => $request->release_notes, - 'is_forced' => $request->has('is_forced') || (bool) $request->input('is_forced', false), + 'is_forced' => $request->boolean('is_forced'), ]); return redirect()->route('admin.basic-settings.apk-versions.index') From b0fc0e65200c41ab80a165a3fc1b919fad269718 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Thu, 28 May 2026 09:35:31 +0800 Subject: [PATCH 3/4] =?UTF-8?q?[FEAT]=20=E6=96=B0=E5=A2=9E=20APK=20?= =?UTF-8?q?=E4=B8=8A=E5=82=B3=E6=AA=94=E5=90=8D=E8=87=AA=E5=8B=95=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E8=88=87=E5=84=AA=E5=8C=96=E4=B8=AD=E5=9C=8B=E9=86=AB?= =?UTF-8?q?=E9=A2=A8=E5=91=B3=E9=81=B8=E9=A0=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增 APK 上傳時的檔名自動解析功能(可從 app-[flavor]-[version_name] 格式自動解析並填入版本名稱與機台風味)。 2. 新增版本代碼的自動數學推導,將 Major_Minor_Patch 轉換為整數(例如 21_01_6_R 轉換為 210106)並自動填入。 3. 將原本「中國醫客製」風味選項由 S_12_XY_U_Cmuh 修正為 S_9_XY_U_Cmuh(S9 主板)。 4. 重構 create.blade.php 表單,將原本位於 HTML x-data 屬性內部的複雜 JS 邏輯抽離至獨立的 @endsection From c3e60b8746599d56d5f621ebfcae58ae650fc9b4 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Thu, 28 May 2026 11:19:00 +0800 Subject: [PATCH 4/4] =?UTF-8?q?[FEAT]=20=E6=96=B0=E5=A2=9E=E5=95=86?= =?UTF-8?q?=E5=93=81=20Excel=20=E6=89=B9=E9=87=8F=E5=8C=AF=E5=85=A5?= =?UTF-8?q?=E8=88=87=E6=A8=A1=E6=9D=BF=E4=B8=8B=E8=BC=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增 ProductImportService 服務,實作 Excel 商品檔案解析、欄位驗證、多租戶 company_id 歸屬處理及資料批量寫入/更新邏輯。 2. 在 ProductController 新增 import 與 downloadTemplate 動作,處理檔案接收驗證與匯出,並在匯入後自動重建該公司商品目錄快取。 3. routes/web.php 新增商品下載模板與 Excel 匯入的專用路由。 4. 重構 products/index.blade.php,新增極簡奢華風商品匯入 Modal、檔案拖放/選擇區,並採用 Alpine.js 進行彈窗開啟與非同步上傳 API 交互。 5. 擴充 components/page-header.blade.php,使頁面主標頭支援渲染多個操作按鈕 (actions 插槽),以配合新增的「匯入商品」按鈕。 6. 同步更新 lang/zh_TW.json 與 lang/en.json,對齊「商品匯入成功」、「請選擇公司」等相關語系翻譯。 7. 微調 AdvertisementController,維持全站 Controller 進度對齊。 --- .../Admin/AdvertisementController.php | 5 +- .../Controllers/Admin/ProductController.php | 40 ++ app/Services/ProductImportService.php | 348 ++++++++++++++++++ lang/en.json | 6 + lang/zh_TW.json | 6 + .../views/admin/products/index.blade.php | 233 +++++++++++- .../products/partials/tab-products.blade.php | 4 +- .../views/components/page-header.blade.php | 6 +- routes/web.php | 2 + 9 files changed, 632 insertions(+), 18 deletions(-) create mode 100644 app/Services/ProductImportService.php diff --git a/app/Http/Controllers/Admin/AdvertisementController.php b/app/Http/Controllers/Admin/AdvertisementController.php index 2d51b71..801c84a 100644 --- a/app/Http/Controllers/Admin/AdvertisementController.php +++ b/app/Http/Controllers/Admin/AdvertisementController.php @@ -19,6 +19,9 @@ class AdvertisementController extends AdminController { $user = auth()->user(); $tab = $request->input('tab', 'list'); + $perPage = in_array((int) $request->input('per_page', 10), [10, 25, 50, 100], true) + ? (int) $request->input('per_page', 10) + : 10; // Tab 1: 廣告列表 $query = Advertisement::with('company'); @@ -36,7 +39,7 @@ class AdvertisementController extends AdminController $query->where('company_id', $request->company_id); } - $advertisements = $query->latest()->paginate(10); + $advertisements = $query->latest()->paginate($perPage)->withQueryString(); // Tab 2: 機台廣告設置 (所需資料) - 隱藏已過期的廣告 $allAds = Advertisement::playing()->get(); diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index cc1cba1..391b364 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -13,6 +13,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use App\Services\Product\ProductCatalogService; +use App\Services\ProductImportService; use App\Models\System\SystemOperationLog; class ProductController extends Controller @@ -87,6 +88,7 @@ class ProductController extends Controller 'success' => true, 'html' => view('admin.products.partials.tab-products', [ 'products' => $products, + 'categories' => $categories, 'companySettings' => $companySettings, 'companies' => $companies, 'routeName' => $routeName @@ -540,6 +542,44 @@ class ProductController extends Controller } } + public function downloadTemplate(ProductImportService $importService) + { + return $importService->downloadTemplate(); + } + + public function import(Request $request, ProductImportService $importService) + { + $request->validate([ + 'file' => 'required|file|mimes:xlsx|max:5120', + 'company_id' => 'nullable|exists:companies,id', + ]); + + $companyId = $request->get('company_id') ?: auth()->user()->company_id; + + if (auth()->user()->isSystemAdmin() && !$companyId) { + return response()->json([ + 'success' => false, + 'message' => __('Please select a company'), + ], 422); + } + + $results = $importService->import( + $request->file('file')->getRealPath(), + $companyId, + auth()->id() + ); + + foreach ($results['company_ids'] as $importedCompanyId) { + $this->catalogService->rebuildCache($importedCompanyId); + } + + return response()->json([ + 'success' => true, + 'message' => __(':count products imported successfully', ['count' => $results['success_count']]), + 'results' => $results, + ]); + } + /** * 同步商品目錄至全公司機台 (Phase 2) */ diff --git a/app/Services/ProductImportService.php b/app/Services/ProductImportService.php new file mode 100644 index 0000000..ad80d60 --- /dev/null +++ b/app/Services/ProductImportService.php @@ -0,0 +1,348 @@ +open($filePath); + + $results = [ + 'success_count' => 0, + 'created_count' => 0, + 'updated_count' => 0, + 'error_count' => 0, + 'errors' => [], + 'company_ids' => [], + ]; + + $rowCount = 0; + + foreach ($reader->getSheetIterator() as $sheet) { + foreach ($sheet->getRowIterator() as $row) { + $rowCount++; + + if ($rowCount === 1) { + continue; + } + + $cells = $row->toArray(); + $data = $this->normalizeRow($cells, $companyId); + + if ($this->isEmptyRow($data)) { + continue; + } + + $validator = Validator::make($data, [ + 'company_id' => 'nullable|exists:companies,id', + 'category' => 'nullable|string|max:255', + 'name_zh_TW' => 'required|string|max:255', + 'name_en' => 'nullable|string|max:255', + 'name_ja' => 'nullable|string|max:255', + 'barcode' => 'nullable|string|max:100', + 'spec' => 'nullable|string|max:255', + 'manufacturer' => 'nullable|string|max:255', + 'price' => 'required|numeric|min:0', + 'member_price' => 'nullable|numeric|min:0', + 'cost' => 'required|numeric|min:0', + 'track_limit' => 'nullable|integer|min:0', + 'spring_limit' => 'nullable|integer|min:0', + 'material_code' => 'nullable|string|max:255', + 'points_full' => 'nullable|integer|min:0', + 'points_half' => 'nullable|integer|min:0', + 'points_half_amount' => 'nullable|integer|min:0', + 'is_active' => 'nullable|boolean', + ]); + + if ($validator->fails()) { + $this->appendError($results, $rowCount, $validator->errors()->all()); + continue; + } + + try { + DB::transaction(function () use ($data, $rowCount, $userId, &$results) { + $categoryId = $this->resolveCategoryId($data['category'], $data['company_id']); + $lookup = $this->buildProductLookup($data); + $product = Product::withoutGlobalScopes()->where($lookup)->first(); + $oldValues = $product?->toArray(); + $dictKey = $product?->name_dictionary_key ?: Str::uuid()->toString(); + + $this->syncTranslations($dictKey, $data, $data['company_id']); + + $payload = [ + 'company_id' => $data['company_id'], + 'category_id' => $categoryId, + 'name' => $data['name_zh_TW'], + 'name_dictionary_key' => $dictKey, + 'barcode' => $data['barcode'], + 'spec' => $data['spec'], + 'manufacturer' => $data['manufacturer'], + 'track_limit' => $data['track_limit'], + 'spring_limit' => $data['spring_limit'], + 'price' => $data['price'], + 'cost' => $data['cost'], + 'member_price' => $data['member_price'], + 'metadata' => [ + 'material_code' => $data['material_code'], + 'points_full' => $data['points_full'], + 'points_half' => $data['points_half'], + 'points_half_amount' => $data['points_half_amount'], + ], + 'is_active' => $data['is_active'], + ]; + + if ($product) { + $product->update($payload); + $results['updated_count']++; + $action = 'update'; + $note = 'product_import_updated'; + } else { + $product = Product::withoutGlobalScopes()->create($payload); + $results['created_count']++; + $action = 'create'; + $note = 'product_import_created'; + } + + $results['success_count']++; + if ($product->company_id) { + $results['company_ids'][$product->company_id] = $product->company_id; + } + + SystemOperationLog::create([ + 'company_id' => $product->company_id, + 'user_id' => $userId, + 'module' => 'product', + 'action' => $action, + 'target_id' => $product->id, + 'target_type' => Product::class, + 'note' => $note, + 'old_values' => $oldValues, + 'new_values' => array_merge($product->fresh()->toArray(), ['import_row' => $rowCount]), + ]); + }); + } catch (\Exception $e) { + $this->appendError($results, $rowCount, [$e->getMessage()]); + } + } + } + + $reader->close(); + $results['company_ids'] = array_values($results['company_ids']); + + return $results; + } + + public function downloadTemplate() + { + $writer = new Writer(); + $writer->openToBrowser('products_template.xlsx'); + + $writer->addRow(Row::fromValues([ + '分類 ID 或名稱(選填)', + '商品名稱 zh_TW(必填)', + '商品名稱 en(選填)', + '商品名稱 ja(選填)', + '商品條碼(選填,用於更新)', + '規格(選填)', + '生產公司(選填)', + '售價(必填)', + '會員價(選填)', + '成本(必填)', + '履帶貨道上限(選填,預設 15)', + '彈簧貨道上限(選填,預設 15)', + '物料代碼(選填)', + '全額點數(選填)', + '半額點數(選填)', + '半額點數折抵金額(選填)', + '是否啟用(1/0,選填)', + ])); + + $writer->addRow(Row::fromValues([ + '飲料', + '經典拿鐵', + 'Classic Latte', + 'クラシックラテ', + '471000000001', + '350ml', + 'Star Cloud', + 80, + 75, + 45, + 15, + 15, + 'MAT-LATTE-001', + 800, + 400, + 40, + 1, + ])); + + $writer->close(); + } + + private function normalizeRow(array $cells, ?int $companyId): array + { + return [ + 'company_id' => $companyId, + 'category' => $this->stringValue($cells[0] ?? null), + 'name_zh_TW' => $this->stringValue($cells[1] ?? null), + 'name_en' => $this->stringValue($cells[2] ?? null), + 'name_ja' => $this->stringValue($cells[3] ?? null), + 'barcode' => $this->stringValue($cells[4] ?? null), + 'spec' => $this->stringValue($cells[5] ?? null), + 'manufacturer' => $this->stringValue($cells[6] ?? null), + 'price' => $this->numericValue($cells[7] ?? null, null), + 'member_price' => $this->numericValue($cells[8] ?? null, 0), + 'cost' => $this->numericValue($cells[9] ?? null, null), + 'track_limit' => (int) $this->numericValue($cells[10] ?? null, 15), + 'spring_limit' => (int) $this->numericValue($cells[11] ?? null, 15), + 'material_code' => $this->stringValue($cells[12] ?? null), + 'points_full' => (int) $this->numericValue($cells[13] ?? null, 0), + 'points_half' => (int) $this->numericValue($cells[14] ?? null, 0), + 'points_half_amount' => (int) $this->numericValue($cells[15] ?? null, 0), + 'is_active' => $this->booleanValue($cells[16] ?? null), + ]; + } + + private function isEmptyRow(array $data): bool + { + return empty($data['category']) + && empty($data['name_zh_TW']) + && empty($data['barcode']) + && $data['price'] === null + && $data['cost'] === null; + } + + private function resolveCategoryId(?string $category, ?int $companyId): ?int + { + if (!$category) { + return null; + } + + $query = ProductCategory::withoutGlobalScopes() + ->where(function ($q) use ($companyId) { + $q->where('company_id', $companyId)->orWhereNull('company_id'); + }); + + if (ctype_digit($category)) { + $found = (clone $query)->where('id', (int) $category)->first(); + if ($found) { + return $found->id; + } + } + + $found = (clone $query) + ->where(function ($q) use ($category) { + $q->where('name', $category) + ->orWhereHas('translations', function ($translationQuery) use ($category) { + $translationQuery->withoutGlobalScopes()->where('value', $category); + }); + }) + ->first(); + + if (!$found) { + throw new \InvalidArgumentException(__('Category not found: :category', ['category' => $category])); + } + + return $found->id; + } + + private function buildProductLookup(array $data): array + { + if (!empty($data['barcode'])) { + return [ + 'company_id' => $data['company_id'], + 'barcode' => $data['barcode'], + ]; + } + + return [ + 'company_id' => $data['company_id'], + 'name' => $data['name_zh_TW'], + ]; + } + + private function syncTranslations(string $dictKey, array $data, ?int $companyId): void + { + $names = [ + 'zh_TW' => $data['name_zh_TW'], + 'en' => $data['name_en'], + 'ja' => $data['name_ja'], + ]; + + foreach ($names as $locale => $name) { + if (empty($name)) { + Translation::withoutGlobalScopes()->where([ + 'group' => 'product', + 'key' => $dictKey, + 'locale' => $locale, + ])->delete(); + continue; + } + + Translation::withoutGlobalScopes()->updateOrCreate( + [ + 'group' => 'product', + 'key' => $dictKey, + 'locale' => $locale, + ], + [ + 'value' => $name, + 'company_id' => $companyId, + ] + ); + } + } + + private function appendError(array &$results, int $rowCount, array $messages): void + { + $results['error_count']++; + $results['errors'][] = [ + 'row' => $rowCount, + 'messages' => $messages, + ]; + } + + private function stringValue(mixed $value): ?string + { + $value = trim((string) $value); + return $value === '' ? null : $value; + } + + private function numericValue(mixed $value, mixed $default): mixed + { + if ($value === null || $value === '') { + return $default; + } + + return is_numeric($value) ? $value : $value; + } + + private function booleanValue(mixed $value): bool + { + if ($value === null || $value === '') { + return true; + } + + if (is_bool($value)) { + return $value; + } + + $normalized = strtolower(trim((string) $value)); + + return in_array($normalized, ['1', 'true', 'yes', 'y', 'active', 'enabled', '啟用', '是'], true); + } +} diff --git a/lang/en.json b/lang/en.json index b8194f2..6dfb8d3 100644 --- a/lang/en.json +++ b/lang/en.json @@ -6,6 +6,7 @@ "60 Seconds": "60 Seconds", "60s": "60s", ":count staff cards imported successfully": ":count staff cards imported successfully", + ":count products imported successfully": ":count products imported successfully", "A new verification link has been sent to your email address.": "A new verification link has been sent to your email address.", "A sync command was recently sent. Please wait 1 minute.": "A sync command was recently sent. Please wait 1 minute.", "AI Prediction": "AI Prediction", @@ -256,6 +257,7 @@ "Cash Module Function": "Cash Module Function", "Category": "Category", "Category Management": "Category Management", + "Category not found: :category": "Category not found: :category", "Category Name": "Category Name", "Category Name (en)": "Category Name (en)", "Category Name (ja)": "Category Name (ja)", @@ -809,8 +811,10 @@ "Image Downloaded": "Image Downloaded", "Immediate": "Immediate", "Import Excel": "Import Excel", + "Import Products": "Import Products", "Import Staff Cards": "Import Staff Cards", "Import failed": "Import failed", + "Imported with errors. Please check the console.": "Imported with errors. Please check the console.", "Important Notice": "Important Notice", "In Stock": "In Stock", "Inactive": "Inactive", @@ -1225,6 +1229,7 @@ "Online Machines": "Online Machines", "Online Only": "Online Only", "Online Status": "Online Status", + "Only .xlsx files are supported (Max 5MB)": "Only .xlsx files are supported (Max 5MB)", "Only .xlsx, .xls, .csv files are supported (Max 5MB)": "Only .xlsx, .xls, .csv files are supported (Max 5MB)", "Only machines under the same company can be cloned for security.": "Only machines under the same company can be cloned for security.", "Only system roles can be assigned to platform administrative accounts.": "Only system roles can be assigned to platform administrative accounts.", @@ -1745,6 +1750,7 @@ "Standby Ad": "Standby Ad", "Start Date": "Start Date", "Start Delivery": "Start Delivery", + "Processing...": "Processing...", "Start Import": "Start Import", "Start Time": "Start Time", "Statistics": "Statistics", diff --git a/lang/zh_TW.json b/lang/zh_TW.json index 4fd8677..7805b7f 100644 --- a/lang/zh_TW.json +++ b/lang/zh_TW.json @@ -6,6 +6,7 @@ "60 Seconds": "60 秒", "60s": "60秒", ":count staff cards imported successfully": "成功匯入 :count 張員工識別卡", + ":count products imported successfully": "成功匯入 :count 筆商品", "A new verification link has been sent to your email address.": "已將新的驗證連結發送至您的電子郵件地址。", "A sync command was recently sent. Please wait 1 minute.": "近期已發送過同步指令,請等待 1 分鐘後再試", "AI Prediction": "AI智能預測", @@ -256,6 +257,7 @@ "Cash Module Function": "現金模組功能", "Category": "類別", "Category Management": "分類管理", + "Category not found: :category": "找不到商品分類::category", "Category Name": "分類名稱", "Category Name (en)": "分類名稱 (英文)", "Category Name (ja)": "分類名稱 (日文)", @@ -809,8 +811,10 @@ "Image Downloaded": "圖片已下載", "Immediate": "立即", "Import Excel": "Excel 匯入", + "Import Products": "匯入商品", "Import Staff Cards": "匯入員工識別卡", "Import failed": "匯入失敗", + "Imported with errors. Please check the console.": "部分資料匯入失敗,請查看瀏覽器主控台。", "Important Notice": "重要通知", "In Stock": "當前庫存", "Inactive": "已停用", @@ -1225,6 +1229,7 @@ "Online Machines": "在線機台", "Online Only": "僅限線上機台", "Online Status": "在線狀態", + "Only .xlsx files are supported (Max 5MB)": "僅支援 .xlsx 檔案 (最大 5MB)", "Only .xlsx, .xls, .csv files are supported (Max 5MB)": "僅支援 .xlsx, .xls, .csv 檔案 (最大 5MB)", "Only machines under the same company can be cloned for security.": "基於安全性,僅限複製同公司旗下的機台設定。", "Only system roles can be assigned to platform administrative accounts.": "僅系統角色可指派給平台管理帳號。", @@ -1745,6 +1750,7 @@ "Standby Ad": "待機廣告", "Start Date": "起始日", "Start Delivery": "開始配送", + "Processing...": "處理中...", "Start Import": "開始匯入", "Start Time": "開始時間", "Statistics": "數據統計", diff --git a/resources/views/admin/products/index.blade.php b/resources/views/admin/products/index.blade.php index fff5ba1..b022608 100644 --- a/resources/views/admin/products/index.blade.php +++ b/resources/views/admin/products/index.blade.php @@ -23,23 +23,34 @@ hover:bg-slate-100 dark:hover:bg-white/5 rounded-lg flex items-center justify-be {{-- Page Header --}} + :subtitle="__('Manage your catalog, prices, and multilingual details.')" + container-class="flex flex-wrap md:flex-nowrap items-center justify-between gap-4" + actions-class="contents md:flex md:items-center md:gap-3 md:shrink-0">