255 lines
5.9 KiB
Vue
255 lines
5.9 KiB
Vue
<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> |