255 lines
5.9 KiB
Vue
Raw Normal View History

2026-04-15 09:09:27 +08:00
<template>
<div class="selector-component">
<el-input :modelValue="modelValue" :placeholder="placeholder" readonly @click="openDialog">
<template #suffix>
<el-icon><ArrowDown /></el-icon>
</template>
</el-input>
<el-dialog v-model="dialogVisible" :title="dialogTitle" :width="dialogWidth" :append-to-body="true" :modal="true" @close="handleDialogClose">
<div class="dialog-content">
<div class="title">已选择地点路线{{ tempSelectedItem?.routeCode }}</div>
<el-input v-model="searchKeyword" :placeholder="searchPlaceholder" clearable @input="handleSearch" />
<el-table :data="dataList" v-loading="loading" height="350" @row-click="handleRowClick" highlight-current-row>
<el-table-column prop="routeCode" label="线路编号" />
</el-table>
<div class="pagination-wrapper" v-if="total > 0">
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="total"
:page-sizes="pageSizes"
layout="prev, pager, next, sizes"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, onUnmounted, watch } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { request } from '@shared/utils/request'
// ==================== Props ====================
const props = defineProps({
// v-model 绑定的值
modelValue: {
type: [String, Number],
default: ''
},
// 占位符文本
placeholder: {
type: String,
default: '请选择'
},
// 弹窗标题
dialogTitle: {
type: String,
default: '选择地点路线'
},
// 弹窗宽度
dialogWidth: {
type: String,
default: '500px'
},
// 搜索框占位符
searchPlaceholder: {
type: String,
default: '请输入关键词搜索'
},
// 请求接口地址
apiUrl: {
type: String,
default: '/snow-ops-platform/infrastructure-asset/routes'
},
// 每页大小
pageSize: {
type: Number,
default: 10
},
// 每页大小选项
pageSizes: {
type: Array,
default: () => [10, 20, 50]
},
// 额外请求参数
extraParams: {
type: Object,
default: () => ({})
},
// 搜索防抖延迟时间(ms)
searchDelay: {
type: Number,
default: 1000
}
})
const emit = defineEmits(['update:modelValue', 'change'])
// ==================== 状态 ====================
const dialogVisible = ref(false)
const searchKeyword = ref('')
const dataList = ref([])
const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(props.pageSize)
const total = ref(0)
// 临时选中的数据(确定前暂存)
const tempSelectedItem = ref(null)
// 防抖定时器
let searchTimer = null
// 已确认选中的值
const selectedValue = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
watch(() => props.modelValue, () => {
if(props.modelValue != tempSelectedItem.value?.routeCode) {
tempSelectedItem.value = null
}
})
// ==================== API 请求 ====================
const fetchData = async () => {
loading.value = true
try {
const params = {
pageNum: currentPage.value,
pageSize: pageSize.value,
lxbh: props.modelValue,
xzdj: props.extraParams?.xzdj, // 行政等级
qxid: props.extraParams?.qxid // 区县编码
}
// 如果有搜索关键词,添加到参数中
if (searchKeyword.value) {
params.lxbh = searchKeyword.value
}
const response = await request({
url: props.apiUrl,
method: 'get',
params
})
if (response?.code === '00000') {
const records = response.data.records || []
total.value = response.data.total || 0
// 统一格式化数据,便于内部使用
dataList.value = records
} else {
ElMessage.error(response.message || '加载失败')
dataList.value = []
total.value = 0
}
} catch (error) {
console.error('请求失败:', error)
ElMessage.error('加载失败,请重试')
dataList.value = []
total.value = 0
} finally {
loading.value = false
}
}
// 带防抖的搜索函数
const debouncedSearch = () => {
// 清除之前的定时器
if (searchTimer) {
clearTimeout(searchTimer)
}
// 设置新的定时器
searchTimer = setTimeout(() => {
currentPage.value = 1
fetchData()
}, props.searchDelay)
}
// ==================== 事件处理 ====================
const openDialog = () => {
dialogVisible.value = true
// 重置状态
searchKeyword.value = tempSelectedItem.value?.routeCode || ''
currentPage.value = 1
fetchData()
}
const handleDialogClose = () => {
dialogVisible.value = false
// 关闭弹窗时清除防抖定时器
if (searchTimer) {
clearTimeout(searchTimer)
}
}
const handleSearch = () => {
// 使用防抖处理搜索
debouncedSearch()
}
const handlePageChange = (page) => {
currentPage.value = page
fetchData()
}
const handleSizeChange = (size) => {
pageSize.value = size
currentPage.value = 1
fetchData()
}
const handleRowClick = (row) => {
tempSelectedItem.value = row
selectedValue.value = tempSelectedItem.value.routeCode
dialogVisible.value = false
emit('change', tempSelectedItem.value)
}
// ==================== 清理定时器 ====================
onUnmounted(() => {
if (searchTimer) {
clearTimeout(searchTimer)
}
})
</script>
<style scoped lang="scss">
.selector-component {
width: 100%;
:deep(.el-input__wrapper) {
cursor: pointer;
.el-input__inner {
cursor: pointer;
}
}
}
.dialog-content {
display: flex;
flex-direction: column;
gap: 16px;
.pagination-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 8px;
}
}
</style>