Files
workspace/code/base-project/base-vue/packages/ao-design/src/radio/radio-group.tsx
T
2025-09-16 15:14:56 +08:00

163 lines
4.4 KiB
TypeScript

import type { PropType, RenderFunction } from 'vue'
import { computed, defineComponent, provide, reactive, ref, watch } from 'vue'
import { getPrefixCls } from '../_utils/getPrefixCls'
import Space from '../space/index'
import { radioGroupKey } from './context'
import Radio from './radio'
interface RadioOption {
label?: string | RenderFunction
value?: string | number
extra?: string | RenderFunction
disabled?: boolean
[key: string]: any
}
export default defineComponent({
name: 'RadioGroup',
props: {
modelValue: {
type: [String, Number, Boolean] as PropType<string | number | boolean>,
default: undefined,
},
defaultValue: {
type: [String, Number, Boolean] as PropType<string | number | boolean>,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
labelField: {
type: String,
default: 'label',
},
valueField: {
type: String,
default: 'value',
},
extraField: {
type: String,
default: 'extra',
},
options: {
type: Array as PropType<Array<string | number | RadioOption>>,
default: [],
},
spaceProps: {
type: Object as PropType<Record<string, any>>,
default: () => ({}),
},
type: {
type: String as PropType<'default' | 'filled' | 'primaryFilled' | 'card' | 'pureCard' | 'outline'>,
default: 'default',
},
size: {
type: String as PropType<'small' | 'medium' | 'large'>,
default: 'medium',
},
},
emits: ['update:modelValue', 'change'],
setup(props, { emit, slots }) {
const prefixCls = getPrefixCls('radio-group')
// 内部值,优先使用 v-model
const _value = ref(props.modelValue ?? props.defaultValue)
watch(() => props.modelValue, (newVal) => {
if (newVal !== undefined) {
_value.value = newVal
}
})
const handleChange = (val: string | number | boolean, e: Event) => {
_value.value = val
emit('update:modelValue', val)
emit('change', val, e)
}
const groupDisabled = computed(() => props.disabled)
const groupType = computed(() => props.type)
// 提供 context 给 Radio 使用
provide(radioGroupKey, reactive({
name: 'AoRadioGroup' as const,
value: _value,
disabled: groupDisabled,
type: groupType,
slots,
handleChange,
}))
const renderOptions = () => {
return props.options.map((option) => {
// 标准化 option
const opt: RadioOption
= typeof option === 'string' || typeof option === 'number'
? { [props.labelField]: String(option), [props.valueField]: option }
: option
const labelContent = slots.label
? slots.label({ data: opt })
: typeof opt[props.labelField] === 'function'
? opt[props.labelField]()
: opt[props.labelField]
const extraContent = slots.extra
? slots.extra({ data: opt })
: typeof opt[props.extraField] === 'function'
? opt[props.extraField]()
: opt[props.extraField]
if (slots.radio) {
return (
<div
key={opt[props.valueField]}
onClick={e => handleChange(opt[props.valueField], e)} // 内部统一处理点击
>
{slots.radio({ data: opt, checked: _value.value === opt[props.valueField] })}
</div>
)
}
return (
<Radio
key={opt[props.valueField]}
value={opt[props.valueField]}
disabled={opt.disabled ?? props.disabled}
modelValue={_value.value === opt[props.valueField]}
extra={extraContent}
type={props.type}
>
{labelContent}
</Radio>
)
})
}
const spaceSize
= ['default', 'card', 'pureCard'].includes(groupType.value)
? [6, 6]
: props.spaceProps.size ?? 0
const sizeClass = computed(() => {
if (['filled', 'primaryFilled', 'outline'].includes(groupType.value)) {
return `${prefixCls}-${props.size}`
}
return null
})
return () => (
<Space
align="start"
size={spaceSize}
{...props.spaceProps}
class={[
`${prefixCls}-${groupType.value}`,
sizeClass.value,
]}
>
{props.options.length > 0 ? renderOptions() : slots.default?.()}
</Space>
)
},
})