star-cloud/app/Http/Controllers/Admin/GeocodingController.php
sky121113 39a246d3c5 [FEAT] 倉儲管理模組標準化與庫存矩陣功能實作
1. 實作「目前庫存」矩陣化視圖:將商品作為行、倉庫作為列,支援跨倉庫水位分析與自動加總。
2. 優化矩陣 UI:實作固定欄位 (Sticky Columns) 以應對多倉庫場景,並修正深色模式下的背景色對齊問題。
3. 強化倉儲操作驗證:實作調撥與補貨單的 Alpine.js 前端欄位驗證與 AJAX 提交邏輯,提升操作流暢度。
4. 優化機台地址定位:實作結構化地址解析與 Nominatim API 對接,提升經緯度自動獲取的準確性。
5. 標準化 UI 組件:更新 Breadcrumbs、刪除確認彈窗與搜尋下拉選單,確保全站視覺一致性。
6. 完善多語系支援:補全倉儲管理、機台設定與 UI 提示的繁體中文翻譯。
7. 增加 Product Model 的 stocks() 關聯,支援高效率的庫存矩陣查詢。
2026-04-23 17:34:22 +08:00

138 lines
4.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
/**
* 地址地理編碼代理控制器
*
* 透過後端轉發 Nominatim API 請求,解決瀏覽器無法自訂 User-Agent
* 導致 Nominatim 回傳 403 Forbidden 的問題。
*/
class GeocodingController extends Controller
{
/**
* 將台灣地址轉換為經緯度座標
*/
public function resolve(Request $request)
{
$request->validate([
'address' => 'required|string|max:255',
]);
$address = $request->input('address');
// 策略 1結構化搜尋台灣地址精確度最高
$parsed = $this->parseTaiwanAddress($address);
if ($parsed['street']) {
$response = Http::withHeaders([
'User-Agent' => 'StarCloud/1.0 (admin@starcloud.com)',
])->get('https://nominatim.openstreetmap.org/search', [
'format' => 'json',
'street' => $parsed['street'],
'city' => $parsed['city'],
'country' => 'Taiwan',
'limit' => 1,
]);
if ($response->successful()) {
$data = $response->json();
if (!empty($data)) {
return response()->json([
'success' => true,
'lat' => (float) $data[0]['lat'],
'lon' => (float) $data[0]['lon'],
'display_name' => $data[0]['display_name'] ?? '',
]);
}
}
}
// 策略 2自由搜尋 + 台灣限定
$searchQuery = $address;
if (!str_contains($searchQuery, '台灣') && !str_contains($searchQuery, 'Taiwan')) {
$searchQuery .= ', 台灣';
}
$response = Http::withHeaders([
'User-Agent' => 'StarCloud/1.0 (admin@starcloud.com)',
])->get('https://nominatim.openstreetmap.org/search', [
'format' => 'json',
'q' => $searchQuery,
'countrycodes' => 'tw',
'limit' => 1,
]);
if ($response->successful()) {
$data = $response->json();
if (!empty($data)) {
return response()->json([
'success' => true,
'lat' => (float) $data[0]['lat'],
'lon' => (float) $data[0]['lon'],
'display_name' => $data[0]['display_name'] ?? '',
]);
}
}
return response()->json([
'success' => false,
'message' => __('Location not found'),
], 404);
}
/**
* 解析台灣地址格式為結構化參數
* 例如「台中市北區興進路90之3號」→ city: "台中市 北區", street: "90之3 興進路"
*/
private function parseTaiwanAddress(string $address): array
{
$address = trim($address);
// 解析城市 (市/縣)
preg_match('/^(.+?[市縣])/u', $address, $cityMatch);
$city = $cityMatch[1] ?? '';
// 解析區域 (區/鄉/鎮) — 排除「市」以避免與城市混淆
preg_match('/[市縣](.+?[區鄉鎮])/u', $address, $districtMatch);
$district = $districtMatch[1] ?? '';
// 取得路街部分:從最後一個「區/鄉/鎮」之後開始截取
if ($district) {
// 精確定位到 district 結束後的部分
$pos = mb_strpos($address, $district);
if ($pos !== false) {
$streetPart = mb_substr($address, $pos + mb_strlen($district));
} else {
$streetPart = $address;
}
} else {
$streetPart = $address;
}
// 拆分路名與門牌號碼
preg_match('/^(.+?(?:路|街|大道|巷|弄)(?:[一二三四五六七八九十]+段)?)\s*(.*)/u', $streetPart, $roadMatch);
$street = '';
$houseNumber = '';
if ($roadMatch) {
$street = $roadMatch[1];
$houseNumber = str_replace('號', '', $roadMatch[2] ?? '');
} else {
$street = $streetPart;
}
$fullStreet = ($houseNumber ? $houseNumber . ' ' : '') . $street;
$fullCity = $city . ($district ? ' ' . $district : '');
return [
'city' => $fullCity,
'street' => $fullStreet,
];
}
}