Toolbar.vue 4.14 KB
<script setup>
/**
 * 筛选工具条。**列表页的筛选区只能用它** —— 页面自己搭栅格,就会各拍各的列数和宽度,一个后台里
 * 三种筛选区长相。
 *
 * **一格 = label 在上、控件在下**,不是 label 贴在控件左边横排 —— 后者把一排筛选读成一句流水句
 * (「时间窗 [x] 状态 [y] 厂商 [z]…」),字段边界只能靠数空格找,且控件左边被长短不一的中文标签顶得
 * 参差不齐。
 *
 * **宽度由栅格给,不由控件自己拍。** 每格 `minmax(200px, 1fr)`,列数按容器宽度自适应;控件填满这一格。
 * 各页面不要再给单个字段写死宽度 —— 那正是「用户框比模型框宽一倍」的来源。
 *
 * 用法:每个字段包一层 `.drama-toolbar-field`(内含 `<label>` + 控件);`actions` slot 放查询/重置,
 * 它和最后一行控件**底边对齐**。字段值特别长(request id、时间区间)时加 `.drama-toolbar-field--wide`
 * 占两格。
 *
 * `advanced` slot 放展开式高级筛选:同一套栅格续在第二行,上面一条分隔线。展开与否由页面控制
 * (`v-if`),行怎么排由这里定 —— 页面不要为了第二行再搓一个栅格。
 *
 * `compact` 给只有一两个开关的行(区块标题栏里的启用筛选、图表的维度切换):控件保持自然宽度横排,
 * 不进栅格 —— 三个选项的分段控件被拉到一整格宽,是另一种难看。
 */

defineProps({
  /** 控件按自然宽度横排,不进栅格。 */
  compact: { type: Boolean, default: false },
})
</script>

<template>
  <div
    class="drama-toolbar"
    :class="{ 'is-compact': compact }"
  >
    <div class="drama-toolbar__main">
      <div class="drama-toolbar__filters">
        <slot />
      </div>
      <div
        v-if="$slots.actions"
        class="drama-toolbar__actions"
      >
        <slot name="actions" />
      </div>
    </div>
    <div
      v-if="$slots.advanced"
      class="drama-toolbar__advanced"
    >
      <slot name="advanced" />
    </div>
  </div>
</template>

<style scoped>
.drama-toolbar {
  display: grid;
  gap: var(--drama-space-lg);
}

.drama-toolbar__main {
  display: flex;
  /* 控件底边成一条线:label 高度不一时也不会把控件顶得错位。 */
  align-items: flex-end;
  gap: var(--drama-space-lg);
  flex-wrap: wrap;
}

.drama-toolbar__filters,
.drama-toolbar__advanced {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--drama-space-md) var(--drama-space-lg);
  align-items: end;
}

.drama-toolbar__filters {
  flex: 1 1 auto;
}

.drama-toolbar__advanced {
  padding-top: var(--drama-space-lg);
  border-top: 1px solid var(--drama-color-border);
}

.drama-toolbar__actions {
  display: flex;
  align-items: center;
  gap: var(--drama-space-sm);
  margin-left: auto;
}

.drama-toolbar :deep(.drama-toolbar-field) {
  display: flex;
  flex-direction: column;
  gap: var(--drama-space-xs);
  /* 不给 0,长值会把这一格撑出栅格。 */
  min-width: 0;
}

.drama-toolbar :deep(.drama-toolbar-field > label) {
  color: var(--drama-color-text-muted);
  font-size: var(--drama-font-size-xs);
  font-weight: var(--drama-font-weight-medium);
  line-height: var(--drama-line-height-tight);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 控件撑满这一格 —— 宽度是栅格的事。 */
.drama-toolbar :deep(.drama-input),
.drama-toolbar :deep(.drama-select),
.drama-toolbar :deep(.drama-segmented) {
  display: flex;
  width: 100%;
}

.drama-toolbar :deep(.drama-segmented__item) {
  flex: 1 1 0;
  min-width: 0;
}

/* 分段控件横排 3–4 个选项,一格塞不下(英文 "Aborted" 就溢出);它天生占两格。 */
.drama-toolbar :deep(.drama-toolbar-field:has(.drama-segmented)),
.drama-toolbar :deep(.drama-toolbar-field--wide) {
  grid-column: span 2;
}

.drama-toolbar.is-compact .drama-toolbar__filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
}

.drama-toolbar.is-compact :deep(.drama-input),
.drama-toolbar.is-compact :deep(.drama-select),
.drama-toolbar.is-compact :deep(.drama-segmented) {
  width: auto;
}

.drama-toolbar.is-compact :deep(.drama-segmented__item) {
  flex: 0 0 auto;
}
</style>