<template>
<div :class="`${prefixCls}`">
<el-tabs
v-model="tableDescIndex"
editable
:class="`${prefixCls}__tabs`"
@tab-change="handleTabChange"
@edit="handleTabsEdit"
>
<el-tab-pane
v-for="(item, index) in routeDescTabs"
:key="index"
:label="item.routeAbbr"
:name="index"
>
<template #label>
<span @dblclick="handleTabDblClick(index)" class="tab-title">
<el-tooltip
class="box-item"
effect="dark"
:content="item.lineDesc"
placement="top"
v-if="item.lineDesc"
>
{{ item.routeAbbr }}
</el-tooltip>
<span v-else>{{ item.routeAbbr }}</span>
</span>
</template>
<el-table
:data="item.planStopList"
:class="`${prefixCls}__table`"
:header-cell-style="headerCellStyle"
:cell-style="cellStyle"
>
<el-table-column prop="sort" :label="t('lineEdit.sort')" width="90" align="center">
<template #default="scope">
<div>
{{ scope.$index + 1 }}
<el-icon @click="addColumnAfterRow(scope.$index)" class="add-icon">
<Plus />
</el-icon>
<el-icon @click="removeRow(scope.$index)" class="add-icon">
<Delete />
</el-icon>
</div>
</template>
</el-table-column>
<el-table-column prop="stopId" :label="t('lineMapEdit.stationName')" align="center">
<template #default="scope">
<el-select
v-model="scope.row.stopDesc"
filterable
:filter-method="handleSearch"
virtual-scroll
:virtual-scroll-item-size="40"
:virtual-scroll-visible-items="15"
v-select-loadmore="loadMoreData"
placeholder="请选择或搜索"
>
<el-option
v-for="item in visibleOptions"
:key="item.stopId"
:label="item.stopDesc"
:value="item.stopId"
/>
</el-select>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang="ts" setup>
import { headerCellStyle, cellStyle } from '@/components/PlanningComps/common'
import { Plus, Delete } from '@element-plus/icons-vue'
import { ref } from 'vue'
import { debounce } from 'lodash-es'
import { getAllStopList } from '@/api/planning/stop/index'
import { RouteTab, lineRouteInfoItem } from '@/api/planning/line/type'
import { ElMessageBox } from 'element-plus'
defineOptions({ name: 'RouteDesc' })
const props = defineProps<{
lineRouteInfoList: lineRouteInfoItem[]
}>()
const emit = defineEmits(['update:tabIndex', 'update-line-detail', 'stop-added', 'stop-deleted'])
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('route-desc')
const message = useMessage()
const { t } = useI18n()
const tableDescIndex = ref(0) // 当前选中的选项
const routeDescTabs = ref<lineRouteInfoItem[]>([])
// 新增线路点
const wayPoint = ref([])
// 初始化响应式引用
const allOptions = ref([]) // 所有选项
const filteredOptions = ref([]) // 过滤后的选项
const currentPage = ref(1) // 当前页码
const pageSize = 50 // 每页大小
const hasMore = ref(true) // 是否还有更多数据
const isLoading = ref(false) // 加载状态
const selectKey = ref(0)
const searchQuery = ref('')
const loadedCount = ref(100) // 初始加载100条
// 新增对话框删除tab
const handleTabsEdit = (targetName: TabPaneName | undefined, action: 'remove' | 'add') => {
if (action === 'add') {
ElMessageBox.prompt('Please enter the line name', 'Prompt', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
inputPattern: /.+/, // 正则验证
inputErrorMessage: 'The input cannot be empty'
})
.then(({ value }) => {
const title = value.trim()
routeDescTabs.value.push({
lineId: '',
lineDesc: '',
routeAbbr: title,
planStopList: [{ stopId: '', stopDesc: '', sort: 1, lng: '', lat: '' }],
routeGeometry: {}
})
tableDescIndex.value = routeDescTabs.value.length - 1
handleTabChange()
// lineRouteInfoItem 添加线路描述
emit('update-line-detail', routeDescTabs.value)
})
.catch(() => {
console.log('User cancellation')
})
} else if (action === 'remove') {
const tabs = routeDescTabs.value
let activeName = tableDescIndex.value
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
const nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeName = nextTab.name
}
}
})
}
tableDescIndex.value = activeName
routeDescTabs.value = tabs.filter((tab) => tab.name !== targetName)
}
}
// 删除
const removeTab = (targetName: string) => {
const tabs = routeDescTabs.value
let activeName = tableDescIndex.value
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
const nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeName = nextTab.name
}
}
})
}
tableDescIndex.value = activeName
routeDescTabs.value = tabs.filter((tab) => tab.name !== targetName)
}
// 判定索引位置
const determinePositionType = (index: number) => {
const stops = routeDescTabs.value[tableDescIndex.value].planStopList
if (index === 0) return 'start'
if (index === stops.length - 1) return 'end'
return 'middle'
}
// 新增行index 索引(新增包含lineID站点-保存新增索引-判断位置-传递事件-构建几何请求-重绘线路)
const addColumnAfterRow = (index: number) => {
const newIndex = index + 1
routeDescTabs.value[tableDescIndex.value].planStopList.splice(newIndex, 0, {
sort: `${newIndex + 1}`,
stopId: '',
stopDesc: '',
lng: '',
lat: ''
})
// 更新所有行的 No 值
routeDescTabs.value[tableDescIndex.value].planStopList.forEach((row, i) => {
row.sort = `${i + 1}`
})
}
// 监听删除操作后重排序
const removeRow = (index) => {
if (routeDescTabs.value[tableDescIndex.value].planStopList.length <= 1)
return message.warning('Only one value cannot be deleted')
// 触发线路更新事件,携带被删除站点的类型信息
if (routeDescTabs.value[tableDescIndex.value].lineId != '') {
emit('stop-deleted', {
delectIndex: index, // 删除站点索引
positionType: determinePositionType(index) // 删除站点位置类型
})
}
routeDescTabs.value[tableDescIndex.value].planStopList.splice(index, 1)
// 重置编号
routeDescTabs.value[tableDescIndex.value].planStopList.forEach((row, i) => {
row.sort = `${i + 1}`
})
message.success('Delete successfully')
}
// 获取站点列表
const getAllStopsList = async () => {
try {
const data = (await getAllStopList()) as StopListItem[]
allOptions.value = data
} catch (error) {
console.error('获取站点列表失败:', error)
allOptions.value = []
filteredOptions.value = []
}
}
// 防抖远程搜索
const handleSearch = debounce(
(query) => {
debugger
if (!query) {
// filteredOptions.value = []
return
}
try {
searchQuery.value = query.toLowerCase()
} catch (error) {
console.error('搜索站点失败:', error)
filteredOptions.value = []
}
},
300,
{ leading: true, trailing: true }
)
// 局部注册自定义滚动指令翻页加载
const vSelectLoadmore = {
mounted(el, binding) {
// 使用nextTick确保DOM渲染完成
setTimeout(() => {
const SELECTWRAP_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
if (SELECTWRAP_DOM) {
SELECTWRAP_DOM.addEventListener('scroll', () => {
// 精确计算是否滚动到底部
const isBottom =
Math.ceil(SELECTWRAP_DOM.scrollTop + SELECTWRAP_DOM.clientHeight) >=
SELECTWRAP_DOM.scrollHeight - 1
if (isBottom) {
binding.value() // 触发绑定的加载函数
}
})
}
}, 100)
}
}
// 分页加载函数
const loadMoreData = () => {
if (loadedCount.value < allOptions.value.length) {
loadedCount.value += 50 // 每次加载50条
}
}
// 动态计算可见选项(结合搜索和分页)
const visibleOptions = computed(() => {
debugger
let result = allOptions.value
if (searchQuery.value) {
result = result.filter((item) =>
item.stopDesc.toLowerCase().includes(searchQuery.value.toLowerCase())
)
}
return result.slice(0, loadedCount.value) // 只返回当前加载的数据
})
// 双击修改方案名
const handleTabDblClick = (index: number) => {
const currentTab = routeDescTabs.value[index]
if (!currentTab) return
ElMessageBox.prompt('Please enter the new line description', 'Modify the line name', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
inputPattern: /.+/,
inputErrorMessage: 'Please enter a valid line name',
inputValue: currentTab.routeAbbr // 初始值为当前名称
})
.then(({ value }) => {
// 更新 tab 的 lineDesc 字段
routeDescTabs.value[index].routeAbbr = value.trim()
})
.catch(() => {
console.log('User cancels modification')
})
}
const handleChangeSelectStops = (value, sort) => {
console.log(value, sort, 'dddd')
// debugger
filteredOptions.value.some((item, index) => {
if (item.stopId == value) {
const tabIndex = tableDescIndex.value
const stops = routeDescTabs.value[tabIndex].planStopList
// // 获取新增索引
const addedIndex = sort - 1
// 获取原始 stopId
// const oldStop = stops[addedIndex]?.stopId
// 设置当前站点信息
routeDescTabs.value[tabIndex].planStopList[addedIndex] = {
...stops[addedIndex],
stopId: item.stopId,
stopDesc: item.stopDesc,
lng: item.lng,
lat: item.lat
}
// 新增及替换点的更改
if (routeDescTabs.value[tableDescIndex.value].lineId != '') {
emit('stop-added', {
stopIndex: addedIndex,
positionType: determinePositionType(addedIndex),
behindStation: addedIndex > 0 ? stops[addedIndex] : null
})
}
}
})
}
const handleTabChange = () => {
emit('update:tabIndex', tableDescIndex.value)
}
// 计算当前选项卡的有效 stopId 数量
const validStopCount = computed(() => {
const currentTab = routeDescTabs.value[tableDescIndex.value]
if (!currentTab?.planStopList) return 0
return currentTab.planStopList.filter((stop) => stop.stopId).length
})
// 监听站点数量变化并触发更新
watch(
validStopCount,
(newCount, oldCount) => {
if (newCount !== oldCount) {
// console.log('站点数量变化', newCount, oldCount, tableDescIndex.value, routeDescTabs.value)
const currentTab = routeDescTabs.value[tableDescIndex.value]
// 新增
if (currentTab && currentTab.lineId == '') {
// debugger
// console.log(routeDescTabs.value, 'routeDescTabs.value')
emit('update-line-detail', routeDescTabs.value)
}
}
},
{ immediate: true }
)
watchEffect(() => {
if (props.lineRouteInfoList && props.lineRouteInfoList.length > 0) {
routeDescTabs.value = [...props.lineRouteInfoList]
console.log(routeDescTabs.value, 'routeDescTabs.value')
}
})
onBeforeMount(() => {
getAllStopsList()
})
onMounted(() => {})
</script>
<style lang="scss" scoped>
$prefix-cls: #{$namespace}-route-desc;
.#{$prefix-cls} {
.el-select-dropdown {
max-height: 300px;
}
width: 458px;
background: rgba(255, 255, 255, 0.7);
border-radius: 4px;
padding: 0px 16px;
overflow-y: auto;
&__tabs {
height: calc(100vh - 110px);
.tab-title {
display: flex;
align-items: center;
gap: 6px;
padding: 0 8px;
&:hover .el-icon {
opacity: 1;
}
.el-icon {
opacity: 0;
transition: opacity 0.2s;
&:hover {
color: #409eff;
}
}
}
}
&__table {
height: calc(100vh - 175px);
// 表头圆角
:deep(.el-table__header) {
border-radius: 6px;
overflow: hidden;
}
// 表内容行间距
:deep(.el-table__body) {
border-collapse: separate !important;
border-spacing: 0 7px !important; /* 第二个值控制行间距 */
overflow: hidden !important;
}
// 表内容每一行的首尾单元格具有圆角效果
:deep(.el-table__body-wrapper table) {
border-collapse: separate;
overflow: hidden;
}
:deep(.el-table__body tr td:first-child),
:deep(.el-table__body tr td:last-child) {
position: relative;
}
:deep(.el-table__body tr td:first-child) {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
:deep(.el-table__body tr td:last-child) {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
.add-icon {
opacity: 0;
transition: opacity 0.2s ease;
}
tr:hover .add-icon {
opacity: 1;
cursor: pointer;
margin-left: 6px;
}
}
}
</style>
<style lang="scss">
$prefix-cls: #{$namespace}-route-desc;
.#{$prefix-cls} {
}
</style>
当前页vSelectLoadmore 在搜索数据出来后无法获取到滚动事件的原因
最新发布