u
This commit is contained in:
1 parent
5834738700
commit
df704b3056
8 files changed
+344
-121
No files matched your search
@@ -11,7 +11,7 @@ export { default as RadioGroup } from './radio/radio-group'
|
||||
export { default as Separator } from './separator'
|
||||
export { default as Space } from './space'
|
||||
export { default as Spin } from './spin'
|
||||
export { default as Tag } from './tag'
|
||||
export { default as TagGroup } from './tag-group'
|
||||
export { default as Tag } from './tag/tag'
|
||||
export { default as TagGroup } from './tag/tag-group'
|
||||
export { default as Textarea } from './textarea'
|
||||
export { default as Tooltip } from './tooltip'
|
||||
@@ -34,7 +34,7 @@ export default defineComponent({
|
||||
type: {
|
||||
type: String as PropType<'default' | 'filled' | 'primaryFilled' | 'card' | 'pureCard' | 'outline'>,
|
||||
default: 'default',
|
||||
}
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { slots, emit }) {
|
||||
@@ -49,7 +49,7 @@ export default defineComponent({
|
||||
const isChecked = computed(() => {
|
||||
if (inGroup.value)
|
||||
return props.value === radioGroupCtx!.value
|
||||
return _checked.value
|
||||
return props.modelValue ?? _checked.value
|
||||
})
|
||||
|
||||
const handleClick = (e: Event) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ 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/index'
|
||||
import Tag from '../tag/tag'
|
||||
|
||||
interface TagOption {
|
||||
label?: string
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { InjectionKey, Slots } from 'vue'
|
||||
|
||||
export interface TagGroupContext {
|
||||
name: 'AoTagGroup'
|
||||
value: Array<string | number | boolean>
|
||||
disabled: boolean
|
||||
checkable: boolean
|
||||
slots: Slots
|
||||
multiple: boolean
|
||||
handleChange: (value: string | number | boolean, e: Event) => void
|
||||
}
|
||||
|
||||
export const tagGroupKey: InjectionKey<TagGroupContext> = Symbol('TagGroup')
|
||||
@@ -1,110 +0,0 @@
|
||||
import type { PropType } from 'vue'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||
import Icon from '../icon/index'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Tag',
|
||||
props: {
|
||||
shape: {
|
||||
type: String as PropType<'square' | 'round' | 'mark'>,
|
||||
default: 'square',
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<'small' | 'medium' | 'large'>,
|
||||
default: 'medium',
|
||||
},
|
||||
type: {
|
||||
type: String as () => 'solid' | 'light' | 'outline' | 'outline-light',
|
||||
default: 'solid',
|
||||
},
|
||||
status: {
|
||||
type: String as () => 'primary' | 'danger' | 'warning' | 'success' | 'default',
|
||||
default: 'default',
|
||||
},
|
||||
checkable: { type: Boolean, default: false },
|
||||
checked: { type: Boolean, default: false },
|
||||
value: { type: [String, Number], required: false },
|
||||
checkedProps: {
|
||||
type: Object as PropType<{ type?: 'solid' | 'light' | 'outline' | 'outline-light', status?: 'primary' | 'danger' | 'warning' | 'success' | 'default' }>,
|
||||
default: () => ({
|
||||
type: 'solid',
|
||||
status: 'primary',
|
||||
}),
|
||||
},
|
||||
uncheckedProps: {
|
||||
type: Object as PropType<{ type?: 'solid' | 'light' | 'outline' | 'outline-light', status?: 'primary' | 'danger' | 'warning' | 'success' | 'default' }>,
|
||||
default: () => ({
|
||||
type: 'solid',
|
||||
status: 'default',
|
||||
}),
|
||||
},
|
||||
showClose: { type: Boolean, default: false },
|
||||
icon: { type: String, default: '' },
|
||||
disabled: { type: Boolean, default: false },
|
||||
class: { type: [String, Array, Object], default: '' },
|
||||
style: { type: Object, default: () => ({}) },
|
||||
},
|
||||
emits: ['click', 'close', 'update:checked'],
|
||||
setup(props, { slots, emit }) {
|
||||
const prefixCls = getPrefixCls('tag')
|
||||
const visible = ref(true)
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (props.disabled)
|
||||
return
|
||||
|
||||
// 如果 checkable,触发 update:checked
|
||||
if (props.checkable) {
|
||||
emit('update:checked', !props.checked)
|
||||
}
|
||||
|
||||
emit('click', { value: props.value, checked: props.checked, event })
|
||||
}
|
||||
|
||||
const handleClose = (event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
visible.value = false
|
||||
emit('close', { value: props.value, event })
|
||||
}
|
||||
|
||||
return () =>
|
||||
visible.value && (() => {
|
||||
const renderType = props.checkable
|
||||
? props.checked
|
||||
? props.checkedProps.type
|
||||
: props.uncheckedProps.type
|
||||
: props.type
|
||||
|
||||
const renderStatus = props.checkable
|
||||
? props.checked
|
||||
? props.checkedProps.status
|
||||
: props.uncheckedProps.status
|
||||
: props.status
|
||||
|
||||
return (
|
||||
<div
|
||||
class={[
|
||||
prefixCls,
|
||||
`${prefixCls}-size-${props.size}`,
|
||||
`${prefixCls}-shape-${props.shape}`,
|
||||
`${prefixCls}-type-${renderType}`,
|
||||
`${prefixCls}-status-${renderStatus}`,
|
||||
{ [`${prefixCls}-check`]: props.checkable },
|
||||
{ [`${prefixCls}-checked`]: props.checked },
|
||||
{ [`${prefixCls}-disabled`]: props.disabled },
|
||||
props.class,
|
||||
]}
|
||||
style={{ ...props.style }}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{props.icon && <Icon icon={props.icon} class={`${prefixCls}-icon`} />}
|
||||
<span>{slots.default?.()}</span>
|
||||
{!props.disabled && props.showClose && (
|
||||
<Icon icon="ix:close" class={`${prefixCls}-close`} onClick={handleClose} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})()
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { PropType, RenderFunction } from 'vue'
|
||||
import { computed, defineComponent, provide, reactive, ref, watch } from 'vue'
|
||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||
import Tag from '../tag/tag'
|
||||
import { tagGroupKey } from './context'
|
||||
|
||||
interface TagOption {
|
||||
label?: string | RenderFunction
|
||||
value?: string | boolean | number
|
||||
disabled?: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ATagGroup',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array as PropType<Array<string | number | boolean>>,
|
||||
default: () => [],
|
||||
},
|
||||
defaultValue: {
|
||||
type: Array as PropType<Array<string | number | boolean>>,
|
||||
default: () => [],
|
||||
},
|
||||
multiple: { type: Boolean, default: true }, // 单选也用数组,只限制长度为1即可
|
||||
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' }) },
|
||||
disabled: { type: Boolean, default: false },
|
||||
class: { type: [String, Array, Object], default: '' },
|
||||
style: { type: Object, default: () => ({}) },
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { slots, emit }) {
|
||||
// 统一为数组
|
||||
const _value = ref<Array<string | number | boolean>>(
|
||||
Array.isArray(props.modelValue) ? props.modelValue : props.defaultValue ?? [],
|
||||
)
|
||||
|
||||
const prefixCls = getPrefixCls('tag-group')
|
||||
const groupDisabled = computed(() => props.disabled)
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (Array.isArray(newVal)) {
|
||||
_value.value = newVal
|
||||
}
|
||||
})
|
||||
|
||||
const handleChange = (val: string | number | boolean, e: Event) => {
|
||||
const arr = [..._value.value]
|
||||
|
||||
const idx = arr.indexOf(val)
|
||||
if (idx >= 0) {
|
||||
arr.splice(idx, 1)
|
||||
}
|
||||
else {
|
||||
if (!props.multiple)
|
||||
arr.length = 0 // 单选清空已有值
|
||||
arr.push(val)
|
||||
}
|
||||
|
||||
_value.value = arr
|
||||
emit('update:modelValue', _value.value)
|
||||
emit('change', _value.value, e)
|
||||
}
|
||||
|
||||
provide(tagGroupKey, reactive({
|
||||
name: 'AoTagGroup' as const,
|
||||
value: _value,
|
||||
disabled: groupDisabled,
|
||||
slots,
|
||||
checkable: true,
|
||||
multiple: props.multiple,
|
||||
handleChange,
|
||||
}))
|
||||
|
||||
const renderOptions = () => {
|
||||
return props.options.map((option) => {
|
||||
const opt = 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 isChecked = _value.value.includes(opt[props.valueField])
|
||||
|
||||
return (
|
||||
<Tag
|
||||
key={opt[props.valueField]}
|
||||
value={opt[props.valueField]}
|
||||
disabled={opt.disabled ?? props.disabled}
|
||||
checkable
|
||||
checked={isChecked}
|
||||
checkedProps={props.checkedProps}
|
||||
uncheckedProps={props.uncheckedProps}
|
||||
>
|
||||
{labelContent}
|
||||
</Tag>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div class={[prefixCls, props.class]} style={props.style}>
|
||||
{props.options.length > 0 ? renderOptions() : slots.default?.()}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,117 @@
|
||||
import type { PropType } from 'vue'
|
||||
import type { TagGroupContext } from './context'
|
||||
import { computed, defineComponent, inject, ref } from 'vue'
|
||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||
import Icon from '../icon/index'
|
||||
import { tagGroupKey } from './context'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Tag',
|
||||
props: {
|
||||
shape: { type: String as PropType<'square' | 'round' | 'mark'>, default: 'square' },
|
||||
size: { type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium' },
|
||||
type: { type: String as () => 'solid' | 'light' | 'outline' | 'outline-light', default: 'solid' },
|
||||
status: { type: String as () => 'primary' | 'danger' | 'warning' | 'success' | 'default', default: 'default' },
|
||||
checkable: { type: Boolean, default: false },
|
||||
value: { type: [String, Number, Boolean] as PropType<string | number | boolean>, default: true },
|
||||
checked: { type: Boolean, default: undefined },
|
||||
modelValue: { type: Boolean, default: undefined },
|
||||
defaultChecked: { type: Boolean, default: false },
|
||||
checkedProps: { type: Object as PropType<any>, default: () => ({ type: 'solid', status: 'primary' }) },
|
||||
uncheckedProps: { type: Object as PropType<any>, default: () => ({ type: 'solid', status: 'default' }) },
|
||||
showClose: { type: Boolean, default: false },
|
||||
icon: { type: String, default: '' },
|
||||
disabled: { type: Boolean, default: false },
|
||||
class: { type: [String, Array, Object], default: '' },
|
||||
style: { type: Object, default: () => ({}) },
|
||||
},
|
||||
emits: ['click', 'close', 'update:checked', 'update:modelValue'],
|
||||
setup(props, { slots, emit }) {
|
||||
const prefixCls = getPrefixCls('tag')
|
||||
const tagGroupCtx = inject<TagGroupContext | undefined>(tagGroupKey, undefined)
|
||||
const inGroup = computed(() => !!tagGroupCtx)
|
||||
const _checked = ref(props.defaultChecked)
|
||||
const visible = ref(true)
|
||||
|
||||
const isMultiple = computed(() => tagGroupCtx?.multiple ?? false)
|
||||
const isDisabled = computed(() => props.disabled || tagGroupCtx?.disabled)
|
||||
const isChecked = computed(() => {
|
||||
if (inGroup.value) {
|
||||
const groupValue = tagGroupCtx!.value
|
||||
return groupValue.includes(props.value)
|
||||
}
|
||||
return props.checked ?? props.modelValue ?? _checked.value
|
||||
})
|
||||
|
||||
const isCheckable = computed(() => props.checkable || tagGroupCtx?.checkable)
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (isDisabled.value)
|
||||
return
|
||||
|
||||
if (inGroup.value) {
|
||||
// 多选:切换
|
||||
if (tagGroupCtx!.multiple) {
|
||||
tagGroupCtx!.handleChange(props.value, event)
|
||||
}
|
||||
else {
|
||||
// 单选:清空已有值或选中新值
|
||||
const newValue = isChecked.value ? '' : props.value
|
||||
tagGroupCtx!.handleChange(newValue, event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 独立 Tag 自己控制
|
||||
const newChecked = !isChecked.value
|
||||
_checked.value = newChecked
|
||||
emit('update:modelValue', newChecked)
|
||||
emit('update:checked', newChecked)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = (event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
visible.value = false
|
||||
emit('close', { value: props.value, event })
|
||||
}
|
||||
|
||||
return () =>
|
||||
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
|
||||
class={[
|
||||
prefixCls,
|
||||
`${prefixCls}-size-${props.size}`,
|
||||
`${prefixCls}-shape-${props.shape}`,
|
||||
`${prefixCls}-type-${renderType}`,
|
||||
`${prefixCls}-status-${renderStatus}`,
|
||||
{ [`${prefixCls}-check`]: isCheckable.value },
|
||||
{ [`${prefixCls}-checked`]: isChecked.value },
|
||||
{ [`${prefixCls}-disabled`]: isDisabled.value },
|
||||
props.class,
|
||||
]}
|
||||
style={{ ...props.style }}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{props.icon && <Icon icon={props.icon} class={`${prefixCls}-icon`} />}
|
||||
<span>{slots.default?.()}</span>
|
||||
{!props.disabled && props.showClose && (
|
||||
<Icon icon="ix:close" class={`${prefixCls}-close`} onClick={handleClose} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})()
|
||||
},
|
||||
})
|
||||
@@ -1,13 +1,101 @@
|
||||
<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>
|
||||
content
|
||||
<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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
Reference in new issue
Block a user