This commit is contained in:
oneao committed 2025-09-16 22:01:18 +08:00
1 parent 1b73170f78
commit 104e0a78c6
14 files changed
+379 -352

No files matched your search

@@ -13,7 +13,6 @@
@use './drawer/index.scss' as *; @use './drawer/index.scss' as *;
@use './tooltip/index.scss' as *; @use './tooltip/index.scss' as *;
@use './tag/index.scss' as *; @use './tag/index.scss' as *;
@use './tag-group/index.scss' as *;
@use './space/index.scss' as *; @use './space/index.scss' as *;
@use './separator/index.scss' as *; @use './separator/index.scss' as *;
@use './radio/index.scss' as *; @use './radio/index.scss' as *;
@@ -2,11 +2,12 @@ import type { InjectionKey, Slots } from 'vue'
export interface RadioGroupContext { export interface RadioGroupContext {
name: 'AoRadioGroup' name: 'AoRadioGroup'
value: string | number | boolean value: string | number | boolean | undefined
disabled: boolean disabled: boolean
slots: Slots slots: Slots
allowUncheck: boolean
type?: 'default' | 'filled' | 'primaryFilled' | 'card' | 'pureCard' | 'outline' type?: 'default' | 'filled' | 'primaryFilled' | 'card' | 'pureCard' | 'outline'
handleChange: (value: string | number | boolean, e: Event) => void handleChange: (value: string | number | boolean | undefined, e: Event) => void
} }
export const radioGroupKey: InjectionKey<RadioGroupContext> export const radioGroupKey: InjectionKey<RadioGroupContext>
@@ -54,6 +54,7 @@ function toggleThemeDark() {
extra-field="note" extra-field="note"
type="filled" type="filled"
size="small" size="small"
:allow-uncheck="false"
@change="handleChange" @change="handleChange"
/> />
</div> </div>
@@ -22,7 +22,7 @@ export default defineComponent({
}, },
defaultValue: { defaultValue: {
type: [String, Number, Boolean] as PropType<string | number | boolean>, type: [String, Number, Boolean] as PropType<string | number | boolean>,
default: '', default: undefined,
}, },
disabled: { disabled: {
type: Boolean, type: Boolean,
@@ -56,21 +56,23 @@ export default defineComponent({
type: String as PropType<'small' | 'medium' | 'large'>, type: String as PropType<'small' | 'medium' | 'large'>,
default: 'medium', default: 'medium',
}, },
allowUncheck: {
type: Boolean,
default: true,
},
}, },
emits: ['update:modelValue', 'change'], emits: ['update:modelValue', 'change'],
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
const prefixCls = getPrefixCls('radio-group') const prefixCls = getPrefixCls('radio-group')
// 内部值,优先使用 v-model // 内部值,优先使用 v-model
const _value = ref(props.modelValue ?? props.defaultValue) const _value = ref(props.modelValue ?? props.defaultValue ?? undefined)
watch(() => props.modelValue, (newVal) => { watch(() => props.modelValue, (newVal) => {
if (newVal !== undefined) { _value.value = newVal ?? undefined
_value.value = newVal
}
}) })
const handleChange = (val: string | number | boolean, e: Event) => { const handleChange = (val: string | number | boolean | undefined, e: Event) => {
_value.value = val _value.value = val
emit('update:modelValue', val) emit('update:modelValue', val)
emit('change', val, e) emit('change', val, e)
@@ -84,6 +86,7 @@ export default defineComponent({
disabled: groupDisabled, disabled: groupDisabled,
type: groupType, type: groupType,
slots, slots,
allowUncheck: props.allowUncheck,
handleChange, handleChange,
})) }))
@@ -126,6 +129,7 @@ export default defineComponent({
modelValue={_value.value === opt[props.valueField]} modelValue={_value.value === opt[props.valueField]}
extra={extraContent} extra={extraContent}
type={props.type} type={props.type}
allowUncheck={props.allowUncheck}
> >
{labelContent} {labelContent}
</Radio> </Radio>
@@ -52,18 +52,20 @@ export default defineComponent({
return props.modelValue ?? _checked.value return props.modelValue ?? _checked.value
}) })
const isAllowUncheck = computed(() => props.allowUncheck || radioGroupCtx?.allowUncheck)
const handleClick = (e: Event) => { const handleClick = (e: Event) => {
if (isDisabled.value) if (isDisabled.value)
return return
if (isChecked.value && !props.allowUncheck) if (isChecked.value && !isAllowUncheck.value)
return return
if (inGroup.value) { if (inGroup.value) {
const newValue = isChecked.value && props.allowUncheck ? '' : props.value const newValue = isChecked.value && isAllowUncheck.value ? undefined : props.value
radioGroupCtx?.handleChange(newValue, e) radioGroupCtx?.handleChange(newValue, e)
} }
else { else {
const newValue = props.allowUncheck ? !isChecked.value : true const newValue = isAllowUncheck.value ? !isChecked.value : true
_checked.value = newValue _checked.value = newValue
emit('update:modelValue', newValue) emit('update:modelValue', newValue)
emit('change', props.value, e) emit('change', props.value, e)
@@ -1,101 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
interface TagOption {
label: string
value: string | number
}
const tagOptions: TagOption[] = [
{ label: '标签1', value: 1 },
{ label: '标签2', value: 2 },
{ label: '标签3', value: 3 },
]
const customOptions: TagOption[] = [
{ label: '成功', value: 'success' },
{ label: '警告', value: 'warning' },
{ label: '错误', value: 'error' },
]
// 多选示例
const multiSelected = ref<(string | number)[]>([1, 2])
function handleMultiChange(selectedOptions: TagOption[]) {
console.log('多选 change:', selectedOptions)
}
// 单选示例
const singleSelected = ref<(string | number)[]>([2])
function handleSingleChange(selectedOptions: TagOption[]) {
console.log('单选 change:', selectedOptions)
}
// slot 渲染示例
const slotSelected = ref<(string | number)[]>(['b'])
// 自定义样式示例
const customSelected = ref<(string | number)[]>(['success'])
function handleCustomChange(selectedOptions: TagOption[]) {
console.log('自定义样式 change:', selectedOptions)
}
</script>
<template>
<div style="padding: 20px;">
<h2>ATagGroup 使用示例</h2>
<!-- 1. 多选 options 渲染 -->
<section style="margin-bottom: 20px;">
<h3>多选 - Options 渲染</h3>
<ATagGroup
v-model="multiSelected"
:options="tagOptions"
multiple
@change="handleMultiChange"
/>
<p>选中值: {{ multiSelected }}</p>
</section>
<!-- 2. 单选 options 渲染 -->
<section style="margin-bottom: 20px;">
<h3>单选 - Options 渲染</h3>
<ATagGroup
v-model="singleSelected"
:options="tagOptions"
:multiple="false"
@change="handleSingleChange"
/>
<p>选中值: {{ singleSelected }}</p>
</section>
<!-- 3. Slot 渲染 -->
<section style="margin-bottom: 20px;">
<h3>Slot 渲染</h3>
<ATagGroup v-model="slotSelected">
<ATag value="a">
自定义A
</ATag>
<ATag value="b" checked>
自定义B
</ATag>
<ATag value="c" disabled>
自定义C
</ATag>
</ATagGroup>
<p>选中值: {{ slotSelected }}</p>
</section>
<!-- 4. 自定义 checkedProps / uncheckedProps -->
<section>
<h3>自定义样式</h3>
<ATagGroup
v-model="customSelected"
:options="customOptions"
:checked-props="{ type: 'solid', status: 'success' }"
:unchecked-props="{ type: 'solid', status: 'default' }"
@change="handleCustomChange"
/>
<p>选中值: {{ customSelected }}</p>
</section>
</div>
</template>
@@ -1,12 +0,0 @@
@use '../_styles/_variables.scss' as *;
@use 'sass:color';
$tag-prefix: '#{$prefix}-tag';
$tag-group-prefix: '#{$prefix}-tag-group';
.#{$tag-group-prefix} {
> .#{$tag-prefix}:not(:last-child) {
margin-right: 8px;
}
}
@@ -1,83 +0,0 @@
import type { PropType, VNode } from 'vue'
import { cloneVNode, defineComponent } from 'vue'
import { useSelectable } from '../_hooks/useSelectable'
import { getPrefixCls } from '../_utils/getPrefixCls'
import Tag from '../tag/tag'
interface TagOption {
label?: string
value: string | number
disabled?: boolean
[key: string]: any
}
export default defineComponent({
name: 'ATagGroup',
props: {
modelValue: { type: Array as PropType<(string | number)[]>, default: () => [] },
multiple: { type: Boolean, default: true },
options: { type: Array as PropType<TagOption[]>, default: () => [] },
labelField: { type: String, default: 'label' },
valueField: { type: String, default: 'value' },
checkedProps: { type: Object as PropType<Record<string, any>>, default: () => ({ type: 'solid', status: 'primary' }) },
uncheckedProps: { type: Object as PropType<Record<string, any>>, default: () => ({ type: 'solid', status: 'default' }) },
class: { type: [String, Array, Object], default: '' },
style: { type: Object, default: () => ({}) },
},
emits: ['update:modelValue', 'change'],
setup(props, { slots, emit }) {
const prefixCls = getPrefixCls('tag-group')
const { selectedValues, updateValue } = useSelectable(
{
modelValue: props.modelValue,
multiple: props.multiple,
options: props.options,
valueField: props.valueField,
},
(newVal, selectedOptions) => {
emit('update:modelValue', newVal)
emit('change', selectedOptions)
},
)
const renderTag = (option: TagOption, vnode?: VNode) => {
const val = option[props.valueField]
const checked = selectedValues.value.includes(val)
const baseProps = {
'key': val,
'value': val,
checked,
'checkable': true,
'checkedProps': props.checkedProps,
'uncheckedProps': props.uncheckedProps,
'disabled': option.disabled,
'onUpdate:checked': (valChecked: boolean) => updateValue(option, valChecked),
}
return vnode ? cloneVNode(vnode, baseProps) : <Tag {...baseProps}>{option[props.labelField]}</Tag>
}
const renderOptionsTags = () => props.options.map(opt => renderTag(opt))
const renderSlotTags = () => {
const slotNodes = slots.default?.() || []
return slotNodes.map((vnode: VNode) => {
// 判断 vnode.type 是否等于 Tag 组件本身
if (vnode.type === Tag) {
const vnodeProps = vnode.props || {}
const option = {
value: vnodeProps.value,
label: vnodeProps.label ?? vnodeProps.value,
disabled: vnodeProps.disabled,
}
return renderTag(option, vnode)
}
return vnode
})
}
return () => {
const children = props.options.length ? renderOptionsTags() : renderSlotTags()
return <div class={[prefixCls, props.class]} style={props.style}>{children}</div>
}
},
})
@@ -2,7 +2,7 @@ import type { InjectionKey, Slots } from 'vue'
export interface TagGroupContext { export interface TagGroupContext {
name: 'AoTagGroup' name: 'AoTagGroup'
value: Array<string | number | boolean> value: string | number | boolean | (string | number | boolean)[] | undefined
disabled: boolean disabled: boolean
checkable: boolean checkable: boolean
slots: Slots slots: Slots
@@ -204,6 +204,8 @@
</ATag> </ATag>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
@@ -220,3 +222,107 @@
gap: 12px; gap: 12px;
} }
</style> </style>
<!-- <script setup lang="ts">
import { ref } from 'vue'
interface TagOption {
label: string
value: string | number
}
const tagOptions: TagOption[] = [
{ label: '标签1', value: 1 },
{ label: '标签2', value: 2 },
{ label: '标签3', value: 3 },
]
const customOptions: TagOption[] = [
{ label: '成功', value: 'success' },
{ label: '警告', value: 'warning' },
{ label: '错误', value: 'error' },
]
// 多选示例
const multiSelected = ref<(string | number)[]>([1, 2])
function handleMultiChange(selectedOptions: TagOption[]) {
console.log('多选 change:', selectedOptions)
}
// 单选示例
const singleSelected = ref<(string | number)[]>([2])
function handleSingleChange(selectedOptions: TagOption[]) {
console.log('单选 change:', selectedOptions)
}
// slot 渲染示例
const slotSelected = ref<(string | number)[]>(['b'])
// 自定义样式示例
const customSelected = ref<(string | number)[]>(['success'])
function handleCustomChange(selectedOptions: TagOption[]) {
console.log('自定义样式 change:', selectedOptions)
}
</script>
<template>
<div style="padding: 20px;">
<h2>ATagGroup 使用示例</h2>
<!-- 1. 多选 options 渲染 -->
<section style="margin-bottom: 20px;">
<h3>多选 - Options 渲染</h3>
<ATagGroup
v-model="multiSelected"
:options="tagOptions"
multiple
@change="handleMultiChange"
/>
<p>选中值: {{ multiSelected }}</p>
</section>
<!-- 2. 单选 options 渲染 -->
<section style="margin-bottom: 20px;">
<h3>单选 - Options 渲染</h3>
<ATagGroup
v-model="singleSelected"
:options="tagOptions"
:multiple="false"
@change="handleSingleChange"
/>
<p>选中值: {{ singleSelected }}</p>
</section>
<!-- 3. Slot 渲染 -->
<section style="margin-bottom: 20px;">
<h3>Slot 渲染</h3>
<ATagGroup v-model="slotSelected">
<ATag value="a">
自定义A
</ATag>
<ATag value="b">
自定义B
</ATag>
<ATag value="c" disabled>
自定义C
</ATag>
</ATagGroup>
<p>选中值: {{ slotSelected }}</p>
</section>
<!-- 4. 自定义 checkedProps / uncheckedProps -->
<section>
<h3>自定义样式</h3>
<ATagGroup
v-model="customSelected"
:options="customOptions"
:checked-props="{ type: 'solid', status: 'success' }"
:unchecked-props="{ type: 'solid', status: 'default' }"
@change="handleCustomChange"
/>
<p>选中值: {{ customSelected }}</p>
</section>
</div>
</template> -->
@@ -2,6 +2,13 @@
@use 'sass:color'; @use 'sass:color';
$tag-prefix: '#{$prefix}-tag'; $tag-prefix: '#{$prefix}-tag';
$tag-group-prefix: '#{$prefix}-tag-group';
.#{$tag-group-prefix} {
> .#{$tag-prefix}:not(:last-child) {
margin-right: 8px;
}
}
.#{$tag-prefix} { .#{$tag-prefix} {
box-sizing: border-box; box-sizing: border-box;
@@ -15,58 +15,77 @@ export default defineComponent({
name: 'ATagGroup', name: 'ATagGroup',
props: { props: {
modelValue: { modelValue: {
type: Array as PropType<Array<string | number | boolean>>, type: [String, Number, Boolean, Array] as PropType<
default: () => [], string | number | boolean | (string | number | boolean)[]
>,
default: undefined,
}, },
defaultValue: { defaultValue: {
type: Array as PropType<Array<string | number | boolean>>, type: [String, Number, Boolean, Array] as PropType<
default: () => [], string | number | boolean | (string | number | boolean)[]
>,
default: undefined,
}, },
multiple: { type: Boolean, default: true }, // 单选也用数组,只限制长度为1即可 multiple: { type: Boolean, default: true },
options: { type: Array as PropType<TagOption[]>, default: () => [] }, options: { type: Array as PropType<TagOption[]>, default: () => [] },
labelField: { type: String, default: 'label' }, labelField: { type: String, default: 'label' },
valueField: { type: String, default: 'value' }, valueField: { type: String, default: 'value' },
checkedProps: { type: Object as PropType<Record<string, any>>, default: () => ({ type: 'solid', status: 'primary' }) }, checkedProps: {
uncheckedProps: { type: Object as PropType<Record<string, any>>, default: () => ({ type: 'solid', status: 'default' }) }, type: Object as PropType<Record<string, any>>,
default: () => ({ type: 'solid', status: 'primary' }),
},
uncheckedProps: {
type: Object as PropType<Record<string, any>>,
default: () => ({ type: 'solid', status: 'default' }),
},
disabled: { type: Boolean, default: false }, disabled: { type: Boolean, default: false },
class: { type: [String, Array, Object], default: '' }, class: { type: [String, Array, Object], default: '' },
style: { type: Object, default: () => ({}) }, style: { type: Object, default: () => ({}) },
}, },
emits: ['update:modelValue', 'change'], emits: ['update:modelValue', 'change'],
setup(props, { slots, emit }) { setup(props, { slots, emit }) {
// 统一为数组 const _value = ref(props.modelValue ?? props.defaultValue ?? (props.multiple ? [] : undefined))
const _value = ref<Array<string | number | boolean>>(
Array.isArray(props.modelValue) ? props.modelValue : props.defaultValue ?? [],
)
const prefixCls = getPrefixCls('tag-group') const prefixCls = getPrefixCls('tag-group')
const groupDisabled = computed(() => props.disabled) const groupDisabled = computed(() => props.disabled)
watch(() => props.modelValue, (newVal) => { watch(
if (Array.isArray(newVal)) { () => props.modelValue,
_value.value = newVal (newVal) => {
} _value.value = newVal ?? (props.multiple ? [] : undefined)
}) },
)
const handleChange = (val: string | number | boolean, e: Event) => { const handleChange = (val: string | number | boolean, e: Event) => {
const arr = [..._value.value] // 外部 v-model 类型决定内部 value 的类型
const isArrayMode = Array.isArray(_value.value)
if (isArrayMode) {
const arr = [...(_value.value as (string | number | boolean)[])]
const idx = arr.indexOf(val) const idx = arr.indexOf(val)
if (idx >= 0) { if (idx >= 0) {
arr.splice(idx, 1) arr.splice(idx, 1)
} }
else { else {
if (!props.multiple) if (!props.multiple)
arr.length = 0 // 单选清空已有值 arr.length = 0 // 单选时只保留最后一个
arr.push(val) arr.push(val)
} }
_value.value = arr _value.value = arr
emit('update:modelValue', _value.value) emit('update:modelValue', arr)
emit('change', _value.value, e) emit('change', arr, e)
}
else {
// 非数组模式:单选或多选时非数组 v-model
const newVal = _value.value === val ? undefined : val
_value.value = newVal
emit('update:modelValue', newVal)
emit('change', newVal, e)
}
} }
provide(tagGroupKey, reactive({ provide(
tagGroupKey,
reactive({
name: 'AoTagGroup' as const, name: 'AoTagGroup' as const,
value: _value, value: _value,
disabled: groupDisabled, disabled: groupDisabled,
@@ -74,11 +93,13 @@ export default defineComponent({
checkable: true, checkable: true,
multiple: props.multiple, multiple: props.multiple,
handleChange, handleChange,
})) }),
)
const renderOptions = () => { const renderOptions = () => {
return props.options.map((option) => { return props.options.map((option) => {
const opt = typeof option === 'string' || typeof option === 'number' const opt
= typeof option === 'string' || typeof option === 'number'
? { [props.labelField]: String(option), [props.valueField]: option } ? { [props.labelField]: String(option), [props.valueField]: option }
: option : option
@@ -88,7 +109,10 @@ export default defineComponent({
? opt[props.labelField]() ? opt[props.labelField]()
: opt[props.labelField] : opt[props.labelField]
const isChecked = _value.value.includes(opt[props.valueField]) const isChecked = props.multiple
? Array.isArray(_value.value)
&& _value.value.includes(opt[props.valueField])
: _value.value === opt[props.valueField]
return ( return (
<Tag <Tag
@@ -13,9 +13,8 @@ export default defineComponent({
type: { type: String as () => 'solid' | 'light' | 'outline' | 'outline-light', default: 'solid' }, type: { type: String as () => 'solid' | 'light' | 'outline' | 'outline-light', default: 'solid' },
status: { type: String as () => 'primary' | 'danger' | 'warning' | 'success' | 'default', default: 'default' }, status: { type: String as () => 'primary' | 'danger' | 'warning' | 'success' | 'default', default: 'default' },
checkable: { type: Boolean, default: false }, checkable: { type: Boolean, default: false },
value: { type: [String, Number, Boolean] as PropType<string | number | boolean>, default: true }, value: { type: [String, Number, Boolean] as PropType<string | number | boolean>, required: true },
checked: { type: Boolean, default: undefined }, checked: { type: Boolean, default: undefined },
modelValue: { type: Boolean, default: undefined },
defaultChecked: { type: Boolean, default: false }, defaultChecked: { type: Boolean, default: false },
checkedProps: { type: Object as PropType<any>, default: () => ({ type: 'solid', status: 'primary' }) }, checkedProps: { type: Object as PropType<any>, default: () => ({ type: 'solid', status: 'primary' }) },
uncheckedProps: { type: Object as PropType<any>, default: () => ({ type: 'solid', status: 'default' }) }, uncheckedProps: { type: Object as PropType<any>, default: () => ({ type: 'solid', status: 'default' }) },
@@ -25,7 +24,7 @@ export default defineComponent({
class: { type: [String, Array, Object], default: '' }, class: { type: [String, Array, Object], default: '' },
style: { type: Object, default: () => ({}) }, style: { type: Object, default: () => ({}) },
}, },
emits: ['click', 'close', 'update:checked', 'update:modelValue'], emits: ['click', 'close', 'update:checked'],
setup(props, { slots, emit }) { setup(props, { slots, emit }) {
const prefixCls = getPrefixCls('tag') const prefixCls = getPrefixCls('tag')
const tagGroupCtx = inject<TagGroupContext | undefined>(tagGroupKey, undefined) const tagGroupCtx = inject<TagGroupContext | undefined>(tagGroupKey, undefined)
@@ -34,38 +33,33 @@ export default defineComponent({
const visible = ref(true) const visible = ref(true)
const isDisabled = computed(() => props.disabled || tagGroupCtx?.disabled) const isDisabled = computed(() => props.disabled || tagGroupCtx?.disabled)
const isCheckable = computed(() => props.checkable || tagGroupCtx?.checkable)
const isChecked = computed(() => { const isChecked = computed(() => {
if (inGroup.value) { if (inGroup.value && tagGroupCtx) {
const groupValue = tagGroupCtx!.value const groupValue = tagGroupCtx.value
if (Array.isArray(groupValue)) {
return groupValue.includes(props.value) return groupValue.includes(props.value)
} }
return props.checked ?? props.modelValue ?? _checked.value return groupValue === props.value
}
return props.checked ?? _checked.value
}) })
const isCheckable = computed(() => props.checkable || tagGroupCtx?.checkable)
const handleClick = (event: MouseEvent) => { const handleClick = (event: MouseEvent) => {
if (isDisabled.value) if (isDisabled.value)
return return
if (inGroup.value) { if (inGroup.value && tagGroupCtx) {
// 多选:切换 tagGroupCtx.handleChange(props.value, event)
if (tagGroupCtx!.multiple) {
tagGroupCtx!.handleChange(props.value, event)
}
else {
// 单选:清空已有值或选中新值
const newValue = isChecked.value ? '' : props.value
tagGroupCtx!.handleChange(newValue, event)
}
} }
else { else {
// 独立 Tag 自己控制 // 独立 Tag 自己控制
const newChecked = !isChecked.value const newChecked = !isChecked.value
_checked.value = newChecked _checked.value = newChecked
emit('update:modelValue', newChecked)
emit('update:checked', newChecked) emit('update:checked', newChecked)
} }
emit('click', { value: props.value, event })
} }
const handleClose = (event: MouseEvent) => { const handleClose = (event: MouseEvent) => {
@@ -75,27 +69,14 @@ export default defineComponent({
} }
return () => return () =>
visible.value && (() => { visible.value && (
const renderType = isCheckable.value
? isChecked.value
? props.checkedProps.type
: props.uncheckedProps.type
: props.type
const renderStatus = isCheckable.value
? isChecked.value
? props.checkedProps.status
: props.uncheckedProps.status
: props.status
return (
<div <div
class={[ class={[
prefixCls, prefixCls,
`${prefixCls}-size-${props.size}`, `${prefixCls}-size-${props.size}`,
`${prefixCls}-shape-${props.shape}`, `${prefixCls}-shape-${props.shape}`,
`${prefixCls}-type-${renderType}`, `${prefixCls}-type-${isCheckable.value ? (isChecked.value ? props.checkedProps.type : props.uncheckedProps.type) : props.type}`,
`${prefixCls}-status-${renderStatus}`, `${prefixCls}-status-${isCheckable.value ? (isChecked.value ? props.checkedProps.status : props.uncheckedProps.status) : props.status}`,
{ [`${prefixCls}-check`]: isCheckable.value }, { [`${prefixCls}-check`]: isCheckable.value },
{ [`${prefixCls}-checked`]: isChecked.value }, { [`${prefixCls}-checked`]: isChecked.value },
{ [`${prefixCls}-disabled`]: isDisabled.value }, { [`${prefixCls}-disabled`]: isDisabled.value },
@@ -111,6 +92,5 @@ export default defineComponent({
)} )}
</div> </div>
) )
})()
}, },
}) })
+180 -81
View File
@@ -1,101 +1,200 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
interface TagOption { const selectedValue = ref()
label: string
value: string | number
}
const tagOptions: TagOption[] = [ // 自定义字段名
{ label: '标签1', value: 1 }, const options = [
{ label: '标签2', value: 2 }, { name: '选项一', id: 1, note: '这是额外信息 1' },
{ label: '标签3', value: 3 }, { name: '选项二', id: 2, note: '这是额外信息 2' },
{ name: '选项三', id: 3 },
] ]
const customOptions: TagOption[] = [ function handleChange(val: string | number | boolean) {
{ label: '成功', value: 'success' }, console.log('当前选中值:', val)
{ label: '警告', value: 'warning' },
{ label: '错误', value: 'error' },
]
// 多选示例
const multiSelected = ref<(string | number)[]>([1, 2])
function handleMultiChange(selectedOptions: TagOption[]) {
console.log('多选 change:', selectedOptions)
} }
// 单选示例 function toggleThemeLight() {
const singleSelected = ref<(string | number)[]>([2]) document.documentElement.setAttribute('theme-mode', 'light')
function handleSingleChange(selectedOptions: TagOption[]) {
console.log('单选 change:', selectedOptions)
} }
function toggleThemeDark() {
// slot 渲染示例 document.documentElement.setAttribute('theme-mode', 'dark')
const slotSelected = ref<(string | number)[]>(['b'])
// 自定义样式示例
const customSelected = ref<(string | number)[]>(['success'])
function handleCustomChange(selectedOptions: TagOption[]) {
console.log('自定义样式 change:', selectedOptions)
} }
</script> </script>
<template> <template>
<div style="padding: 20px;"> <AButton status="primary" @click="toggleThemeLight">
<h2>ATagGroup 使用示例</h2> 白天
</AButton>
<AButton status="primary" @click="toggleThemeDark">
黑天
</AButton>
<!-- 1. 多选 options 渲染 --> <div class="demo-wrapper">
<section style="margin-bottom: 20px;"> <div class="demo-container">
<h3>多选 - Options 渲染</h3> <h3>Default</h3>
<ATagGroup <ARadioGroup
v-model="multiSelected" v-model="selectedValue"
:options="tagOptions" :options="options"
multiple label-field="name"
@change="handleMultiChange" value-field="id"
extra-field="note"
type="default"
@change="handleChange"
/> />
<p>选中值: {{ multiSelected }}</p> </div>
</section>
<!-- 2. 单选 options 渲染 --> <div class="demo-container">
<section style="margin-bottom: 20px;"> <h3>filled</h3>
<h3>单选 - Options 渲染</h3> <ARadioGroup
<ATagGroup v-model="selectedValue"
v-model="singleSelected" :options="options"
:options="tagOptions" label-field="name"
:multiple="false" value-field="id"
@change="handleSingleChange" extra-field="note"
type="filled"
size="small"
:allow-uncheck="false"
@change="handleChange"
/> />
<p>选中值: {{ singleSelected }}</p> </div>
</section> <div class="demo-container">
<h3>filled</h3>
<!-- 3. Slot 渲染 --> <ARadioGroup
<section style="margin-bottom: 20px;"> v-model="selectedValue"
<h3>Slot 渲染</h3> :options="options"
<ATagGroup v-model="slotSelected"> label-field="name"
<ATag value="a"> value-field="id"
自定义A extra-field="note"
</ATag> type="filled"
<ATag value="b"> @change="handleChange"
自定义B
</ATag>
<ATag value="c" disabled>
自定义C
</ATag>
</ATagGroup>
<p>选中值: {{ slotSelected }}</p>
</section>
<!-- 4. 自定义 checkedProps / uncheckedProps -->
<section>
<h3>自定义样式</h3>
<ATagGroup
v-model="customSelected"
:options="customOptions"
:checked-props="{ type: 'solid', status: 'success' }"
:unchecked-props="{ type: 'solid', status: 'default' }"
@change="handleCustomChange"
/> />
<p>选中值: {{ customSelected }}</p> </div>
</section> <div class="demo-container">
<h3>filled</h3>
<ARadioGroup
v-model="selectedValue"
:options="options"
label-field="name"
value-field="id"
extra-field="note"
type="filled"
size="large"
@change="handleChange"
/>
</div>
<div class="demo-container">
<h3>primaryFilled</h3>
<ARadioGroup
v-model="selectedValue"
:options="options"
label-field="name"
value-field="id"
extra-field="note"
type="primaryFilled"
@change="handleChange"
/>
</div>
<div class="demo-container">
<h3>outline</h3>
<ARadioGroup
v-model="selectedValue"
:options="options"
label-field="name"
value-field="id"
extra-field="note"
type="outline"
@change="handleChange"
/>
</div>
<div class="demo-container">
<h3>card</h3>
<ARadioGroup
v-model="selectedValue"
:options="options"
label-field="name"
value-field="id"
extra-field="note"
type="card"
@change="handleChange"
/>
</div>
<div class="demo-container">
<h3>pureCard</h3>
<ARadioGroup
v-model="selectedValue"
:options="options"
label-field="name"
value-field="id"
extra-field="note"
type="pureCard"
@change="handleChange"
/>
</div>
<div class="demo-container">
<h3>自定义插槽radio</h3>
<ARadioGroup
v-model="selectedValue" :options="options"
value-field="id"
>
<template #radio="{ data, checked }">
<div
:style="{
padding: '4px 8px',
border: checked ? '2px solid #409eff' : '1px solid #dcdfe6',
borderRadius: '4px',
cursor: 'pointer',
marginRight: '4px',
}"
>
{{ checked ? '选中' : '未选中' }} - {{ data.name }}
</div> </div>
</template> </template>
</ARadioGroup>
</div>
</div>
</template>
<style scoped>
.demo-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 24px 16px;
min-height: 100vh;
transition: background-color 0.3s ease;
}
.demo-container {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 16px;
border-radius: 6px;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08);
width: 100%;
max-width: 400px;
text-align: center;
transition: background-color 0.3s ease;
}
h3 {
margin: 0;
font-weight: 500;
font-size: 14px;
}
/* 主题背景色 */
:root[theme-mode='light'] .demo-wrapper {
}
:root[theme-mode='dark'] .demo-wrapper {
background-color: #1e1e1e;
color: #fff;
}
</style>