92 lines
3.4 KiB
Vue
92 lines
3.4 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref } from 'vue'
|
||
import { ChevronDown } from '@lucide/vue'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Checkbox } from '@/components/ui/checkbox'
|
||
import { Label } from '@/components/ui/label'
|
||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||
import { sourceName } from './sources'
|
||
|
||
const props = defineProps<{
|
||
modelValue: string[]
|
||
/** 可选源(客户端名)列表 */
|
||
options: string[]
|
||
}>()
|
||
const emit = defineEmits<{ 'update:model-value': [string[]] }>()
|
||
|
||
const open = ref(false)
|
||
|
||
const checked = computed(() => new Set(props.modelValue))
|
||
|
||
const toggle = (code: string) => {
|
||
const next = new Set(checked.value)
|
||
if (next.has(code)) next.delete(code)
|
||
else next.add(code)
|
||
emit('update:model-value', [...next])
|
||
}
|
||
|
||
const selectAll = () => emit('update:model-value', [...props.options])
|
||
const clearAll = () => emit('update:model-value', [])
|
||
|
||
const label = computed(() => {
|
||
if (props.modelValue.length === 0) return '选择搜索源'
|
||
if (props.modelValue.length === 1) return sourceName(props.modelValue[0])
|
||
return `已选 ${props.modelValue.length} 个源`
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<Popover v-model:open="open">
|
||
<PopoverTrigger as-child>
|
||
<Button variant="outline" size="sm" class="h-9 max-w-56 justify-between font-normal">
|
||
<span class="truncate">{{ label }}</span>
|
||
<ChevronDown class="size-3.5 shrink-0 opacity-50" />
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent class="w-64 p-2" align="start">
|
||
<div class="mb-1 flex items-center justify-between px-1">
|
||
<span class="text-[11px] text-muted-foreground">搜索源</span>
|
||
<div class="flex items-center gap-2">
|
||
<button
|
||
type="button"
|
||
class="text-[11px] text-muted-foreground hover:text-foreground disabled:opacity-40"
|
||
:disabled="modelValue.length === options.length"
|
||
@click="selectAll"
|
||
>
|
||
全选
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="text-[11px] text-muted-foreground hover:text-foreground disabled:opacity-40"
|
||
:disabled="modelValue.length === 0"
|
||
@click="clearAll"
|
||
>
|
||
清空
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<!-- ScrollArea 的 viewport 为 h-full,需要根节点有确定高度才能滚动 -->
|
||
<ScrollArea class="h-72">
|
||
<div class="space-y-0.5 pr-2">
|
||
<label
|
||
v-for="code in options"
|
||
:key="code"
|
||
class="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 hover:bg-accent/60"
|
||
>
|
||
<Checkbox :model-value="checked.has(code)" @update:model-value="toggle(code)" />
|
||
<Label class="cursor-pointer truncate text-[13px]">{{ sourceName(code) }}</Label>
|
||
</label>
|
||
<p v-if="options.length === 0" class="px-2 py-3 text-[11.5px] text-muted-foreground">
|
||
未获取到可用源,请先在「设置 → 环境与诊断」安装运行时。
|
||
</p>
|
||
</div>
|
||
</ScrollArea>
|
||
<div class="mt-1 flex items-center justify-between border-t px-1 pt-1.5">
|
||
<span class="text-[11px] text-muted-foreground">{{ options.length }} 个可选源</span>
|
||
<span class="text-[11px] text-muted-foreground">已选 {{ modelValue.length }}</span>
|
||
</div>
|
||
</PopoverContent>
|
||
</Popover>
|
||
</template>
|