Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
2548800c
Commit
2548800c
authored
Jan 11, 2026
by
IanShaw027
Browse files
feat(ui): 增强Select组件功能和样式
parent
9dce8a53
Changes
1
Hide whitespace changes
Inline
Side-by-side
frontend/src/components/common/Select.vue
View file @
2548800c
...
@@ -67,12 +67,13 @@
...
@@ -67,12 +67,13 @@
:aria-selected="isSelected(option)"
:aria-selected="isSelected(option)"
:aria-disabled="isOptionDisabled(option)"
:aria-disabled="isOptionDisabled(option)"
@click.stop="!isOptionDisabled(option)
&&
selectOption(option)"
@click.stop="!isOptionDisabled(option)
&&
selectOption(option)"
@mouseenter="
focusedIndex =
index"
@mouseenter="
handleOptionMouseEnter(option,
index
)
"
:class="[
:class="[
'select-option',
'select-option',
isGroupHeaderOption(option)
&&
'select-option-group',
isSelected(option)
&&
'select-option-selected',
isSelected(option)
&&
'select-option-selected',
isOptionDisabled(option)
&&
'select-option-disabled',
isOptionDisabled(option)
&&
!isGroupHeaderOption(option)
&&
'select-option-disabled',
focusedIndex === index
&&
'select-option-focused'
focusedIndex === index
&&
!isGroupHeaderOption(option)
&&
'select-option-focused'
]"
]"
>
>
<slot
name=
"option"
:option=
"option"
:selected=
"isSelected(option)"
>
<slot
name=
"option"
:option=
"option"
:selected=
"isSelected(option)"
>
...
@@ -201,6 +202,13 @@ const isOptionDisabled = (option: any): boolean => {
...
@@ -201,6 +202,13 @@ const isOptionDisabled = (option: any): boolean => {
return
false
return
false
}
}
const
isGroupHeaderOption
=
(
option
:
any
):
boolean
=>
{
if
(
typeof
option
===
'
object
'
&&
option
!==
null
)
{
return
option
.
kind
===
'
group
'
}
return
false
}
const
selectedOption
=
computed
(()
=>
{
const
selectedOption
=
computed
(()
=>
{
return
props
.
options
.
find
((
opt
)
=>
getOptionValue
(
opt
)
===
props
.
modelValue
)
||
null
return
props
.
options
.
find
((
opt
)
=>
getOptionValue
(
opt
)
===
props
.
modelValue
)
||
null
})
})
...
@@ -225,6 +233,31 @@ const isSelected = (option: any): boolean => {
...
@@ -225,6 +233,31 @@ const isSelected = (option: any): boolean => {
return
getOptionValue
(
option
)
===
props
.
modelValue
return
getOptionValue
(
option
)
===
props
.
modelValue
}
}
const
findNextEnabledIndex
=
(
startIndex
:
number
):
number
=>
{
const
opts
=
filteredOptions
.
value
if
(
opts
.
length
===
0
)
return
-
1
for
(
let
offset
=
0
;
offset
<
opts
.
length
;
offset
++
)
{
const
idx
=
(
startIndex
+
offset
)
%
opts
.
length
if
(
!
isOptionDisabled
(
opts
[
idx
]))
return
idx
}
return
-
1
}
const
findPrevEnabledIndex
=
(
startIndex
:
number
):
number
=>
{
const
opts
=
filteredOptions
.
value
if
(
opts
.
length
===
0
)
return
-
1
for
(
let
offset
=
0
;
offset
<
opts
.
length
;
offset
++
)
{
const
idx
=
(
startIndex
-
offset
+
opts
.
length
)
%
opts
.
length
if
(
!
isOptionDisabled
(
opts
[
idx
]))
return
idx
}
return
-
1
}
const
handleOptionMouseEnter
=
(
option
:
any
,
index
:
number
)
=>
{
if
(
isOptionDisabled
(
option
)
||
isGroupHeaderOption
(
option
))
return
focusedIndex
.
value
=
index
}
// Update trigger rect periodically while open to follow scroll/resize
// Update trigger rect periodically while open to follow scroll/resize
const
updateTriggerRect
=
()
=>
{
const
updateTriggerRect
=
()
=>
{
if
(
containerRef
.
value
)
{
if
(
containerRef
.
value
)
{
...
@@ -259,8 +292,15 @@ watch(isOpen, (open) => {
...
@@ -259,8 +292,15 @@ watch(isOpen, (open) => {
if
(
open
)
{
if
(
open
)
{
calculateDropdownPosition
()
calculateDropdownPosition
()
// Reset focused index to current selection or first item
// Reset focused index to current selection or first item
const
selectedIdx
=
filteredOptions
.
value
.
findIndex
(
isSelected
)
if
(
filteredOptions
.
value
.
length
===
0
)
{
focusedIndex
.
value
=
selectedIdx
>=
0
?
selectedIdx
:
0
focusedIndex
.
value
=
-
1
}
else
{
const
selectedIdx
=
filteredOptions
.
value
.
findIndex
(
isSelected
)
const
initialIdx
=
selectedIdx
>=
0
?
selectedIdx
:
0
focusedIndex
.
value
=
isOptionDisabled
(
filteredOptions
.
value
[
initialIdx
])
?
findNextEnabledIndex
(
initialIdx
+
1
)
:
initialIdx
}
if
(
props
.
searchable
)
{
if
(
props
.
searchable
)
{
nextTick
(()
=>
searchInputRef
.
value
?.
focus
())
nextTick
(()
=>
searchInputRef
.
value
?.
focus
())
...
@@ -295,13 +335,13 @@ const onDropdownKeyDown = (e: KeyboardEvent) => {
...
@@ -295,13 +335,13 @@ const onDropdownKeyDown = (e: KeyboardEvent) => {
switch
(
e
.
key
)
{
switch
(
e
.
key
)
{
case
'
ArrowDown
'
:
case
'
ArrowDown
'
:
e
.
preventDefault
()
e
.
preventDefault
()
focusedIndex
.
value
=
(
focusedIndex
.
value
+
1
)
%
filteredOptions
.
value
.
length
focusedIndex
.
value
=
findNextEnabledIndex
(
focusedIndex
.
value
+
1
)
scrollToFocused
()
if
(
focusedIndex
.
value
>=
0
)
scrollToFocused
()
break
break
case
'
ArrowUp
'
:
case
'
ArrowUp
'
:
e
.
preventDefault
()
e
.
preventDefault
()
focusedIndex
.
value
=
(
focusedIndex
.
value
-
1
+
filteredOptions
.
value
.
length
)
%
filteredOptions
.
value
.
length
focusedIndex
.
value
=
findPrevEnabledIndex
(
focusedIndex
.
value
-
1
)
scrollToFocused
()
if
(
focusedIndex
.
value
>=
0
)
scrollToFocused
()
break
break
case
'
Enter
'
:
case
'
Enter
'
:
e
.
preventDefault
()
e
.
preventDefault
()
...
@@ -441,6 +481,17 @@ onUnmounted(() => {
...
@@ -441,6 +481,17 @@ onUnmounted(() => {
@apply
cursor-not-allowed
opacity-40;
@apply
cursor-not-allowed
opacity-40;
}
}
.select-dropdown-portal
.select-option-group
{
@apply
cursor-default
select-none;
@apply
bg-gray-50
dark
:
bg-dark-900
;
@apply
text-[11px]
font-bold
uppercase
tracking-wider;
@apply
text-gray-500
dark
:
text-gray-400
;
}
.select-dropdown-portal
.select-option-group
:hover
{
@apply
bg-gray-50
dark
:
bg-dark-900
;
}
.select-dropdown-portal
.select-option-label
{
.select-dropdown-portal
.select-option-label
{
@apply
flex-1
min-w-0
truncate
text-left;
@apply
flex-1
min-w-0
truncate
text-left;
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment