287 lines
6.0 KiB
Vue
287 lines
6.0 KiB
Vue
<template>
|
|
<div class="warehouse-selector">
|
|
<el-input
|
|
:model-value="modelValue"
|
|
readonly
|
|
placeholder="请选择库位"
|
|
@click="handleOpen"
|
|
clearable
|
|
@clear="handleClear"
|
|
>
|
|
<template #suffix>
|
|
<el-icon class="clear-icon" @click.stop="handleOpen">
|
|
<ArrowDown />
|
|
</el-icon>
|
|
</template>
|
|
</el-input>
|
|
|
|
<el-popover
|
|
ref="popoverRef"
|
|
:visible="popoverVisible"
|
|
placement="bottom-start"
|
|
:width="380"
|
|
trigger="click"
|
|
@update:visible="handleVisibleChange"
|
|
>
|
|
<template #reference>
|
|
<div ref="triggerRef" style="width: 0; height: 0; overflow: hidden;"></div>
|
|
</template>
|
|
|
|
<div class="selector-container">
|
|
<!-- 顶部导航区 -->
|
|
<div class="selector-header">
|
|
<div class="header-left">
|
|
<el-button
|
|
v-if="currentPath.length > 0"
|
|
type="primary"
|
|
link
|
|
size="small"
|
|
@click="handleBack"
|
|
>
|
|
<el-icon><ArrowLeft /></el-icon>
|
|
返回上一级
|
|
</el-button>
|
|
</div>
|
|
<div class="header-right">
|
|
<el-breadcrumb separator="/">
|
|
<el-breadcrumb-item>
|
|
<span class="breadcrumb-root" @click="handleGoHome">全部</span>
|
|
</el-breadcrumb-item>
|
|
<el-breadcrumb-item v-for="(item, index) in currentPath" :key="index">
|
|
<span class="breadcrumb-item">{{ item.name }}</span>
|
|
</el-breadcrumb-item>
|
|
</el-breadcrumb>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 列表展示区 -->
|
|
<div class="selector-body">
|
|
<div v-if="currentList.length === 0" class="empty-tip">
|
|
当前层级无库位数据
|
|
</div>
|
|
<ul v-else class="location-list">
|
|
<li
|
|
v-for="item in currentList"
|
|
:key="item.id"
|
|
class="location-item"
|
|
@click="handleSelect(item)"
|
|
>
|
|
<div class="item-left">
|
|
<el-icon><Location /></el-icon>
|
|
<span class="item-name">{{ item.name }}</span>
|
|
</div>
|
|
<div class="item-right">
|
|
<el-button
|
|
v-if="item.children && item.children.length > 0"
|
|
type="primary"
|
|
link
|
|
size="small"
|
|
@click.stop="handleDrillDown(item)"
|
|
>
|
|
进入下级
|
|
<el-icon><ArrowRight /></el-icon>
|
|
</el-button>
|
|
<el-tag v-else type="info" size="small">末级</el-tag>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</el-popover>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, watch } from 'vue'
|
|
import { ArrowDown, ArrowLeft, ArrowRight, Location } from '@element-plus/icons-vue'
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
interface WarehouseItem {
|
|
id: number
|
|
name: string
|
|
full_path: string
|
|
level: number
|
|
children?: WarehouseItem[]
|
|
}
|
|
|
|
interface Props {
|
|
modelValue?: string
|
|
options: WarehouseItem[]
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
modelValue: '',
|
|
options: () => []
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: string]
|
|
}>()
|
|
|
|
const popoverRef = ref()
|
|
const popoverVisible = ref(false)
|
|
const triggerRef = ref<HTMLElement>()
|
|
|
|
// 当前导航路径(保存每一层的节点信息)
|
|
const currentPath = ref<WarehouseItem[]>([])
|
|
|
|
// 当前显示的列表数据
|
|
const currentList = computed(() => {
|
|
if (currentPath.value.length === 0) {
|
|
// 顶层:显示根节点列表
|
|
return props.options
|
|
}
|
|
// 非顶层:显示当前层级最后一个节点的 children
|
|
const lastNode = currentPath.value[currentPath.value.length - 1]
|
|
return lastNode?.children || []
|
|
})
|
|
|
|
// 处理弹窗显示/隐藏
|
|
const handleVisibleChange = (visible: boolean) => {
|
|
popoverVisible.value = visible
|
|
if (!visible) {
|
|
// 关闭时重置导航
|
|
currentPath.value = []
|
|
}
|
|
}
|
|
|
|
// 打开弹窗
|
|
const handleOpen = () => {
|
|
popoverVisible.value = true
|
|
}
|
|
|
|
// 清空选择
|
|
const handleClear = () => {
|
|
emit('update:modelValue', '')
|
|
currentPath.value = []
|
|
}
|
|
|
|
// 返回上一级
|
|
const handleBack = () => {
|
|
if (currentPath.value.length > 0) {
|
|
currentPath.value.pop()
|
|
}
|
|
}
|
|
|
|
// 返回顶层
|
|
const handleGoHome = () => {
|
|
currentPath.value = []
|
|
}
|
|
|
|
// 进入下一级
|
|
const handleDrillDown = (item: WarehouseItem) => {
|
|
currentPath.value.push(item)
|
|
}
|
|
|
|
// 选择当前项
|
|
const handleSelect = (item: WarehouseItem) => {
|
|
emit('update:modelValue', item.full_path)
|
|
popoverVisible.value = false
|
|
currentPath.value = []
|
|
ElMessage.success(`已选择库位:${item.full_path}`)
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.warehouse-selector {
|
|
width: 100%;
|
|
}
|
|
|
|
.clear-icon {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.clear-icon:hover {
|
|
color: #409eff;
|
|
}
|
|
|
|
.selector-container {
|
|
margin: -12px;
|
|
}
|
|
|
|
.selector-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px;
|
|
border-bottom: 1px solid #ebeef5;
|
|
background: #f5f7fa;
|
|
}
|
|
|
|
.header-left {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.header-right {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.breadcrumb-root {
|
|
cursor: pointer;
|
|
color: #409eff;
|
|
}
|
|
|
|
.breadcrumb-root:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.breadcrumb-item {
|
|
max-width: 150px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.selector-body {
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.empty-tip {
|
|
padding: 40px 0;
|
|
text-align: center;
|
|
color: #909399;
|
|
}
|
|
|
|
.location-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.location-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 16px;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.location-item:hover {
|
|
background: #f5f7fa;
|
|
}
|
|
|
|
.item-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.item-left .el-icon {
|
|
color: #409eff;
|
|
}
|
|
|
|
.item-name {
|
|
font-size: 14px;
|
|
color: #303133;
|
|
}
|
|
|
|
.item-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
</style>
|