Jelajahi Sumber

feat: 进出场操作页面支持多语言——新增30+翻译键,适配中/英/繁切换

lq 1 bulan lalu
induk
melakukan
c8a27525a0

+ 22 - 1
frontend/src/lang/en.js

@@ -192,5 +192,26 @@ export default {
   minute: 'Minute',
   entryImg: 'Entry Image',
   exitImg: 'Exit Image',
-  recordDetail: 'Access Record Details'
+  recordDetail: 'Access Record Details',
+  entryVehicle: 'Vehicle Entry',
+  exitVehicle: 'Vehicle Exit',
+  confirmEntry: 'Confirm Entry',
+  confirmEntryTemp: 'Confirm Entry (Temp)',
+  confirmExitAndPay: 'Confirm Exit & Pay',
+  exitAutoCalcFee: 'Parking fee will be calculated automatically',
+  notFoundVehicle: 'Vehicle Not Found',
+  tempCarEntryHint: 'Will be processed as temporary vehicle. Select parking lot and confirm entry.',
+  plateNotRegHint: 'Vehicle not registered. Please verify the plate number.',
+  entrySuccessMsg: 'Entry Success',
+  exitSuccessMsg: 'Exit Success',
+  entryFailed: 'Entry Failed',
+  exitFailed: 'Exit Failed',
+  pleaseEnterPlateOrRfid: 'Please enter plate number or RFID tag',
+  vehicleDetail: 'Vehicle Info',
+  exitDetail: 'Exit Info',
+  confirmExitTitle: 'Confirm Exit',
+  confirmExitMsg: 'Confirm vehicle exit?',
+  pleaseSelectPark: 'Please select parking lot',
+  rfidInput: 'Please enter RFID tag',
+  exitRecordDetail: 'Entry: {entryTime} | Stay: {duration} | Fee: ¥{fee}',
 }

+ 22 - 1
frontend/src/lang/zh-CN.js

@@ -192,5 +192,26 @@ export default {
   minute: '分钟',
   entryImg: '入场图片',
   exitImg: '出场图片',
-  recordDetail: '进出记录详情'
+  recordDetail: '进出记录详情',
+  entryVehicle: '车辆进场',
+  exitVehicle: '车辆出场',
+  confirmEntry: '确认进场',
+  confirmEntryTemp: '确认进场(临时车)',
+  confirmExitAndPay: '确认出场并收费',
+  exitAutoCalcFee: '确认后系统将自动计算停车费用',
+  notFoundVehicle: '未找到车辆信息',
+  tempCarEntryHint: '将按临时车处理,请选择停车场后确认进场',
+  plateNotRegHint: '该车辆未登记,请核实车牌号',
+  entrySuccessMsg: '入场成功',
+  exitSuccessMsg: '出场成功',
+  entryFailed: '入场失败',
+  exitFailed: '出场失败',
+  pleaseEnterPlateOrRfid: '请至少输入车牌号或RFID标签',
+  vehicleDetail: '车辆信息',
+  exitDetail: '出场信息',
+  confirmExitTitle: '确认出场',
+  confirmExitMsg: '确认车辆出场?',
+  pleaseSelectPark: '请选择停车场',
+  rfidInput: '请输入RFID标签',
+  exitRecordDetail: '进场: {entryTime} | 停留: {duration} | 费用: ¥{fee}',
 }

+ 22 - 1
frontend/src/lang/zh-HK.js

@@ -192,5 +192,26 @@ export default {
   minute: '分鐘',
   entryImg: '入場圖片',
   exitImg: '出場圖片',
-  recordDetail: '進出記錄詳情'
+  recordDetail: '進出記錄詳情',
+  entryVehicle: '車輛進場',
+  exitVehicle: '車輛出場',
+  confirmEntry: '確認進場',
+  confirmEntryTemp: '確認進場(臨時車)',
+  confirmExitAndPay: '確認出場並收費',
+  exitAutoCalcFee: '確認後系統將自動計算停車費用',
+  notFoundVehicle: '未找到車輛信息',
+  tempCarEntryHint: '將按臨時車處理,請選擇停車場後確認進場',
+  plateNotRegHint: '該車輛未登記,請核實車牌號',
+  entrySuccessMsg: '入場成功',
+  exitSuccessMsg: '出場成功',
+  entryFailed: '入場失敗',
+  exitFailed: '出場失敗',
+  pleaseEnterPlateOrRfid: '請至少輸入車牌號或RFID標籤',
+  vehicleDetail: '車輛信息',
+  exitDetail: '出場信息',
+  confirmExitTitle: '確認出場',
+  confirmExitMsg: '確認車輛出場?',
+  pleaseSelectPark: '請選擇停車場',
+  rfidInput: '請輸入RFID標籤',
+  exitRecordDetail: '進場: {entryTime} | 停留: {duration} | 費用: ¥{fee}',
 }

+ 22 - 1
frontend/src/lang/zh-TW.js

@@ -192,5 +192,26 @@ export default {
   minute: '分鐘',
   entryImg: '入場圖片',
   exitImg: '出場圖片',
-  recordDetail: '進出記錄詳情'
+  recordDetail: '進出記錄詳情',
+  entryVehicle: '車輛進場',
+  exitVehicle: '車輛出場',
+  confirmEntry: '確認進場',
+  confirmEntryTemp: '確認進場(臨時車)',
+  confirmExitAndPay: '確認出場並收費',
+  exitAutoCalcFee: '確認後系統將自動計算停車費用',
+  notFoundVehicle: '未找到車輛信息',
+  tempCarEntryHint: '將按臨時車處理,請選擇停車場後確認進場',
+  plateNotRegHint: '該車輛未登記,請核實車牌號',
+  entrySuccessMsg: '入場成功',
+  exitSuccessMsg: '出場成功',
+  entryFailed: '入場失敗',
+  exitFailed: '出場失敗',
+  pleaseEnterPlateOrRfid: '請至少輸入車牌號或RFID標籤',
+  vehicleDetail: '車輛信息',
+  exitDetail: '出場信息',
+  confirmExitTitle: '確認出場',
+  confirmExitMsg: '確認車輛出場?',
+  pleaseSelectPark: '請選擇停車場',
+  rfidInput: '請輸入RFID標籤',
+  exitRecordDetail: '進場: {entryTime} | 停留: {duration} | 費用: ¥{fee}',
 }

+ 42 - 38
frontend/src/view/parking/entryExit.vue

@@ -1,72 +1,72 @@
 <template>
   <div class="entry-exit-page">
     <el-tabs v-model="activeTab" @tab-change="handleTabChange">
-      <el-tab-pane label="车辆进场" name="entry" />
-      <el-tab-pane label="车辆出场" name="exit" />
+      <el-tab-pane :label="$t.value.entryVehicle" name="entry" />
+      <el-tab-pane :label="$t.value.exitVehicle" name="exit" />
     </el-tabs>
 
     <el-card class="query-card">
       <el-form :model="queryForm" inline>
-        <el-form-item label="车牌号">
-          <el-input v-model="queryForm.plate_number" placeholder="请输入车牌号" clearable style="width: 200px" @keyup.enter="queryVehicle" />
+        <el-form-item :label="$t.value.LicensePlateNo">
+          <el-input v-model="queryForm.plate_number" :placeholder="$t.value.carPlateInput" clearable style="width: 200px" @keyup.enter="queryVehicle" />
         </el-form-item>
-        <el-form-item label="RFID标签">
-          <el-input v-model="queryForm.rfid_tag" placeholder="请输入RFID标签" clearable style="width: 240px" @keyup.enter="queryVehicle" />
+        <el-form-item :label="$t.value.RFID_Tag">
+          <el-input v-model="queryForm.rfid_tag" :placeholder="$t.value.rfidInput" clearable style="width: 240px" @keyup.enter="queryVehicle" />
         </el-form-item>
         <el-form-item>
-          <el-button type="primary" icon="Search" @click="queryVehicle">查询</el-button>
-          <el-button icon="Refresh" @click="resetForm">重置</el-button>
+          <el-button type="primary" icon="Search" @click="queryVehicle">{{ $t.value.Search }}</el-button>
+          <el-button icon="Refresh" @click="resetForm">{{ $t.value.Reset }}</el-button>
         </el-form-item>
       </el-form>
     </el-card>
 
     <el-card v-if="vehicleLoaded" class="detail-card">
       <template #header>
-        <span class="card-title">{{ activeTab === 'entry' ? '车辆信息' : '出场信息' }}</span>
+        <span class="card-title">{{ activeTab === 'entry' ? $t.value.vehicleDetail : $t.value.exitDetail }}</span>
       </template>
 
       <el-descriptions :column="3" border>
-        <el-descriptions-item label="车牌号">{{ vehicleInfo.plate_number || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="RFID标签">{{ vehicleInfo.rfid_tag || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="车辆类型">
+        <el-descriptions-item :label="$t.value.LicensePlateNo">{{ vehicleInfo.plate_number || '-' }}</el-descriptions-item>
+        <el-descriptions-item :label="$t.value.RFID_Tag">{{ vehicleInfo.rfid_tag || '-' }}</el-descriptions-item>
+        <el-descriptions-item :label="$t.value.VehicleType">
           <el-tag v-if="vehicleInfo.VehicleType" :type="vehicleInfo.VehicleType.ID === 1 ? 'warning' : 'success'">{{ vehicleInfo.VehicleType.name }}</el-tag>
-          <el-tag v-else type="warning">临时车</el-tag>
+          <el-tag v-else type="warning">{{ $t.value.tempCar }}</el-tag>
         </el-descriptions-item>
-        <el-descriptions-item v-if="vehicleInfo.Owner" label="车主">{{ vehicleInfo.Owner.owner_name }}</el-descriptions-item>
-        <el-descriptions-item v-if="vehicleInfo.Owner && vehicleInfo.Owner.is_vip" label="VIP"><el-tag type="danger"></el-tag></el-descriptions-item>
+        <el-descriptions-item v-if="vehicleInfo.Owner" :label="$t.value.CarOwner">{{ vehicleInfo.Owner.owner_name }}</el-descriptions-item>
+        <el-descriptions-item v-if="vehicleInfo.Owner && vehicleInfo.Owner.is_vip" label="VIP"><el-tag type="danger">{{ $t.value.yes }}</el-tag></el-descriptions-item>
       </el-descriptions>
 
       <div v-if="activeTab === 'entry'" class="action-area">
         <el-divider />
         <el-form :model="entryForm" inline>
-          <el-form-item label="选择停车场" required>
-            <el-select v-model="entryForm.parking_lot_id" placeholder="请选择停车场" style="width: 260px">
+          <el-form-item :label="$t.value.selectParkArea" required>
+            <el-select v-model="entryForm.parking_lot_id" :placeholder="$t.value.pleaseSelectPark" style="width: 260px">
               <el-option v-for="lot in parkingLotList" :key="lot.ID" :label="lot.lot_name" :value="lot.ID" />
             </el-select>
           </el-form-item>
           <el-form-item>
-            <el-button type="success" icon="Check" :loading="submitting" @click="confirmEntry">确认进场</el-button>
+            <el-button type="success" icon="Check" :loading="submitting" @click="confirmEntry">{{ $t.value.confirmEntry }}</el-button>
           </el-form-item>
         </el-form>
       </div>
 
       <div v-if="activeTab === 'exit'" class="action-area">
         <el-divider />
-        <p class="exit-tip">确认后系统将自动计算停车费用</p>
-        <el-button type="danger" icon="Check" :loading="submitting" @click="confirmExit">确认出场并收费</el-button>
+        <p class="exit-tip">{{ $t.value.exitAutoCalcFee }}</p>
+        <el-button type="danger" icon="Check" :loading="submitting" @click="confirmExit">{{ $t.value.confirmExitAndPay }}</el-button>
       </div>
     </el-card>
 
-    <el-result v-if="notFound" icon="warning" title="未找到车辆信息" :sub-title="activeTab === 'entry' ? '将按临时车处理,请选择停车场后确认进场' : '该车辆未登记,请核实车牌号'">
+    <el-result v-if="notFound" icon="warning" :title="$t.value.notFoundVehicle" :sub-title="activeTab === 'entry' ? $t.value.tempCarEntryHint : $t.value.plateNotRegHint">
       <template v-if="activeTab === 'entry'" #extra>
         <el-form :model="entryForm" inline>
-          <el-form-item label="选择停车场" required>
-            <el-select v-model="entryForm.parking_lot_id" placeholder="请选择停车场" style="width: 260px">
+          <el-form-item :label="$t.value.selectParkArea" required>
+            <el-select v-model="entryForm.parking_lot_id" :placeholder="$t.value.pleaseSelectPark" style="width: 260px">
               <el-option v-for="lot in parkingLotList" :key="lot.ID" :label="lot.lot_name" :value="lot.ID" />
             </el-select>
           </el-form-item>
           <el-form-item>
-            <el-button type="success" :loading="submitting" @click="confirmEntry">确认进场(临时车)</el-button>
+            <el-button type="success" :loading="submitting" @click="confirmEntry">{{ $t.value.confirmEntryTemp }}</el-button>
           </el-form-item>
         </el-form>
       </template>
@@ -79,6 +79,7 @@ import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { obtainVehicleLicense, vehicleEntry, vehicleAppear } from '@/api/vehicle'
 import { obtainPullOverList } from '@/api/pullOver'
+import { t } from '@/lang/index.js'
 
 const activeTab = ref('entry')
 const queryForm = reactive({ plate_number: '', rfid_tag: '' })
@@ -94,7 +95,7 @@ onMounted(async () => {
     const res = await obtainPullOverList({ page: 1, pageSize: 100 })
     parkingLotList.value = res.data?.list || []
   } catch (e) {
-    console.error('加载停车场列表失败', e)
+    console.error('Failed to load parking lots', e)
   }
 })
 
@@ -102,7 +103,7 @@ async function queryVehicle() {
   const plate = queryForm.plate_number.trim()
   const rfid = queryForm.rfid_tag.trim()
   if (!plate && !rfid) {
-    ElMessage.warning('请至少输入车牌号或RFID标签')
+    ElMessage.warning(t.value.pleaseEnterPlateOrRfid)
     return
   }
   vehicleInfo.value = null
@@ -123,7 +124,7 @@ async function queryVehicle() {
 
 async function confirmEntry() {
   if (!entryForm.parking_lot_id) {
-    ElMessage.warning('请选择停车场')
+    ElMessage.warning(t.value.pleaseSelectPark)
     return
   }
   if (submitting.value) return
@@ -136,24 +137,23 @@ async function confirmEntry() {
     })
     if (res.code === 0) {
       const plate = res.data?.data?.plate_number || queryForm.plate_number.trim()
-      ElMessage.success(`入场成功!${plate} 已进入停车场`)
+      ElMessage.success(`${t.value.entrySuccessMsg}!${plate}`)
       resetForm()
     } else {
-      ElMessage.error(res.msg || '入场失败')
+      ElMessage.error(res.msg || t.value.entryFailed)
     }
   } catch (e) {
-    ElMessage.error(e.message || '入场失败')
+    ElMessage.error(e.message || t.value.entryFailed)
   } finally {
     submitting.value = false
   }
 }
 
 async function confirmExit() {
-  const plate = vehicleInfo.value?.plate_number || queryForm.plate_number.trim()
   await ElMessageBox.confirm(
-    `确认车辆 ${plate} 出场?`,
-    '确认出场',
-    { confirmButtonText: '确认出场', cancelButtonText: '取消', type: 'warning' }
+    t.value.confirmExitMsg,
+    t.value.confirmExitTitle,
+    { confirmButtonText: t.value.Confirm, cancelButtonText: t.value.Cancel, type: 'warning' }
   )
   if (submitting.value) return
   submitting.value = true
@@ -167,13 +167,17 @@ async function confirmExit() {
       const fee = data.fee || 0
       const stayTime = data.stay_time || 0
       const entryTime = data.entry_time ? new Date(data.entry_time * 1000).toLocaleString('zh-CN') : '-'
-      ElMessage.success(`出场成功!进场: ${entryTime} | 停留: ${formatDuration(stayTime)} | 费用: ¥${fee.toFixed(2)}`)
+      const detail = t.value.exitRecordDetail
+        .replace('{entryTime}', entryTime)
+        .replace('{duration}', formatDuration(stayTime))
+        .replace('{fee}', fee.toFixed(2))
+      ElMessage.success(`${t.value.exitSuccessMsg}!${detail}`)
       resetForm()
     } else {
-      ElMessage.error(res.msg || '出场失败')
+      ElMessage.error(res.msg || t.value.exitFailed)
     }
   } catch (e) {
-    ElMessage.error(e.message || '出场失败')
+    ElMessage.error(e.message || t.value.exitFailed)
   } finally {
     submitting.value = false
   }
@@ -194,7 +198,7 @@ function formatDuration(minutes) {
   if (!minutes && minutes !== 0) return '-'
   const h = Math.floor(minutes / 60)
   const m = minutes % 60
-  return h > 0 ? `${h}小时${m}分钟` : `${m}分钟`
+  return h > 0 ? `${h}h ${m}${t.value.minute}` : `${m}${t.value.minute}`
 }
 </script>