mirror of
https://github.com/fantastic-admin/basic.git
synced 2026-10-06 16:13:52 +08:00
refactor: 更新 CheckboxGroupOption 和 RadioGroupOption 接口,支持泛型扩展
This commit is contained in:
@@ -5,64 +5,58 @@ const options = [
|
||||
{
|
||||
label: '专注模式',
|
||||
value: 'focus',
|
||||
summary: '任务优先',
|
||||
description: '突出主任务,弱化辅助信息,适合录入和审批场景。',
|
||||
},
|
||||
{
|
||||
label: '平衡模式',
|
||||
value: 'balanced',
|
||||
summary: '默认体验',
|
||||
description: '信息密度与可读性保持平衡,适合作为默认配置。',
|
||||
},
|
||||
{
|
||||
label: '高密度模式',
|
||||
value: 'dense',
|
||||
summary: '信息看板',
|
||||
description: '在大屏中同时承载更多信息,适合运营与监控看板。',
|
||||
},
|
||||
]
|
||||
|
||||
const currentText = computed(() =>
|
||||
options
|
||||
.filter(option => value.value.includes(option.value))
|
||||
.map(option => option.label)
|
||||
.join('、'),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FaCheckboxGroup
|
||||
v-model="value"
|
||||
:options="options"
|
||||
class="gap-2 md:grid-cols-3"
|
||||
option-class="rounded-xl border border-transparent px-1 py-1"
|
||||
>
|
||||
<template #option="{ option, checked, disabled }">
|
||||
<div
|
||||
class="px-4 py-3 border rounded-xl flex gap-3 w-full transition-colors items-start justify-between" :class="[
|
||||
checked ? 'border-primary bg-primary/5' : 'border-border hover:border-primary/40',
|
||||
disabled && 'opacity-60',
|
||||
]"
|
||||
>
|
||||
<div class="gap-1 grid min-w-0">
|
||||
<div class="text-sm font-medium truncate">
|
||||
{{ option.label }}
|
||||
</div>
|
||||
<div
|
||||
v-if="option.description"
|
||||
class="text-xs text-muted-foreground leading-5"
|
||||
>
|
||||
{{ option.description }}
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="text-xs font-medium shrink-0" :class="[
|
||||
checked ? 'text-primary' : 'text-muted-foreground',
|
||||
<div class="gap-4 grid">
|
||||
<FaCheckboxGroup
|
||||
v-model="value"
|
||||
:options="options"
|
||||
class="gap-2 md:grid-cols-3"
|
||||
option-class="rounded-xl border border-transparent px-1 py-1"
|
||||
>
|
||||
<template #option="{ option, checked, disabled }">
|
||||
<div
|
||||
class="px-4 py-3 border rounded-xl flex gap-3 w-full transition-colors items-start justify-between" :class="[
|
||||
checked ? 'border-primary bg-primary/5' : 'border-border hover:border-primary/40',
|
||||
disabled && 'opacity-60',
|
||||
]"
|
||||
>
|
||||
{{ checked ? '已选中' : '可选择' }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</FaCheckboxGroup>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
当前模式:{{ currentText }}
|
||||
<div class="gap-1 grid min-w-0">
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="text-sm font-medium truncate">{{ option.label }}</span>
|
||||
<span class="text-xs text-muted-foreground px-1.5 py-0.5 rounded bg-muted">
|
||||
{{ option.summary }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground leading-5">
|
||||
{{ option.description }}
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-xs font-medium shrink-0" :class="checked ? 'text-primary' : 'text-muted-foreground'">
|
||||
{{ checked ? '已选中' : '可选择' }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</FaCheckboxGroup>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
当前值:{{ value.join('、') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -5,32 +5,30 @@ const options = [
|
||||
{
|
||||
label: '专注模式',
|
||||
value: 'focus',
|
||||
badge: '录入',
|
||||
description: '突出主任务,弱化辅助信息,适合录入和审批场景。',
|
||||
},
|
||||
{
|
||||
label: '平衡模式',
|
||||
value: 'balanced',
|
||||
badge: '推荐',
|
||||
description: '信息密度与可读性保持平衡,适合作为默认配置。',
|
||||
},
|
||||
{
|
||||
label: '高密度模式',
|
||||
value: 'dense',
|
||||
badge: '大屏',
|
||||
description: '在大屏中同时承载更多信息,适合运营与监控看板。',
|
||||
},
|
||||
]
|
||||
|
||||
const currentLabel = computed(() =>
|
||||
options.find(option => option.value === value.value)?.label ?? '',
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gap-4 grid">
|
||||
<FaRadioGroup
|
||||
v-model="value"
|
||||
orientation="horizontal"
|
||||
:options="options"
|
||||
class="gap-4 md:grid-cols-3"
|
||||
class="gap-2 md:grid-cols-3"
|
||||
option-class="rounded-xl border border-transparent px-1 py-1"
|
||||
>
|
||||
<template #option="{ option, checked, disabled }">
|
||||
@@ -41,28 +39,24 @@ const currentLabel = computed(() =>
|
||||
]"
|
||||
>
|
||||
<div class="gap-1 grid min-w-0">
|
||||
<div class="text-sm font-medium truncate">
|
||||
{{ option.label }}
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="text-sm font-medium truncate">{{ option.label }}</span>
|
||||
<span class="text-xs text-muted-foreground px-1.5 py-0.5 rounded bg-muted">
|
||||
{{ option.badge }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="option.description"
|
||||
class="text-xs text-muted-foreground leading-5"
|
||||
>
|
||||
<div class="text-xs text-muted-foreground leading-5">
|
||||
{{ option.description }}
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="text-xs font-medium shrink-0" :class="[
|
||||
checked ? 'text-primary' : 'text-muted-foreground',
|
||||
]"
|
||||
>
|
||||
<span class="text-xs font-medium shrink-0" :class="checked ? 'text-primary' : 'text-muted-foreground'">
|
||||
{{ checked ? '已选中' : '可选择' }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</FaRadioGroup>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
当前模式:{{ currentLabel }}
|
||||
当前值:{{ value }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
### CheckboxGroupOption 接口
|
||||
|
||||
```ts
|
||||
interface CheckboxGroupOption {
|
||||
export interface CheckboxGroupOption {
|
||||
label: string
|
||||
value: AcceptableValue
|
||||
description?: string
|
||||
@@ -40,7 +40,7 @@ interface CheckboxGroupOption {
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| `option` | 自定义选项内容,slot props: `{ option, checked, disabled, id }` |
|
||||
| `option` | 自定义选项内容,slot props: `{ option, checked, disabled, id }`,`option` 会保留 `options` 中的扩展字段类型 |
|
||||
|
||||
## Events
|
||||
|
||||
@@ -173,16 +173,19 @@ const options = [
|
||||
{
|
||||
label: '专注模式',
|
||||
value: 'focus',
|
||||
summary: '任务优先',
|
||||
description: '突出主任务,弱化辅助信息。',
|
||||
},
|
||||
{
|
||||
label: '平衡模式',
|
||||
value: 'balanced',
|
||||
summary: '默认体验',
|
||||
description: '信息密度与可读性保持平衡。',
|
||||
},
|
||||
{
|
||||
label: '高密度模式',
|
||||
value: 'dense',
|
||||
summary: '信息看板',
|
||||
description: '适合大屏运营与监控看板。',
|
||||
},
|
||||
]
|
||||
@@ -202,8 +205,13 @@ const options = [
|
||||
]"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<div class="truncate font-medium">{{ option.label }}</div>
|
||||
<div v-if="option.description" class="text-xs text-muted-foreground leading-5">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="truncate font-medium">{{ option.label }}</span>
|
||||
<span class="rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
|
||||
{{ option.summary }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground leading-5">
|
||||
{{ option.description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Regular → Executable
+13
-13
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" generic="TOption extends CheckboxGroupOption">
|
||||
import type { AcceptableValue } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '../../utils'
|
||||
import { Checkbox } from '../checkbox/checkbox'
|
||||
import { Label } from '../label/label'
|
||||
|
||||
interface CheckboxGroupOption {
|
||||
export interface CheckboxGroupOption {
|
||||
label: string
|
||||
value: AcceptableValue
|
||||
description?: string
|
||||
@@ -18,7 +18,7 @@ defineOptions({
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
options: CheckboxGroupOption[]
|
||||
options: TOption[]
|
||||
disabled?: boolean
|
||||
min?: number
|
||||
max?: number
|
||||
@@ -35,7 +35,7 @@ const emit = defineEmits<{
|
||||
|
||||
const slots = defineSlots<{
|
||||
option?: (props: {
|
||||
option: CheckboxGroupOption
|
||||
option: TOption
|
||||
checked: boolean
|
||||
disabled: boolean
|
||||
id: string
|
||||
@@ -53,11 +53,11 @@ watch(value, (newValue) => {
|
||||
emit('change', newValue)
|
||||
})
|
||||
|
||||
function getOptionId(option: CheckboxGroupOption, index: number) {
|
||||
function getOptionId(option: TOption, index: number) {
|
||||
return option.id || `${baseId}-${index}`
|
||||
}
|
||||
|
||||
function getOptionKey(option: CheckboxGroupOption, index: number) {
|
||||
function getOptionKey(option: TOption, index: number) {
|
||||
if (option.id) {
|
||||
return option.id
|
||||
}
|
||||
@@ -67,7 +67,7 @@ function getOptionKey(option: CheckboxGroupOption, index: number) {
|
||||
: index
|
||||
}
|
||||
|
||||
function isOptionDisabled(option: CheckboxGroupOption) {
|
||||
function isOptionDisabled(option: TOption) {
|
||||
if (props.disabled || option.disabled) {
|
||||
return true
|
||||
}
|
||||
@@ -85,15 +85,15 @@ function isOptionDisabled(option: CheckboxGroupOption) {
|
||||
return false
|
||||
}
|
||||
|
||||
function findOptionIndex(option: CheckboxGroupOption) {
|
||||
function findOptionIndex(option: TOption) {
|
||||
return value.value.findIndex(item => Object.is(item, option.value))
|
||||
}
|
||||
|
||||
function isOptionChecked(option: CheckboxGroupOption) {
|
||||
function isOptionChecked(option: TOption) {
|
||||
return findOptionIndex(option) > -1
|
||||
}
|
||||
|
||||
function updateOptionChecked(option: CheckboxGroupOption, checked: boolean) {
|
||||
function updateOptionChecked(option: TOption, checked: boolean) {
|
||||
const nextValue = [...value.value]
|
||||
const optionIndex = nextValue.findIndex(item => Object.is(item, option.value))
|
||||
|
||||
@@ -109,7 +109,7 @@ function updateOptionChecked(option: CheckboxGroupOption, checked: boolean) {
|
||||
value.value = nextValue
|
||||
}
|
||||
|
||||
function handleOptionModelValueChange(option: CheckboxGroupOption, checked: boolean | 'indeterminate') {
|
||||
function handleOptionModelValueChange(option: TOption, checked: boolean | 'indeterminate') {
|
||||
if (isOptionDisabled(option)) {
|
||||
return
|
||||
}
|
||||
@@ -117,7 +117,7 @@ function handleOptionModelValueChange(option: CheckboxGroupOption, checked: bool
|
||||
updateOptionChecked(option, checked === true)
|
||||
}
|
||||
|
||||
function handleCustomOptionClick(option: CheckboxGroupOption) {
|
||||
function handleCustomOptionClick(option: TOption) {
|
||||
if (!slots.option || isOptionDisabled(option)) {
|
||||
return
|
||||
}
|
||||
@@ -125,7 +125,7 @@ function handleCustomOptionClick(option: CheckboxGroupOption) {
|
||||
updateOptionChecked(option, !isOptionChecked(option))
|
||||
}
|
||||
|
||||
function handleCustomOptionKeydown(option: CheckboxGroupOption) {
|
||||
function handleCustomOptionKeydown(option: TOption) {
|
||||
if (!slots.option || isOptionDisabled(option)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
### RadioGroupOption 接口
|
||||
|
||||
```ts
|
||||
interface RadioGroupOption {
|
||||
export interface RadioGroupOption {
|
||||
label: string
|
||||
value: AcceptableValue
|
||||
description?: string
|
||||
@@ -37,7 +37,7 @@ interface RadioGroupOption {
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| `option` | 自定义选项内容,slot props: `{ option, checked, disabled, id }` |
|
||||
| `option` | 自定义选项内容,slot props: `{ option, checked, disabled, id }`,`option` 会保留 `options` 中的扩展字段类型 |
|
||||
|
||||
## Events
|
||||
|
||||
@@ -147,16 +147,19 @@ const options = [
|
||||
{
|
||||
label: '专注模式',
|
||||
value: 'focus',
|
||||
badge: '录入',
|
||||
description: '突出主任务,弱化辅助信息。',
|
||||
},
|
||||
{
|
||||
label: '平衡模式',
|
||||
value: 'balanced',
|
||||
badge: '推荐',
|
||||
description: '信息密度与可读性保持平衡。',
|
||||
},
|
||||
{
|
||||
label: '高密度模式',
|
||||
value: 'dense',
|
||||
badge: '大屏',
|
||||
description: '适合大屏运营与监控看板。',
|
||||
},
|
||||
]
|
||||
@@ -176,8 +179,13 @@ const options = [
|
||||
]"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<div class="truncate font-medium">{{ option.label }}</div>
|
||||
<div v-if="option.description" class="text-xs text-muted-foreground leading-5">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="truncate font-medium">{{ option.label }}</span>
|
||||
<span class="rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
|
||||
{{ option.badge }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground leading-5">
|
||||
{{ option.description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" generic="TOption extends RadioGroupOption">
|
||||
import type { AcceptableValue, RadioGroupRootProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '../../utils'
|
||||
import { Label } from '../label/label'
|
||||
import { RadioGroup, RadioGroupItem } from './radio-group'
|
||||
|
||||
interface RadioGroupOption {
|
||||
export interface RadioGroupOption {
|
||||
label: string
|
||||
value: AcceptableValue
|
||||
description?: string
|
||||
@@ -18,7 +18,7 @@ defineOptions({
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
options: RadioGroupOption[]
|
||||
options: TOption[]
|
||||
disabled?: RadioGroupRootProps['disabled']
|
||||
dir?: RadioGroupRootProps['dir']
|
||||
class?: HTMLAttributes['class']
|
||||
@@ -32,7 +32,7 @@ const emit = defineEmits<{
|
||||
|
||||
const slots = defineSlots<{
|
||||
option?: (props: {
|
||||
option: RadioGroupOption
|
||||
option: TOption
|
||||
checked: boolean
|
||||
disabled: boolean
|
||||
id: string
|
||||
@@ -46,11 +46,11 @@ watch(value, (newValue) => {
|
||||
emit('change', newValue)
|
||||
})
|
||||
|
||||
function getOptionId(option: RadioGroupOption, index: number) {
|
||||
function getOptionId(option: TOption, index: number) {
|
||||
return option.id || `${baseId}-${index}`
|
||||
}
|
||||
|
||||
function getOptionKey(option: RadioGroupOption, index: number) {
|
||||
function getOptionKey(option: TOption, index: number) {
|
||||
if (option.id) {
|
||||
return option.id
|
||||
}
|
||||
@@ -60,11 +60,11 @@ function getOptionKey(option: RadioGroupOption, index: number) {
|
||||
: index
|
||||
}
|
||||
|
||||
function isOptionDisabled(option: RadioGroupOption) {
|
||||
function isOptionDisabled(option: TOption) {
|
||||
return Boolean(props.disabled || option.disabled)
|
||||
}
|
||||
|
||||
function isOptionChecked(option: RadioGroupOption) {
|
||||
function isOptionChecked(option: TOption) {
|
||||
return Object.is(value.value, option.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user