[FEAT] 商品管理模組重構、UI 清晰度優化與多語系標籤字體調整

This commit is contained in:
2026-03-26 17:32:15 +08:00
parent ac51027dda
commit 8ec5473ec7
12 changed files with 1152 additions and 9 deletions

View File

@@ -0,0 +1,236 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Product\Product;
use App\Models\Product\ProductCategory;
use App\Models\System\Company;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class ProductController extends Controller
{
public function index(Request $request)
{
$user = auth()->user();
$query = Product::with(['category', 'translations', 'company']);
// 租戶隔離由 Global Scope (TenantScoped) 處理,但系統管理員可額外篩選
if ($user->isSystemAdmin() && $request->filled('company_id')) {
$query->where('company_id', $request->company_id);
}
// 搜尋
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('barcode', 'like', "%{$search}%")
->orWhere('spec', 'like', "%{$search}%");
});
}
// 分類篩選
if ($request->filled('category_id')) {
$query->where('category_id', $request->category_id);
}
$per_page = $request->input('per_page', 10);
$products = $query->latest()->paginate($per_page)->withQueryString();
$categories = ProductCategory::all();
$companies = $user->isSystemAdmin() ? Company::all() : collect();
$companySettings = $user->company ? ($user->company->settings ?? []) : [];
$routeName = 'admin.data-config.products.index';
return view('admin.products.index', [
'products' => $products,
'categories' => $categories,
'companies' => $companies,
'companySettings' => $companySettings,
'routeName' => $routeName
]);
}
public function store(Request $request)
{
$validated = $request->validate([
'names.zh_TW' => 'required|string|max:255',
'names.en' => 'nullable|string|max:255',
'names.ja' => 'nullable|string|max:255',
'barcode' => 'nullable|string|max:100',
'spec' => 'nullable|string|max:255',
'category_id' => 'nullable|exists:product_categories,id',
'manufacturer' => 'nullable|string|max:255',
'track_limit' => 'required|integer|min:1',
'spring_limit' => 'required|integer|min:1',
'price' => 'required|numeric|min:0',
'cost' => 'required|numeric|min:0',
'member_price' => 'required|numeric|min:0',
'metadata' => 'nullable|array',
'is_active' => 'nullable|boolean',
]);
try {
DB::beginTransaction();
$dictKey = (string) Str::uuid();
$company_id = auth()->user()->company_id;
// Store translations
foreach ($request->names as $locale => $name) {
if (empty($name)) continue;
Translation::create([
'group' => 'product',
'key' => $dictKey,
'locale' => $locale,
'text' => $name,
'company_id' => $company_id,
]);
}
$product = Product::create([
'company_id' => $company_id,
'category_id' => $request->category_id,
'name' => $request->names['zh_TW'], // Default name uses zh_TW
'name_dictionary_key' => $dictKey,
'barcode' => $request->barcode,
'spec' => $request->spec,
'manufacturer' => $request->manufacturer,
'track_limit' => $request->track_limit,
'spring_limit' => $request->spring_limit,
'price' => $request->price,
'cost' => $request->cost,
'member_price' => $request->member_price,
'metadata' => $request->metadata ?? [],
'is_active' => $request->boolean('is_active', true),
]);
DB::commit();
if ($request->wantsJson()) {
return response()->json([
'success' => true,
'message' => __('Product created successfully'),
'data' => $product
]);
}
return redirect()->back()->with('success', __('Product created successfully'));
} catch (\Exception $e) {
DB::rollBack();
if ($request->wantsJson()) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
return redirect()->back()->with('error', $e->getMessage());
}
}
public function update(Request $request, $id)
{
$product = Product::findOrFail($id);
$validated = $request->validate([
'names.zh_TW' => 'required|string|max:255',
'names.en' => 'nullable|string|max:255',
'names.ja' => 'nullable|string|max:255',
'barcode' => 'nullable|string|max:100',
'spec' => 'nullable|string|max:255',
'category_id' => 'nullable|exists:product_categories,id',
'manufacturer' => 'nullable|string|max:255',
'track_limit' => 'required|integer|min:1',
'spring_limit' => 'required|integer|min:1',
'price' => 'required|numeric|min:0',
'cost' => 'required|numeric|min:0',
'member_price' => 'required|numeric|min:0',
'metadata' => 'nullable|array',
'is_active' => 'nullable|boolean',
]);
try {
DB::beginTransaction();
$dictKey = $product->name_dictionary_key;
$company_id = auth()->user()->company_id;
// Update or Create translations
foreach ($request->names as $locale => $name) {
if (empty($name)) {
Translation::where([
'group' => 'product',
'key' => $dictKey,
'locale' => $locale
])->delete();
continue;
}
Translation::updateOrCreate(
[
'group' => 'product',
'key' => $dictKey,
'locale' => $locale,
],
[
'text' => $name,
'company_id' => $company_id,
]
);
}
$product->update([
'category_id' => $request->category_id,
'name' => $request->names['zh_TW'],
'barcode' => $request->barcode,
'spec' => $request->spec,
'manufacturer' => $request->manufacturer,
'track_limit' => $request->track_limit,
'spring_limit' => $request->spring_limit,
'price' => $request->price,
'cost' => $request->cost,
'member_price' => $request->member_price,
'metadata' => $request->metadata ?? [],
'is_active' => $request->boolean('is_active', true),
]);
DB::commit();
if ($request->wantsJson()) {
return response()->json([
'success' => true,
'message' => __('Product updated successfully'),
'data' => $product
]);
}
return redirect()->back()->with('success', __('Product updated successfully'));
} catch (\Exception $e) {
DB::rollBack();
if ($request->wantsJson()) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
return redirect()->back()->with('error', $e->getMessage());
}
}
public function destroy($id)
{
try {
$product = Product::findOrFail($id);
// Delete translations associated with this product
if ($product->name_dictionary_key) {
Translation::where('key', $product->name_dictionary_key)->delete();
}
$product->delete();
return redirect()->back()->with('success', __('Product deleted successfully'));
} catch (\Exception $e) {
return redirect()->back()->with('error', $e->getMessage());
}
}
}