You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
6.9 KiB
323 lines
6.9 KiB
<script lang="ts" setup>
|
|
import { defineEmits, defineProps, onBeforeMount, onMounted, ref, toRefs, watchEffect } from 'vue'
|
|
|
|
const props = defineProps({
|
|
optionsLeft: {
|
|
type: Array as () => System.Option[],
|
|
required: true,
|
|
},
|
|
options: {
|
|
type: Array as () => System.Option[],
|
|
required: true,
|
|
},
|
|
selectedValue: {
|
|
type: [String, Number, Boolean, Object],
|
|
default: null,
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: '请选择',
|
|
},
|
|
searchable: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
defaultValue: {
|
|
type: Number,
|
|
default: 0,
|
|
},
|
|
})
|
|
|
|
const emits = defineEmits(['confirm', 'cancel'])
|
|
const optionsList = ref<HTMLUListElement | null>(null)
|
|
const { optionsLeft, options } = toRefs(props)
|
|
|
|
const tempSelectedLeftValue = ref('positivePressure')
|
|
|
|
const tempSelectedRightValue = ref(props.defaultValue)
|
|
|
|
const tempSelectedValue = ref<string[]>(['positivePressure', '10%'])
|
|
|
|
const filteredOptionsLeft = ref<System.Option[]>([])
|
|
|
|
const filteredOptionsRight = ref<System.Option[]>([])
|
|
|
|
onBeforeMount(() => {
|
|
filteredOptionsLeft.value = optionsLeft.value.filter(item => item.value)
|
|
filteredOptionsRight.value = options.value.filter(item => item.value)
|
|
})
|
|
|
|
onMounted(() => {
|
|
scrollToSelectedItem()
|
|
})
|
|
|
|
const scrollToSelectedItem = () => {
|
|
if (!optionsList.value) {
|
|
return
|
|
}
|
|
// 获取选中的li元素(带selected类的)
|
|
const selectedLi = optionsList.value.querySelector('li.selected')
|
|
if (selectedLi) {
|
|
const containerRect = optionsList.value.getBoundingClientRect()
|
|
const itemRect = selectedLi.getBoundingClientRect()
|
|
const offsetTop = itemRect.top - containerRect.top
|
|
const scrollTop = offsetTop - (containerRect.height - itemRect.height) / 2
|
|
optionsList.value.scrollTop = scrollTop + 10
|
|
}
|
|
}
|
|
|
|
const selectOptionLeft = (option: System.Option) => {
|
|
if (option.value === 'constantPressure') {
|
|
tempSelectedValue.value = [option.value]
|
|
filteredOptionsRight.value = []
|
|
}
|
|
else {
|
|
tempSelectedValue.value[0] = option.value
|
|
filteredOptionsRight.value = options.value.filter(item => item.value)
|
|
}
|
|
tempSelectedLeftValue.value = option.value
|
|
}
|
|
|
|
const selectOption = (option: System.Option) => {
|
|
tempSelectedRightValue.value = option.value
|
|
tempSelectedValue.value[1] = option.value
|
|
}
|
|
|
|
const confirmSelection = () => {
|
|
emits('confirm', tempSelectedValue.value)
|
|
}
|
|
|
|
const handleCancel = () => {
|
|
emits('cancel')
|
|
}
|
|
|
|
watchEffect(() => {
|
|
tempSelectedRightValue.value = props.defaultValue
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="modal-overlay" @click.self="handleCancel">
|
|
<div class="modal-container">
|
|
<div>
|
|
<div class="modal-header">
|
|
<h3>{{ placeholder }}</h3>
|
|
<button class="close-btn" @click="handleCancel">
|
|
<i class="fa fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="modal-main">
|
|
<div class="modal-content">
|
|
<ul class="options-list">
|
|
<li
|
|
v-for="(option, index) in filteredOptionsLeft"
|
|
:key="option.value || index"
|
|
:class="{ selected: option.value === tempSelectedLeftValue }"
|
|
@click="selectOptionLeft(option)"
|
|
>
|
|
{{ option.label }}
|
|
</li>
|
|
</ul>
|
|
<div v-if="!filteredOptionsLeft.length" class="no-results">
|
|
没有找到匹配项
|
|
</div>
|
|
</div>
|
|
<div ref="optionsList" class="modal-content-right">
|
|
<ul class="options-list">
|
|
<li
|
|
v-for="(option, index) in filteredOptionsRight"
|
|
:key="option.value || index"
|
|
:class="{ selected: option.value === tempSelectedRightValue }"
|
|
@click="selectOption(option)"
|
|
>
|
|
{{ option.label }}
|
|
</li>
|
|
</ul>
|
|
<div v-if="!filteredOptionsRight.length" class="no-results">
|
|
没有找到匹配项
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="cancel-btn" @click="handleCancel">取消</button>
|
|
<button class="confirm-btn" @click="confirmSelection">确定</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 200;
|
|
}
|
|
|
|
.modal-container {
|
|
background-color: white;
|
|
border-radius: 12px;
|
|
width: 90%;
|
|
max-width: 400px;
|
|
max-height: 80vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
animation: fadeIn 0.2s ease-out;
|
|
}
|
|
|
|
.modal-header {
|
|
padding: 16px 20px;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.modal-header h3 {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
font-weight: 500;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.close-btn {
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
font-size: 18px;
|
|
color: #94a3b8;
|
|
transition: color 0.2s ease;
|
|
}
|
|
|
|
.close-btn:hover {
|
|
color: #64748b;
|
|
}
|
|
|
|
.modal-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 10px 0;
|
|
max-height: 15vw;
|
|
}
|
|
.modal-content-right{
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 10px 0;
|
|
max-height: 15vw;
|
|
}
|
|
|
|
.search-box {
|
|
position: relative;
|
|
padding: 8px 16px;
|
|
}
|
|
|
|
.search-box input {
|
|
width: 100%;
|
|
padding: 10px 32px 10px 12px;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
outline: none;
|
|
transition: border-color 0.2s ease;
|
|
}
|
|
|
|
.search-box input:focus {
|
|
border-color: #3b82f6;
|
|
}
|
|
|
|
.search-box i {
|
|
position: absolute;
|
|
right: 28px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.options-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.options-list li {
|
|
padding: 14px 20px;
|
|
font-size: 16px;
|
|
color: #334155;
|
|
cursor: pointer;
|
|
transition: background-color 0.2s ease;
|
|
text-align: center;
|
|
}
|
|
|
|
.options-list li:hover {
|
|
background-color: #f1f5f9;
|
|
}
|
|
|
|
.options-list li.selected {
|
|
background-color: #a4c4f1;
|
|
color: #0284c7;
|
|
font-weight: 500;
|
|
text-align: center;
|
|
}
|
|
|
|
.no-results {
|
|
padding: 16px 20px;
|
|
font-size: 14px;
|
|
color: #94a3b8;
|
|
text-align: center;
|
|
}
|
|
|
|
.modal-footer {
|
|
padding: 12px 16px;
|
|
border-top: 1px solid #e2e8f0;
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.modal-footer button {
|
|
flex: 1;
|
|
padding: 10px 16px;
|
|
border-radius: 8px;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.cancel-btn {
|
|
background-color: #ffffff;
|
|
border: 1px solid #e2e8f0;
|
|
color: #64748b;
|
|
}
|
|
|
|
.cancel-btn:hover {
|
|
background-color: #f8fafc;
|
|
}
|
|
|
|
.confirm-btn {
|
|
background-color: #3b82f6;
|
|
border: none;
|
|
color: white;
|
|
}
|
|
|
|
.confirm-btn:hover {
|
|
background-color: #2563eb;
|
|
}
|
|
|
|
.modal-main {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: scale(0.95); }
|
|
to { opacity: 1; transform: scale(1); }
|
|
}
|
|
</style>
|