u
This commit is contained in:
1 parent
785bce1907
commit
985b42cafb
9 files changed
+298
-100
No files matched your search
@@ -1,7 +1,4 @@
|
||||
@use './_light' as *;
|
||||
@use './_dark' as *;
|
||||
@use './_radius' as *;
|
||||
@use './_font' as *;
|
||||
@use './_size' as *;
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import type { ConfigProviderContext } from '../_config/context'
|
||||
import { inject } from 'vue'
|
||||
import { getCurrentInstance, inject } from 'vue'
|
||||
import { configProviderInjectionKey } from '../_config/context'
|
||||
|
||||
const DEFAULT_PREFIX = 'ao'
|
||||
|
||||
export function getPrefixCls(componentName?: string): string {
|
||||
const configProvider: ConfigProviderContext | undefined
|
||||
= inject(configProviderInjectionKey, undefined)
|
||||
let prefix = DEFAULT_PREFIX
|
||||
|
||||
const prefix = configProvider?.prefixCls ?? DEFAULT_PREFIX
|
||||
// 仅在 setup 或函数式组件中可以调用 inject
|
||||
const instance = getCurrentInstance()
|
||||
if (instance) {
|
||||
const configProvider: ConfigProviderContext | undefined = inject(configProviderInjectionKey, undefined)
|
||||
prefix = configProvider?.prefixCls ?? DEFAULT_PREFIX
|
||||
}
|
||||
|
||||
return componentName ? `${prefix}-${componentName}` : prefix
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PropType, VNode } from 'vue'
|
||||
import { cloneVNode, defineComponent, ref } from 'vue'
|
||||
import { cloneVNode, defineComponent, ref, watch } from 'vue'
|
||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||
import Tag from '../tag/index'
|
||||
|
||||
@@ -13,60 +13,100 @@ export default defineComponent({
|
||||
default: () => [],
|
||||
},
|
||||
checkedProps: {
|
||||
type: Object as PropType<{
|
||||
type?: string
|
||||
status?: string
|
||||
}>,
|
||||
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?: string
|
||||
status?: string
|
||||
}>,
|
||||
type: Object as PropType<{ type?: 'solid' | 'light' | 'outline' | 'outline-light', status?: 'primary' | 'danger' | 'warning' | 'success' | 'default' }>,
|
||||
default: () => ({
|
||||
type: 'solid',
|
||||
status: 'default',
|
||||
}),
|
||||
},
|
||||
class: {
|
||||
type: [String, Array, Object],
|
||||
default: '',
|
||||
},
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
class: { type: [String, Array, Object], default: '' },
|
||||
style: { type: Object, default: () => ({}) },
|
||||
},
|
||||
emits: [],
|
||||
setup(props, { slots }) {
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { slots, emit }) {
|
||||
const prefixCls = getPrefixCls('tag-group')
|
||||
const selectedValues = ref([...props.modelValue])
|
||||
const selectedValues = ref<(string | number)[]>([...props.modelValue])
|
||||
|
||||
// 外部 v-model 更新时同步内部值
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
selectedValues.value = [...val]
|
||||
},
|
||||
)
|
||||
|
||||
const updateValue = (value: string | number, checked: boolean) => {
|
||||
let newValues = [...selectedValues.value]
|
||||
|
||||
if (props.multiple) {
|
||||
if (checked) {
|
||||
if (!newValues.includes(value))
|
||||
newValues.push(value)
|
||||
}
|
||||
else {
|
||||
newValues = newValues.filter(v => v !== value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 单选:checked 为 true → 只保留当前值
|
||||
newValues = checked ? [value] : []
|
||||
}
|
||||
|
||||
selectedValues.value = newValues
|
||||
emit('update:modelValue', newValues)
|
||||
emit('change', newValues)
|
||||
}
|
||||
|
||||
const renderTag = (value: string | number, label?: string, vnode?: VNode) => {
|
||||
const checked = selectedValues.value.includes(value)
|
||||
const baseProps = {
|
||||
'key': value,
|
||||
value,
|
||||
checked,
|
||||
'checkable': true,
|
||||
'checkedProps': props.checkedProps,
|
||||
'uncheckedProps': props.uncheckedProps,
|
||||
'onUpdate:checked': (val: boolean) => updateValue(value, val),
|
||||
}
|
||||
|
||||
if (vnode) {
|
||||
return cloneVNode(vnode, baseProps)
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<Tag {...baseProps}>
|
||||
{label}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// slot 渲染
|
||||
const renderSlotTags = () => {
|
||||
const slotNodes = slots.default?.() || []
|
||||
return slotNodes.map((vnode: VNode) => {
|
||||
// 判断是否是 Tag 组件
|
||||
if (vnode.type === Tag) {
|
||||
if ((vnode.type as any)?.name === 'Tag') {
|
||||
const value = (vnode.props as any)?.value
|
||||
const checked = selectedValues.value.includes(value)
|
||||
return cloneVNode(vnode, {
|
||||
checked,
|
||||
'type': checked ? props.checkedProps.type : props.uncheckedProps.type,
|
||||
'status': checked ? props.checkedProps.status : props.uncheckedProps.status,
|
||||
'onUpdate:checked': (val: boolean) => handleTagClick(value, val),
|
||||
})
|
||||
return renderTag(value, undefined, vnode)
|
||||
}
|
||||
return vnode
|
||||
})
|
||||
}
|
||||
|
||||
// options 渲染
|
||||
const renderOptionTags = () =>
|
||||
props.options.map(opt => renderTag(opt.value, opt.label))
|
||||
|
||||
return () => (
|
||||
<div class={[prefixCls, props.class]} style={{ ...props.style }}>
|
||||
{slots.default?.()}
|
||||
{props.options.length ? renderOptionTags() : renderSlotTags()}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -53,6 +53,37 @@ $tag-prefix: '#{$prefix}-tag';
|
||||
}
|
||||
}
|
||||
|
||||
&-check {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background-color 0.2s cubic-bezier(0.38, 0, 0.24, 1);
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&-check.#{$tag-prefix}-disabled.#{$tag-prefix}-type-solid.#{$tag-prefix}-status-default {
|
||||
background-color: $default-color-disabled;
|
||||
color: $text-color-disabled;
|
||||
}
|
||||
|
||||
&-checked.#{$tag-prefix}-disabled.#{$tag-prefix}-type-solid.#{$tag-prefix}-status-primary {
|
||||
background-color: $primary-color-disabled;
|
||||
}
|
||||
|
||||
&-checked.#{$tag-prefix}-disabled.#{$tag-prefix}-type-solid.#{$tag-prefix}-status-danger {
|
||||
background-color: $danger-color-disabled;
|
||||
}
|
||||
|
||||
&-checked.#{$tag-prefix}-disabled.#{$tag-prefix}-type-solid.#{$tag-prefix}-status-warning {
|
||||
background-color: $warning-color-disabled;
|
||||
}
|
||||
|
||||
&-checked.#{$tag-prefix}-disabled.#{$tag-prefix}-type-solid.#{$tag-prefix}-status-success {
|
||||
background-color: $success-color-disabled;
|
||||
}
|
||||
|
||||
&-type-solid {
|
||||
&.#{$tag-prefix}-status-default {
|
||||
color: $text-color-base;
|
||||
|
||||
@@ -6,6 +6,7 @@ import Icon from '../icon/index'
|
||||
export default defineComponent({
|
||||
name: 'Tag',
|
||||
props: {
|
||||
|
||||
shape: {
|
||||
type: String as PropType<'square' | 'round' | 'mark'>,
|
||||
default: 'square',
|
||||
@@ -22,62 +23,89 @@ export default defineComponent({
|
||||
type: String as () => 'primary' | 'danger' | 'warning' | 'success' | 'default',
|
||||
default: 'default',
|
||||
},
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
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',
|
||||
}),
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
class: {
|
||||
type: [String, Array, Object],
|
||||
default: '',
|
||||
},
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
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'],
|
||||
emits: ['click', 'close', 'update:checked'],
|
||||
setup(props, { slots, emit }) {
|
||||
const prefixCls = getPrefixCls('tag')
|
||||
const visible = ref(true)
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
emit('click', event)
|
||||
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 // 点击关闭隐藏 Tag
|
||||
emit('close', event)
|
||||
visible.value = false
|
||||
emit('close', { value: props.value, event })
|
||||
}
|
||||
|
||||
return () =>
|
||||
visible.value && (
|
||||
<div
|
||||
class={[
|
||||
prefixCls,
|
||||
`${prefixCls}-size-${props.size}`,
|
||||
`${prefixCls}-shape-${props.shape}`,
|
||||
`${prefixCls}-type-${props.type}`,
|
||||
`${prefixCls}-status-${props.status}`,
|
||||
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>
|
||||
)
|
||||
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>
|
||||
)
|
||||
})()
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user