diff --git a/web/src/features/playback/playback.css b/web/src/features/playback/playback.css
index 63f5dc8..db055c8 100644
--- a/web/src/features/playback/playback.css
+++ b/web/src/features/playback/playback.css
@@ -15,9 +15,7 @@
.player-tools { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
.player-tools .volume-control { display: flex; align-items: center; gap: 6px; margin-right: 16px; }
.player-tools .volume-slider { width: 90px; accent-color: var(--color-foreground); }
-.player-tools .volume-input { box-sizing: border-box; width: 4.5ch; min-width: 0; padding: 4px 3px; text-align: center; font-variant-numeric: tabular-nums; -moz-appearance: textfield; }
-.player-tools .volume-input::-webkit-inner-spin-button, .player-tools .volume-input::-webkit-outer-spin-button { margin: 0; -webkit-appearance: none; }
-.player-tools .volume-unit { color: var(--color-foreground-subtle); font-size: var(--text-ui-xs); }
+.player-tools .volume-readout { min-width: 5ch; color: var(--color-foreground-subtle); font-size: var(--text-ui-sm); font-variant-numeric: tabular-nums; }
.player-panel-toggle { color: var(--color-foreground-subtle); }
.player-panel-toggle:hover:not(:disabled), .player-panel-toggle:focus-visible:not(:disabled), .player-panel-toggle[aria-pressed='true'] { color: var(--color-brand); background: transparent; }
.player-panel-toggle:active:not(:disabled) { color: var(--color-brand); background: transparent; transform: none; }
diff --git a/web/src/features/playback/player-bar.tsx b/web/src/features/playback/player-bar.tsx
index a460ce5..a090bd6 100644
--- a/web/src/features/playback/player-bar.tsx
+++ b/web/src/features/playback/player-bar.tsx
@@ -4,7 +4,7 @@ import { roomStore } from '@/core/room'
import { sourceName } from '@/features/queue'
import { formatDuration } from '@/shared/lib/time'
import { useStore } from '@/shared/lib/use-store'
-import { Artwork, AsyncIconButton, IconButton, VolumeInput } from '@/shared/ui'
+import { Artwork, AsyncIconButton, IconButton } from '@/shared/ui'
import { playerStore } from './player-store'
import { volumeStore } from './volume-store'
import './playback.css'
@@ -64,6 +64,7 @@ export function PlayerBar({ onSkip, onLyrics, onQueue, onChat, queueOpen, chatOp
const connected = useStore(roomStore, s => s.status === 'open')
const volume = useStore(volumeStore, s => s.volume)
const muted = useStore(volumeStore, s => s.muted)
+ const audibleVolume = muted ? 0 : volume
const meta = nowPlaying?.entry.meta
const artist = meta?.artist || '未知艺术家'
const source = nowPlaying ? sourceName(nowPlaying.entry.track) : undefined
@@ -90,15 +91,23 @@ export function PlayerBar({ onSkip, onLyrics, onQueue, onChat, queueOpen, chatOp
diff --git a/web/src/features/settings/settings-page.tsx b/web/src/features/settings/settings-page.tsx
index 23b17c9..ac84d20 100644
--- a/web/src/features/settings/settings-page.tsx
+++ b/web/src/features/settings/settings-page.tsx
@@ -6,10 +6,11 @@ import { errorMessage } from '@/core/errors'
import { historyStore, searchLimits } from '@/features/history'
import { reminderStore, requestNotificationPermission, systemNotificationsSupported, volumeStore } from '@/features/playback'
import { useStore } from '@/shared/lib/use-store'
-import { notificationStore, Select, VolumeInput } from '@/shared/ui'
+import { notificationStore, Select } from '@/shared/ui'
import { appearanceStore, fontSizes, themes } from './appearance-store'
import { performanceLevels, performanceStore } from './performance-store'
import { profileStore } from './profile-store'
+import { VolumeInput } from './volume-input'
import './settings.css'
interface SettingsDraft {
diff --git a/web/src/shared/ui/volume-editor.test.ts b/web/src/features/settings/volume-editor.test.ts
similarity index 100%
rename from web/src/shared/ui/volume-editor.test.ts
rename to web/src/features/settings/volume-editor.test.ts
diff --git a/web/src/shared/ui/volume-editor.ts b/web/src/features/settings/volume-editor.ts
similarity index 100%
rename from web/src/shared/ui/volume-editor.ts
rename to web/src/features/settings/volume-editor.ts
diff --git a/web/src/shared/ui/volume-input.tsx b/web/src/features/settings/volume-input.tsx
similarity index 91%
rename from web/src/shared/ui/volume-input.tsx
rename to web/src/features/settings/volume-input.tsx
index 760b24f..ce35b47 100644
--- a/web/src/shared/ui/volume-input.tsx
+++ b/web/src/features/settings/volume-input.tsx
@@ -4,13 +4,12 @@ import { beginVolumeEdit, changeVolumeDraft, commitVolumeDraft, initialVolumeEdi
interface VolumeInputProps {
volume: number
onCommit: (volume: number) => void
- sliderValue?: number
id?: string
className?: string
disabled?: boolean
}
-export function VolumeInput({ volume, onCommit, sliderValue, id, className = '', disabled = false }: VolumeInputProps) {
+export function VolumeInput({ volume, onCommit, id, className = '', disabled = false }: VolumeInputProps) {
const [editor, setEditor] = useState(() => initialVolumeEditor(volume))
const syncedEditor = syncVolumeDraft(editor, volume)
const commit = () => {
@@ -27,7 +26,7 @@ export function VolumeInput({ volume, onCommit, sliderValue, id, className = '',
type="range"
min="0"
max="100"
- value={sliderValue ?? volume}
+ value={volume}
aria-label="音量"
disabled={disabled}
onChange={(event) => {
diff --git a/web/src/shared/ui/index.ts b/web/src/shared/ui/index.ts
index c5f046b..e5b275c 100644
--- a/web/src/shared/ui/index.ts
+++ b/web/src/shared/ui/index.ts
@@ -16,4 +16,3 @@ export { Skeleton } from './skeleton'
export { SongArtwork } from './song-artwork'
export { SongList } from './song-list'
export type { SongAction, SongItem } from './song-list'
-export { VolumeInput } from './volume-input'