Files
school-developer/src/components/SearchForm/index.vue
guochunsi d283a10257 a
2026-01-21 17:21:14 +08:00

220 lines
5.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="search-form-container">
<el-form :model="formModel" ref="formRef" :inline="true" @keyup.enter="handleKeyupEnter" :label-width="labelWidth">
<!-- 直接展示的表单项 -->
<slot :visible="true" :expanded="isExpanded"></slot>
<!-- 可折叠的表单项区域 - 使用 display: contents 确保不影响布局 -->
<div v-show="isExpanded" class="collapsible-content">
<slot :visible="false" :expanded="isExpanded" @has-content="hasCollapsibleContent = true"></slot>
</div>
<!-- 隐藏的检测元素用于检测是否有可折叠内容 -->
<div v-show="false" ref="detectionWrapperRef" class="detection-wrapper">
<slot :visible="false" :expanded="true">
<!-- 如果插槽有内容这里会被渲染我们可以通过检查这个元素来判断 -->
</slot>
</div>
<!-- 操作按钮插槽查询重置等 -->
<slot name="actions"></slot>
<!-- 展开/收起按钮在操作按钮之后 -->
<el-form-item v-if="hasCollapsibleItems" class="collapse-trigger-item">
<el-button
link
type="primary"
@click="toggleExpand"
class="toggle-btn"
>
<el-icon style="margin-right: 4px">
<ArrowUp v-if="isExpanded" />
<ArrowDown v-else />
</el-icon>
{{ isExpanded ? '收起' : '展开更多' }}
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts" name="search-form">
import { ref, watch, computed, onMounted, nextTick } from 'vue';
import { ArrowUp, ArrowDown } from '@element-plus/icons-vue';
const props = defineProps({
/**
* 表单数据模型
*/
model: {
type: Object,
required: true,
},
/**
* 是否默认展开折叠区域
*/
defaultExpanded: {
type: Boolean,
default: false,
},
/**
* 是否显示展开/折叠按钮
* 如果不设置将自动检测是否有可折叠内容visible=false 的插槽)
*/
showCollapse: {
type: Boolean,
default: undefined,
},
/**
* 表单项标签宽度
*/
labelWidth: {
type: String,
default: '',
},
});
const emit = defineEmits(['expand-change', 'keyup-enter']);
const formModel = props.model;
const formRef = ref();
const isExpanded = ref(props.defaultExpanded);
const hasCollapsibleContent = ref(false);
const detectionWrapperRef = ref<HTMLElement | null>(null);
// 检测是否有可折叠内容
const checkCollapsibleContent = () => {
// 如果 showCollapse 明确指定,则使用指定值
if (props.showCollapse !== undefined) {
hasCollapsibleContent.value = props.showCollapse
return
}
// 否则,通过检查隐藏的检测元素是否有内容来判断
// 需要等待 DOM 渲染完成,可能需要多次尝试以确保数据加载完成
let retryCount = 0
const maxRetries = 5
const check = () => {
nextTick(() => {
setTimeout(() => {
if (detectionWrapperRef.value) {
// 检查检测元素是否有子元素(排除文本节点)
// 检查是否有 el-form-item 元素(因为表单项会被渲染为 el-form-item
const hasContent = detectionWrapperRef.value.children.length > 0 ||
detectionWrapperRef.value.querySelector('.el-form-item') !== null ||
(!!detectionWrapperRef.value.textContent && detectionWrapperRef.value.textContent.trim() !== '')
if (hasContent || retryCount >= maxRetries) {
hasCollapsibleContent.value = hasContent
} else {
// 如果还没检测到内容且未达到最大重试次数,继续重试
retryCount++
setTimeout(check, 100)
}
} else {
if (retryCount < maxRetries) {
retryCount++
setTimeout(check, 100)
} else {
hasCollapsibleContent.value = false
}
}
}, 50)
})
}
check()
}
// 是否有需要折叠的项
const hasCollapsibleItems = computed(() => {
if (props.showCollapse !== undefined) {
return props.showCollapse
}
return hasCollapsibleContent.value
});
// 处理回车键
const handleKeyupEnter = () => {
emit('keyup-enter');
};
// 切换展开/收起状态
const toggleExpand = () => {
isExpanded.value = !isExpanded.value;
emit('expand-change', isExpanded.value);
};
// 监听 defaultExpanded 变化
watch(
() => props.defaultExpanded,
(newVal) => {
isExpanded.value = newVal;
}
);
// 监听 showCollapse 变化
watch(
() => props.showCollapse,
() => {
checkCollapsibleContent()
}
);
// 初始化时检测
onMounted(() => {
checkCollapsibleContent()
});
// 暴露方法供外部调用
defineExpose({
formRef,
toggleExpand,
expand: () => {
isExpanded.value = true;
emit('expand-change', true);
},
collapse: () => {
isExpanded.value = false;
emit('expand-change', false);
},
});
</script>
<style lang="scss" scoped>
.search-form-container {
// 直接使用全局样式 el-form--inline只需要覆盖特殊样式
:deep(.el-form--inline) {
.collapse-trigger-item {
margin-left: 0;
margin-bottom: 0;
}
.toggle-btn {
padding: 0;
font-size: 14px;
}
// 如果需要自定义宽度,可以覆盖全局样式(默认使用全局的 240px
// 如果需要改为 200px取消下面的注释
// .el-form-item {
// & > .el-input,
// .el-cascader,
// .el-select,
// .el-date-editor,
// .el-autocomplete {
// width: 200px;
// }
// }
}
// 可折叠内容区域 - 使用 contents 让包装器不影响布局
.collapsible-content {
display: contents;
}
}
</style>