chuangte_bike_newxcx/page_shanghu/guanli/components/DeviceSelectorPopup.vue
2026-06-02 16:35:58 +08:00

251 lines
5.9 KiB
Vue

<template>
<u-popup v-model="innerVisible" mode="bottom" border-radius="24" safe-area-inset-bottom>
<view class="popup-panel">
<view class="popup-head">
<text class="popup-title">更换车辆</text>
<text hover-class="app-tap-hover" class="popup-close" @click="close">关闭</text>
</view>
<view class="search-bar">
<u-icon name="search" color="#9AA7B8" size="32"></u-icon>
<input v-model="keyword" placeholder="搜索 SN / 车牌" placeholder-style="color:#C7CDD3" @input="filterDevices" />
</view>
<scroll-view scroll-y class="popup-scroll">
<view
v-for="(line, idx) in filteredDeviceList"
:key="line.rowDevId"
class="popup-item"
:class="{ active: line.picked }"
hover-class="app-tap-hover"
:data-idx="idx"
@click="onDeviceItemTap"
>
<view class="dev-lines">
<text class="popup-item-tit">{{ line.rowTitle }}</text>
<text class="dev-sub">{{ line.rowSub }}</text>
<text class="dev-meta">{{ line.rowMeta }}</text>
</view>
<u-icon v-if="line.picked" name="checkbox-mark" color="#4C97E7" size="36"></u-icon>
</view>
<view v-if="showLoading" class="popup-empty">车辆加载中...</view>
<view v-else-if="showEmpty" class="popup-empty">暂无待骑行车辆</view>
</scroll-view>
</view>
</u-popup>
</template>
<script>
export default {
name: 'DeviceSelectorPopup',
props: {
visible: {
type: Boolean,
default: false
},
areaId: {
type: [String, Number],
default: ''
},
currentDeviceId: {
type: [String, Number],
default: ''
}
},
data() {
return {
deviceList: [],
filteredDeviceList: [],
keyword: '',
loading: false,
loadedAreaId: ''
}
},
computed: {
innerVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
},
showLoading() {
return this.loading && this.filteredDeviceList.length === 0
},
showEmpty() {
return !this.loading && this.filteredDeviceList.length === 0
}
},
watch: {
visible(val) {
if (val) {
this.open()
}
},
currentDeviceId() {
this.syncPicked()
}
},
methods: {
open() {
this.keyword = ''
if (!this.areaId) {
uni.showToast({ title: '缺少运营区,无法选择车辆', icon: 'none' })
return
}
if (!this.deviceList.length || String(this.loadedAreaId) !== String(this.areaId)) {
this.loadDeviceList()
} else {
this.filteredDeviceList = this.deviceList
this.syncPicked()
}
},
close() {
this.innerVisible = false
},
loadDeviceList() {
this.loading = true
const areaId = encodeURIComponent(String(this.areaId))
this.$u
.get(`/bst/device/all?areaId=${areaId}&status=1&supportLocation=true`)
.then((res) => {
if (res.code === 200) {
const arr = Array.isArray(res.data) ? res.data : []
this.deviceList = this.normalizeDeviceList(arr)
this.filteredDeviceList = this.deviceList
this.loadedAreaId = this.areaId
this.syncPicked()
} else {
uni.showToast({ title: res.msg || '获取车辆失败', icon: 'none' })
}
})
.catch(() => {
uni.showToast({ title: '获取车辆失败', icon: 'none' })
})
.finally(() => {
this.loading = false
})
},
normalizeDeviceList(arr) {
return (arr || []).map((d, i) => {
const did = d.id != null ? d.id : d.deviceId
const sn = d.sn || d.deviceSn || ''
const plate = d.vehicleNum || ''
const title = plate ? plate + (sn ? ' · ' + sn : '') : sn || '车辆' + (i + 1)
const sub = (d.modelName || '') + (d.areaName ? ' · ' + d.areaName : '')
const meta = `电量:${d.remainingPower == null ? '--' : d.remainingPower + '%'}`
return {
rowDevId: did,
rowTitle: title,
rowSub: sub || '暂无车型信息',
rowMeta: meta,
rowRaw: d,
picked: false
}
}).filter((x) => x.rowDevId != null && x.rowDevId !== '')
},
syncPicked() {
const cur = this.currentDeviceId
this.deviceList.forEach((x) => {
x.picked = cur !== '' && cur !== null && String(x.rowDevId) === String(cur)
})
},
filterDevices() {
const k = (this.keyword || '').trim().toLowerCase()
if (!k) {
this.filteredDeviceList = this.deviceList
this.syncPicked()
return
}
this.filteredDeviceList = this.deviceList.filter((it) => {
const r = it.rowRaw || {}
const sn = String(r.sn || r.deviceSn || '').toLowerCase()
const plate = String(r.vehicleNum || '').toLowerCase()
const model = String(r.modelName || '').toLowerCase()
return sn.includes(k) || plate.includes(k) || model.includes(k)
})
this.syncPicked()
},
onDeviceItemTap(e) {
const idx = parseInt(e.currentTarget.dataset.idx, 10)
if (Number.isNaN(idx)) return
const item = this.filteredDeviceList[idx]
if (!item) return
this.$emit('select', item.rowRaw)
this.close()
}
}
}
</script>
<style lang="scss" scoped>
@import './agent-order-theme.scss';
.popup-panel {
@include ao-popup-panel;
}
.popup-head {
@include ao-popup-head;
height: auto;
}
.popup-title {
@include ao-section-title;
font-weight: 700;
}
.popup-close {
font-size: 28rpx;
color: $ao-primary;
}
.search-bar {
@include ao-field-box;
height: 76rpx;
margin: 20rpx 0;
padding: 0 22rpx;
input {
flex: 1;
margin-left: 12rpx;
font-size: 28rpx;
color: $ao-text;
}
}
.popup-scroll {
max-height: 720rpx;
}
.popup-item {
@include ao-option-item;
justify-content: space-between;
}
.dev-lines {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.popup-item-tit {
font-size: 30rpx;
font-weight: 600;
color: $ao-text;
line-height: 1.4;
}
.dev-sub,
.dev-meta {
font-size: 24rpx;
color: $ao-text-secondary;
margin-top: 8rpx;
}
.popup-empty {
@include ao-empty-tip;
padding: 48rpx 0;
}
</style>