spring
2 天以前 b4e559be27b15cef3388cca703d916d591d05bbd
src/components/SearchPanel/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,257 @@
<template>
  <div class="search-panel-container">
    <el-form
      ref="formRef"
      :model="modelValue"
      class="search-form"
      label-width="0"
    >
      <el-row :gutter="10" class="form-row">
        <!-- æ¸²æŸ“表单项 -->
        <el-col
          v-for="(item, index) in visibleSchema"
          :key="item.prop || index"
          :xs="24"
          :sm="12"
          :md="8"
          :lg="4"
          :xl="4"
          class="search-col"
        >
          <el-form-item :prop="item.prop" :rules="item.rules" class="search-form-item">
            <!-- è‡ªå®šä¹‰æ’æ§½ -->
            <slot v-if="item.slot" :name="item.slot" :item="item"></slot>
            <!-- é»˜è®¤æ¸²æŸ“类型 -->
            <template v-else>
              <!-- è¾“入框 -->
              <el-input
                v-if="item.type === 'input'"
                v-model="modelValue[item.prop]"
                :placeholder="item.placeholder || '请输入'"
                clearable
                class="full-width"
                v-bind="item.props"
                @keyup.enter="handleSearch"
              />
              <!-- ä¸‹æ‹‰æ¡† -->
              <el-select
                v-else-if="item.type === 'select'"
                v-model="modelValue[item.prop]"
                :placeholder="item.placeholder || '请选择'"
                clearable
                class="full-width"
                v-bind="item.props"
              >
                {{ item || '请选择' }}
                <!-- <el-option
                  v-for="(opt,idx) in getOptions(item)"
                  :key="idx"
                  :label="opt.label"
                  :value="opt.value"
                /> -->
              </el-select>
              <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
              <el-date-picker
                v-else-if="item.type === 'date'"
                v-model="modelValue[item.prop]"
                type="date"
                :placeholder="item.placeholder || '选择日期'"
                style="width: 100%"
                value-format="YYYY-MM-DD"
                class="full-width"
                v-bind="item.props"
              />
              <!-- æ—¥æœŸèŒƒå›´é€‰æ‹©å™¨ -->
              <el-date-picker
                v-else-if="item.type === 'daterange'"
                v-model="modelValue[item.prop]"
                type="daterange"
                range-separator="至"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                value-format="YYYY-MM-DD"
                class="full-width"
                v-bind="item.props"
              />
            </template>
          </el-form-item>
        </el-col>
        <!-- æŒ‰é’®åŒºåŸŸ -->
        <el-col :xs="24" :sm="12" :md="8" :lg="4" :xl="4" class="search-actions-col">
          <el-form-item class="search-actions">
            <el-button style="background: #002FA7; color: white;" icon="Search" @click="handleSearch">搜索</el-button>
            <el-button icon="Refresh" @click="handleReset">重置</el-button>
          </el-form-item>
        </el-col>
      </el-row>
      <!-- å±•å¼€/收起按钮 -->
      <div v-if="schema.length > 5" class="expand-toggle" @click="toggleExpand">
        <span>{{ isExpanded ? '收起' : '展开' }}</span>
        <el-icon :class="{ 'is-reverse': isExpanded }">
          <ArrowDown />
        </el-icon>
      </div>
    </el-form>
  </div>
</template>
<script setup name="SearchPanel">
import { ref, reactive, computed, getCurrentInstance, onMounted } from 'vue';
import { ArrowDown, Search, Refresh } from '@element-plus/icons-vue';
const { proxy } = getCurrentInstance();
const props = defineProps({
  // è¡¨å•数据对象
  modelValue: {
    type: Object,
    required: true
  },
  // è¡¨å•配置项
  schema: {
    type: Array,
    default: () => []
  }
});
const emit = defineEmits(['update:modelValue', 'search', 'reset']);
// æ˜¯å¦å±•å¼€
const isExpanded = ref(false);
const formRef = ref(null);
const dictMap = reactive({});
// è®¡ç®—可见的 schema é¡¹
const visibleSchema = computed(() => {
  if (isExpanded.value || props.schema.length <= 5) {
    return props.schema;
  }
  return props.schema.slice(0, 5);
});
// åˆå§‹åŒ–字典数据
onMounted(() => {
  const dicts = props.schema.filter(item => item.dict).map(item => item.dict);
  if (dicts.length > 0 && proxy.useDict) {
    const dictData = proxy.useDict(...dicts);
    Object.keys(dictData).forEach(key => {
      dictMap[key] = dictData[key];
    });
  }
});
// èŽ·å–ä¸‹æ‹‰é€‰é¡¹ (支持静态 options å’Œ å­—å…¸ dict)
function getOptions(item) {
  if (item.options) return item.options;
  if (item.dict && dictMap[item.dict]) {
    return dictMap[item.dict].value || [];
  }
  return [];
}
// æœç´¢
function handleSearch() {
  emit('search', props.modelValue);
}
// é‡ç½®
function handleReset() {
  if (formRef.value) {
    formRef.value.resetFields();
  }
  const keys = props.schema.map(item => item.prop).filter(Boolean);
  keys.forEach(key => {
    props.modelValue[key] = undefined;
  });
  emit('update:modelValue', props.modelValue);
  emit('reset');
}
// åˆ‡æ¢å±•å¼€/收起
function toggleExpand() {
  isExpanded.value = !isExpanded.value;
}
</script>
<style scoped lang="scss">
.search-panel-container {
  background: #fff;
  padding: 15px 15px 5px;
  border-radius: 8px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
  margin-bottom: 15px;
  .search-form {
    .form-row {
      width: 100%;
    }
    .search-col {
      margin-bottom: 10px;
    }
    .search-form-item {
      margin-right: 0;
      margin-bottom: 0;
      width: 100%;
      :deep(.el-form-item__content) {
        width: 100%;
      }
    }
    .full-width {
      width: 100% !important;
    }
    .search-actions-col {
      margin-left: auto;
      display: flex;
      justify-content: flex-end;
      margin-bottom: 10px;
    }
    .search-actions {
      margin-bottom: 0;
      margin-right: 0;
      :deep(.el-button--primary) {
        background-color: #409eff;
        border-color: #409eff;
      }
    }
    .expand-toggle {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 4px;
      font-size: 13px;
      color: #909399;
      cursor: pointer;
      padding: 5px 0;
      user-select: none;
      width: 100%;
      border-top: 1px solid #f0f2f5;
      margin-top: 5px;
      &:hover {
        color: #409eff;
      }
      .el-icon {
        transition: transform 0.3s;
        &.is-reverse {
          transform: rotate(180deg);
        }
      }
    }
  }
}
</style>