Skip to content

Commit ca3412c

Browse files
committed
feat: 新增通用开关组件,替换掉了原生的checkbox/radio
- 新增开关组件 ToggleButton,支持以下两种用法 - 布尔开关:v-model="isDarkMode" - 单选值:v-model="colorMode" value="playerName"
1 parent df8d2da commit ca3412c

4 files changed

Lines changed: 218 additions & 105 deletions

File tree

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<template>
2+
<button
3+
class="toggle-button"
4+
:class="{ 'is-checked': isChecked }"
5+
type="button"
6+
:role="role"
7+
:aria-checked="isChecked"
8+
:disabled="disabled"
9+
@click="toggle"
10+
>
11+
<span class="toggle-button-track" aria-hidden="true">
12+
<span class="toggle-button-thumb"></span>
13+
</span>
14+
<span v-if="$slots.default" class="toggle-button-label">
15+
<slot></slot>
16+
</span>
17+
</button>
18+
</template>
19+
20+
<script setup lang="ts">
21+
import { computed } from 'vue';
22+
23+
type ToggleValue = boolean | string | number | null | undefined;
24+
25+
const props = withDefaults(
26+
defineProps<{
27+
modelValue: ToggleValue;
28+
value?: string | number | boolean;
29+
disabled?: boolean;
30+
}>(),
31+
{
32+
value: undefined,
33+
disabled: false,
34+
},
35+
);
36+
37+
const emit = defineEmits<{
38+
'update:modelValue': [value: ToggleValue];
39+
}>();
40+
41+
const usesOptionValue = computed(() => props.value !== undefined);
42+
43+
const isChecked = computed(() => {
44+
if (usesOptionValue.value) {
45+
return props.modelValue === props.value;
46+
}
47+
48+
return Boolean(props.modelValue);
49+
});
50+
51+
const role = computed(() => (usesOptionValue.value ? 'radio' : 'switch'));
52+
53+
function toggle() {
54+
if (props.disabled) {
55+
return;
56+
}
57+
58+
if (usesOptionValue.value) {
59+
if (!isChecked.value) {
60+
emit('update:modelValue', props.value);
61+
}
62+
return;
63+
}
64+
65+
emit('update:modelValue', !isChecked.value);
66+
}
67+
</script>
68+
69+
<style scoped>
70+
.toggle-button {
71+
display: inline-flex;
72+
align-items: center;
73+
gap: 8px;
74+
min-width: 0;
75+
padding: 0;
76+
border: none;
77+
background: transparent;
78+
color: inherit;
79+
font: inherit;
80+
text-align: left;
81+
cursor: pointer;
82+
}
83+
84+
.toggle-button:disabled {
85+
cursor: not-allowed;
86+
opacity: 0.55;
87+
}
88+
89+
.toggle-button-track {
90+
position: relative;
91+
width: 30px;
92+
height: 16px;
93+
flex: 0 0 auto;
94+
border: 1px solid var(--border-color);
95+
border-radius: 999px;
96+
background-color: var(--inactive-accent);
97+
}
98+
99+
.toggle-button-thumb {
100+
position: absolute;
101+
top: 2px;
102+
left: 2px;
103+
width: 12px;
104+
height: 12px;
105+
border-radius: 50%;
106+
background-color: var(--bg-workspace);
107+
box-shadow: 0 1px 2px var(--box-shadow);
108+
transition: transform 0.15s ease;
109+
}
110+
111+
.toggle-button-label {
112+
min-width: 0;
113+
line-height: 1.35;
114+
}
115+
116+
.toggle-button.is-checked .toggle-button-track {
117+
border-color: var(--active-accent);
118+
background-color: var(--active-accent);
119+
}
120+
121+
.toggle-button.is-checked .toggle-button-thumb {
122+
transform: translateX(14px);
123+
}
124+
125+
.toggle-button:focus-visible {
126+
outline: 2px solid var(--active-accent);
127+
outline-offset: 2px;
128+
}
129+
</style>

src/components/layout/SidebarLeft.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,5 +302,6 @@ function handleNavClick(panelName: typeof windowStore.activeLeftPanelName) {
302302
position: absolute;
303303
left: 49px;
304304
bottom: -1px;
305+
z-index: 10;
305306
}
306307
</style>

src/components/layout/SidebarRight.vue

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -125,38 +125,34 @@
125125
</select>
126126
</div>
127127
<div class="prop-row">
128-
<label class="checkbox-label">
129-
<input
130-
type="checkbox"
131-
:checked="message.isOoc"
132-
@change="
133-
updateField(
134-
chunk.chunkId,
135-
message.messageId,
136-
'isOoc',
137-
$event,
138-
'boolean',
139-
)
140-
"
141-
/>
128+
<ToggleButton
129+
:model-value="message.isOoc"
130+
class="inspector-toggle"
131+
@update:model-value="
132+
updateBooleanField(
133+
chunk.chunkId,
134+
message.messageId,
135+
'isOoc',
136+
Boolean($event),
137+
)
138+
"
139+
>
142140
场外消息
143-
</label>
144-
<label class="checkbox-label">
145-
<input
146-
type="checkbox"
147-
:checked="message.isCommand"
148-
@change="
149-
updateField(
150-
chunk.chunkId,
151-
message.messageId,
152-
'isCommand',
153-
$event,
154-
'boolean',
155-
)
156-
"
157-
/>
141+
</ToggleButton>
142+
<ToggleButton
143+
:model-value="message.isCommand"
144+
class="inspector-toggle"
145+
@update:model-value="
146+
updateBooleanField(
147+
chunk.chunkId,
148+
message.messageId,
149+
'isCommand',
150+
Boolean($event),
151+
)
152+
"
153+
>
158154
指令消息
159-
</label>
155+
</ToggleButton>
160156
</div>
161157

162158
<div class="prop-item full-width">
@@ -227,6 +223,7 @@ import { usePanelResize } from '@/composables/usePanelResize';
227223
import { useLogStore } from '@/stores/logStore';
228224
import { useLogEditorStore } from '@/stores/editorStore';
229225
import { useUiStore } from '@/stores/uiStore';
226+
import ToggleButton from '@/components/common/ToggleButton.vue';
230227
import type { Message } from '@/types/log';
231228
import { formatDate } from '@/utils/date';
232229
import { vClickOutside } from '@/directives/clickOutside';
@@ -288,6 +285,17 @@ function commitDraft(
288285
});
289286
}
290287
288+
function updateBooleanField(
289+
chunkId: string,
290+
messageId: string,
291+
field: 'isOoc' | 'isCommand',
292+
value: boolean,
293+
) {
294+
logEditorStore.updateMessage(chunkId, messageId, {
295+
[field]: value,
296+
});
297+
}
298+
291299
function updateField(
292300
chunkId: string,
293301
messageId: string,
@@ -389,19 +397,14 @@ function updateField(
389397
border-color: var(--active-accent);
390398
}
391399
392-
/* 复选框行 */
393400
.prop-row {
394401
grid-column: span 2;
395402
display: flex;
396403
gap: 20px;
397404
padding-top: 5px;
398405
}
399406
400-
.checkbox-label {
401-
display: flex;
402-
align-items: center;
403-
gap: 6px;
407+
.inspector-toggle {
404408
font-size: 12px;
405-
cursor: pointer;
406409
}
407410
</style>

src/components/popovers/SettingsPopover.vue

Lines changed: 49 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -3,75 +3,60 @@
33
<div class="popover-header">偏好设置</div>
44

55
<div class="settings-section">
6-
<label class="setting-item">
7-
<input
8-
type="checkbox"
9-
:checked="uiStore.followSystem"
10-
@change="
11-
uiStore.setFollowSystem(
12-
($event.target as HTMLInputElement).checked,
13-
)
14-
"
15-
/>
16-
<span>深色模式跟随系统</span>
17-
</label>
18-
<label class="setting-item">
19-
<input
20-
type="checkbox"
21-
v-model="uiStore.exportPreviewAlwaysWhite"
22-
/>
23-
<span>预览始终白色背景</span>
24-
</label>
25-
<label class="setting-item">
26-
<input type="checkbox" v-model="uiStore.showTime" />
27-
<span>显示消息时间</span>
28-
</label>
29-
<label class="setting-item">
30-
<input type="checkbox" v-model="uiStore.showAccount" />
31-
<span>显示帐号</span>
32-
</label>
33-
<label class="setting-item">
34-
<input
35-
type="checkbox"
36-
v-model="styleStore.viewSettings.hideOoc"
37-
/>
38-
<span>隐藏 OOC 消息</span>
39-
</label>
40-
<label class="setting-item">
41-
<input
42-
type="checkbox"
43-
v-model="styleStore.viewSettings.hideCommand"
44-
/>
45-
<span>隐藏指令消息</span>
46-
</label>
47-
<label
6+
<ToggleButton
7+
v-model="styleStore.viewSettings.hideOoc"
488
class="setting-item"
49-
title="开启后,被隐藏的消息将以 0.5 透明度显示而非完全消失"
509
>
51-
<input type="checkbox" v-model="uiStore.showHidden" />
52-
<span>显示已隐藏消息</span>
53-
</label>
54-
<label class="setting-item">
55-
<input
56-
type="radio"
57-
value="playerName"
58-
v-model="styleStore.viewSettings.colorMode"
59-
/>
60-
<span>按角色名染色</span>
61-
</label>
62-
<label class="setting-item">
63-
<input
64-
type="radio"
65-
value="account"
66-
v-model="styleStore.viewSettings.colorMode"
67-
/>
68-
<span>按账号染色</span>
69-
</label>
10+
隐藏 OOC 消息
11+
</ToggleButton>
12+
<ToggleButton
13+
v-model="styleStore.viewSettings.hideCommand"
14+
class="setting-item"
15+
>
16+
隐藏指令消息
17+
</ToggleButton>
18+
<ToggleButton v-model="uiStore.showHidden" class="setting-item">
19+
显示已隐藏消息
20+
</ToggleButton>
21+
<ToggleButton v-model="uiStore.showTime" class="setting-item">
22+
显示消息时间
23+
</ToggleButton>
24+
<ToggleButton v-model="uiStore.showAccount" class="setting-item">
25+
显示帐号
26+
</ToggleButton>
27+
<ToggleButton
28+
v-model="styleStore.viewSettings.colorMode"
29+
class="setting-item"
30+
value="playerName"
31+
>
32+
按角色名染色
33+
</ToggleButton>
34+
<ToggleButton
35+
v-model="styleStore.viewSettings.colorMode"
36+
class="setting-item"
37+
value="account"
38+
>
39+
按账号染色
40+
</ToggleButton>
41+
<ToggleButton
42+
class="setting-item"
43+
:model-value="uiStore.followSystem"
44+
@update:model-value="uiStore.setFollowSystem(Boolean($event))"
45+
>
46+
深色模式跟随系统
47+
</ToggleButton>
48+
<ToggleButton
49+
v-model="uiStore.exportPreviewAlwaysWhite"
50+
class="setting-item"
51+
>
52+
预览始终白色背景
53+
</ToggleButton>
7054
</div>
7155
</div>
7256
</template>
7357

7458
<script setup lang="ts">
59+
import ToggleButton from '@/components/common/ToggleButton.vue';
7560
import { useStyleStore } from '@/stores/styleStore';
7661
import { useUiStore } from '@/stores/uiStore';
7762
@@ -81,7 +66,7 @@ const styleStore = useStyleStore();
8166

8267
<style scoped>
8368
.popover {
84-
width: 200px;
69+
width: 180px;
8570
padding: 12px;
8671
box-shadow: 5px -3px 12px var(--box-shadow);
8772
}
@@ -101,17 +86,12 @@ const styleStore = useStyleStore();
10186
.setting-item {
10287
display: flex;
10388
align-items: center;
104-
gap: 8px;
89+
gap: 10px;
10590
margin-bottom: 8px;
10691
font-size: 13px;
10792
}
10893
10994
.setting-item:hover {
11095
color: var(--active-accent);
11196
}
112-
113-
.setting-item input[type='checkbox'],
114-
.setting-item input[type='radio'] {
115-
cursor: pointer;
116-
}
11797
</style>

0 commit comments

Comments
 (0)