消毒机设备
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

<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>