|
|
@@ -0,0 +1,135 @@
|
|
|
+<template>
|
|
|
+ <van-popup v-model:show="visible" round position="bottom" :style="{ maxHeight: '70vh' }">
|
|
|
+ <div class="picker-container">
|
|
|
+ <div class="search-container">
|
|
|
+ <van-search
|
|
|
+ v-model="keyword"
|
|
|
+ placeholder="搜索车牌号"
|
|
|
+ shape="round"
|
|
|
+ @search="loadOptions"
|
|
|
+ @clear="loadOptions"
|
|
|
+ @update:model-value="onKeywordInput"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="option-list">
|
|
|
+ <van-cell
|
|
|
+ v-for="item in options"
|
|
|
+ :key="item.id || item.licensePlate"
|
|
|
+ :title="item.licensePlate"
|
|
|
+ :label="typeLabel(item.vehicleType)"
|
|
|
+ clickable
|
|
|
+ @click="onSelect(item.licensePlate)"
|
|
|
+ />
|
|
|
+ <van-empty
|
|
|
+ v-if="!loading && options.length === 0"
|
|
|
+ description="暂无匹配,请到门户维护"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </van-popup>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, watch } from 'vue'
|
|
|
+import {
|
|
|
+ fetchPublicVehiclePlates,
|
|
|
+ fetchVehicleOptions,
|
|
|
+ type VehicleOption
|
|
|
+} from '@/api/vehicleOptionsApi'
|
|
|
+
|
|
|
+const props = withDefaults(
|
|
|
+ defineProps<{
|
|
|
+ show: boolean
|
|
|
+ /** 现场免登模式 */
|
|
|
+ publicMode?: boolean
|
|
|
+ inventoryId?: string
|
|
|
+ planId?: string
|
|
|
+ }>(),
|
|
|
+ {
|
|
|
+ publicMode: false
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+const emit = defineEmits<{
|
|
|
+ (e: 'update:show', v: boolean): void
|
|
|
+ (e: 'select', licensePlate: string): void
|
|
|
+}>()
|
|
|
+
|
|
|
+const visible = ref(false)
|
|
|
+const keyword = ref('')
|
|
|
+const loading = ref(false)
|
|
|
+const options = ref<Array<{ id?: string; licensePlate: string; vehicleType?: string }>>([])
|
|
|
+let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => props.show,
|
|
|
+ (v) => {
|
|
|
+ visible.value = v
|
|
|
+ if (v) {
|
|
|
+ keyword.value = ''
|
|
|
+ loadOptions()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+)
|
|
|
+
|
|
|
+watch(visible, (v) => emit('update:show', v))
|
|
|
+
|
|
|
+const typeLabel = (t?: string) => {
|
|
|
+ if (t === 'self') return '自有'
|
|
|
+ if (t === 'carrier') return '三方'
|
|
|
+ return ''
|
|
|
+}
|
|
|
+
|
|
|
+const loadOptions = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ if (props.publicMode) {
|
|
|
+ const list = await fetchPublicVehiclePlates({
|
|
|
+ q: keyword.value,
|
|
|
+ inventoryId: props.inventoryId,
|
|
|
+ planId: props.planId
|
|
|
+ })
|
|
|
+ options.value = list.map((x) => ({
|
|
|
+ id: x.licensePlate,
|
|
|
+ licensePlate: x.licensePlate,
|
|
|
+ vehicleType: x.vehicleType
|
|
|
+ }))
|
|
|
+ } else {
|
|
|
+ const list: VehicleOption[] = await fetchVehicleOptions(keyword.value)
|
|
|
+ options.value = list
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e)
|
|
|
+ options.value = []
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const onKeywordInput = () => {
|
|
|
+ if (debounceTimer) clearTimeout(debounceTimer)
|
|
|
+ debounceTimer = setTimeout(() => loadOptions(), 300)
|
|
|
+}
|
|
|
+
|
|
|
+const onSelect = (licensePlate: string) => {
|
|
|
+ emit('select', licensePlate)
|
|
|
+ visible.value = false
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.picker-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ max-height: 70vh;
|
|
|
+}
|
|
|
+.search-container {
|
|
|
+ padding: 8px 12px 0;
|
|
|
+}
|
|
|
+.option-list {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding-bottom: 16px;
|
|
|
+}
|
|
|
+</style>
|