设置页新增搜索小键盘布局并支持26键

This commit is contained in:
JSR
2026-03-18 19:55:50 +08:00
parent 51545dd0df
commit 5b57ee1411
6 changed files with 165 additions and 20 deletions
+25
View File
@@ -8,6 +8,7 @@ pub struct SettingsSpec {
pub is_wizard_completed: bool, pub is_wizard_completed: bool,
pub log_level: String, pub log_level: String,
pub window_zoom: f64, pub window_zoom: f64,
pub search_keyboard_layout: String,
pub themes_custom: JsonValue, pub themes_custom: JsonValue,
pub auto_score_enabled: bool, pub auto_score_enabled: bool,
pub auto_score_rules: JsonValue, pub auto_score_rules: JsonValue,
@@ -22,6 +23,7 @@ impl Default for SettingsSpec {
is_wizard_completed: false, is_wizard_completed: false,
log_level: "info".to_string(), log_level: "info".to_string(),
window_zoom: 1.0, window_zoom: 1.0,
search_keyboard_layout: "qwerty26".to_string(),
themes_custom: JsonValue::Array(vec![]), themes_custom: JsonValue::Array(vec![]),
auto_score_enabled: false, auto_score_enabled: false,
auto_score_rules: JsonValue::Array(vec![]), auto_score_rules: JsonValue::Array(vec![]),
@@ -37,6 +39,7 @@ pub enum SettingsKey {
IsWizardCompleted, IsWizardCompleted,
LogLevel, LogLevel,
WindowZoom, WindowZoom,
SearchKeyboardLayout,
ThemesCustom, ThemesCustom,
AutoScoreEnabled, AutoScoreEnabled,
AutoScoreRules, AutoScoreRules,
@@ -51,6 +54,7 @@ impl SettingsKey {
SettingsKey::IsWizardCompleted => "is_wizard_completed", SettingsKey::IsWizardCompleted => "is_wizard_completed",
SettingsKey::LogLevel => "log_level", SettingsKey::LogLevel => "log_level",
SettingsKey::WindowZoom => "window_zoom", SettingsKey::WindowZoom => "window_zoom",
SettingsKey::SearchKeyboardLayout => "search_keyboard_layout",
SettingsKey::ThemesCustom => "themes_custom", SettingsKey::ThemesCustom => "themes_custom",
SettingsKey::AutoScoreEnabled => "auto_score_enabled", SettingsKey::AutoScoreEnabled => "auto_score_enabled",
SettingsKey::AutoScoreRules => "auto_score_rules", SettingsKey::AutoScoreRules => "auto_score_rules",
@@ -65,6 +69,7 @@ impl SettingsKey {
"is_wizard_completed" => Some(SettingsKey::IsWizardCompleted), "is_wizard_completed" => Some(SettingsKey::IsWizardCompleted),
"log_level" => Some(SettingsKey::LogLevel), "log_level" => Some(SettingsKey::LogLevel),
"window_zoom" => Some(SettingsKey::WindowZoom), "window_zoom" => Some(SettingsKey::WindowZoom),
"search_keyboard_layout" => Some(SettingsKey::SearchKeyboardLayout),
"themes_custom" => Some(SettingsKey::ThemesCustom), "themes_custom" => Some(SettingsKey::ThemesCustom),
"auto_score_enabled" => Some(SettingsKey::AutoScoreEnabled), "auto_score_enabled" => Some(SettingsKey::AutoScoreEnabled),
"auto_score_rules" => Some(SettingsKey::AutoScoreRules), "auto_score_rules" => Some(SettingsKey::AutoScoreRules),
@@ -270,6 +275,22 @@ impl SettingsService {
}, },
); );
defs.insert(
SettingsKey::SearchKeyboardLayout,
SettingDefinition {
kind: SettingValueKind::String,
default_value: SettingsValue::String("qwerty26".to_string()),
write_permission: PermissionRequirement::Admin,
validate: Some(|v| {
if let SettingsValue::String(s) = v {
matches!(s.as_str(), "t9" | "qwerty26")
} else {
false
}
}),
},
);
defs.insert( defs.insert(
SettingsKey::ThemesCustom, SettingsKey::ThemesCustom,
SettingDefinition { SettingDefinition {
@@ -434,6 +455,10 @@ impl SettingsService {
SettingsValue::Number(n) => n, SettingsValue::Number(n) => n,
_ => 1.0, _ => 1.0,
}, },
search_keyboard_layout: match self.get_value(SettingsKey::SearchKeyboardLayout) {
SettingsValue::String(s) => s,
_ => "qwerty26".to_string(),
},
themes_custom: match self.get_value(SettingsKey::ThemesCustom) { themes_custom: match self.get_value(SettingsKey::ThemesCustom) {
SettingsValue::Json(j) => j, SettingsValue::Json(j) => j,
_ => JsonValue::Array(vec![]), _ => JsonValue::Array(vec![]),
+77 -7
View File
@@ -24,6 +24,7 @@ interface reason {
} }
type SortType = "alphabet" | "surname" | "score" type SortType = "alphabet" | "surname" | "score"
type SearchKeyboardLayout = "t9" | "qwerty26"
const T9_KEY_MAP: Record<string, string> = { const T9_KEY_MAP: Record<string, string> = {
a: "2", a: "2",
@@ -62,6 +63,7 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
const [sortType, setSortType] = useState<SortType>("alphabet") const [sortType, setSortType] = useState<SortType>("alphabet")
const [searchKeyword, setSearchKeyword] = useState("") const [searchKeyword, setSearchKeyword] = useState("")
const [showPinyinKeyboard, setShowPinyinKeyboard] = useState(false) const [showPinyinKeyboard, setShowPinyinKeyboard] = useState(false)
const [searchKeyboardLayout, setSearchKeyboardLayout] = useState<SearchKeyboardLayout>("qwerty26")
const scrollContainerRef = useRef<HTMLDivElement>(null) const scrollContainerRef = useRef<HTMLDivElement>(null)
const groupRefs = useRef<Record<string, HTMLDivElement | null>>({}) const groupRefs = useRef<Record<string, HTMLDivElement | null>>({})
@@ -125,6 +127,46 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
return () => window.removeEventListener("ss:data-updated", onDataUpdated as any) return () => window.removeEventListener("ss:data-updated", onDataUpdated as any)
}, [fetchData]) }, [fetchData])
useEffect(() => {
const api = (window as any).api
if (!api) return
let disposed = false
let unlisten: (() => void) | null = null
api
.getSetting("search_keyboard_layout")
.then((res: any) => {
if (disposed) return
const value = res?.data
if (value === "t9" || value === "qwerty26") {
setSearchKeyboardLayout(value)
}
})
.catch(() => void 0)
api
.onSettingChanged((change: any) => {
if (change?.key !== "search_keyboard_layout") return
if (change?.value === "t9" || change?.value === "qwerty26") {
setSearchKeyboardLayout(change.value)
}
})
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => {
disposed = true
if (unlisten) unlisten()
}
}, [])
useEffect(() => { useEffect(() => {
const onDocumentClick = (e: MouseEvent) => { const onDocumentClick = (e: MouseEvent) => {
const target = e.target as Node | null const target = e.target as Node | null
@@ -136,7 +178,7 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
return () => document.removeEventListener("mousedown", onDocumentClick) return () => document.removeEventListener("mousedown", onDocumentClick)
}, []) }, [])
const pinyinKeyRows = [ const t9KeyRows = [
[ [
{ digit: "2", letters: "ABC" }, { digit: "2", letters: "ABC" },
{ digit: "3", letters: "DEF" }, { digit: "3", letters: "DEF" },
@@ -154,6 +196,12 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
], ],
] ]
const qwertyKeyRows = [
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
["a", "s", "d", "f", "g", "h", "j", "k", "l"],
["z", "x", "c", "v", "b", "n", "m", "⌫"],
]
const toT9Digits = (text: string) => const toT9Digits = (text: string) =>
text text
.toLowerCase() .toLowerCase()
@@ -161,12 +209,12 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
.map((ch) => T9_KEY_MAP[ch] || "") .map((ch) => T9_KEY_MAP[ch] || "")
.join("") .join("")
const handlePinyinKeyPress = (digit: string) => { const handleSearchKeyPress = (keyValue: string) => {
if (digit === "⌫") { if (keyValue === "⌫") {
setSearchKeyword((prev) => prev.slice(0, -1)) setSearchKeyword((prev) => prev.slice(0, -1))
return return
} }
setSearchKeyword((prev) => `${prev}${digit}`) setSearchKeyword((prev) => `${prev}${keyValue}`)
} }
const getDisplayText = (name: string) => { const getDisplayText = (name: string) => {
@@ -861,7 +909,7 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
position: "absolute", position: "absolute",
top: "calc(100% + 8px)", top: "calc(100% + 8px)",
left: 0, left: 0,
width: "220px", width: searchKeyboardLayout === "qwerty26" ? "260px" : "220px",
padding: "8px", padding: "8px",
borderRadius: "10px", borderRadius: "10px",
border: "1px solid var(--ss-border-color)", border: "1px solid var(--ss-border-color)",
@@ -871,7 +919,29 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
}} }}
> >
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}> <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
{pinyinKeyRows.map((row, rowIndex) => ( {searchKeyboardLayout === "qwerty26"
? qwertyKeyRows.map((row, rowIndex) => (
<div
key={`qwerty-row-${rowIndex}`}
style={{
display: "grid",
gridTemplateColumns: `repeat(${row.length}, 1fr)`,
gap: "6px",
}}
>
{row.map((keyItem) => (
<Button
key={keyItem}
size="small"
onClick={() => handleSearchKeyPress(keyItem)}
style={{ height: "28px", fontSize: "11px", padding: 0 }}
>
{keyItem === "⌫" ? "⌫" : keyItem.toUpperCase()}
</Button>
))}
</div>
))
: t9KeyRows.map((row, rowIndex) => (
<div <div
key={`pinyin-row-${rowIndex}`} key={`pinyin-row-${rowIndex}`}
style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: "6px" }} style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: "6px" }}
@@ -880,7 +950,7 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
<Button <Button
key={keyItem.digit} key={keyItem.digit}
size="small" size="small"
onClick={() => handlePinyinKeyPress(keyItem.digit)} onClick={() => handleSearchKeyPress(keyItem.digit)}
style={{ height: "28px", fontSize: "11px", padding: 0 }} style={{ height: "28px", fontSize: "11px", padding: 0 }}
> >
{keyItem.digit === "⌫" ? "⌫" : `${keyItem.digit} ${keyItem.letters}`} {keyItem.digit === "⌫" ? "⌫" : `${keyItem.digit} ${keyItem.letters}`}
+32
View File
@@ -9,6 +9,7 @@ type appSettings = {
is_wizard_completed: boolean is_wizard_completed: boolean
log_level: "debug" | "info" | "warn" | "error" log_level: "debug" | "info" | "warn" | "error"
window_zoom?: string window_zoom?: string
search_keyboard_layout?: "t9" | "qwerty26"
auto_score_enabled?: boolean auto_score_enabled?: boolean
} }
@@ -20,6 +21,7 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
is_wizard_completed: false, is_wizard_completed: false,
log_level: "info", log_level: "info",
window_zoom: "1.0", window_zoom: "1.0",
search_keyboard_layout: "qwerty26",
}) })
const [securityStatus, setSecurityStatus] = useState<{ const [securityStatus, setSecurityStatus] = useState<{
@@ -108,6 +110,8 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
if (change?.key === "is_wizard_completed") if (change?.key === "is_wizard_completed")
return { ...prev, is_wizard_completed: change.value } return { ...prev, is_wizard_completed: change.value }
if (change?.key === "window_zoom") return { ...prev, window_zoom: change.value } if (change?.key === "window_zoom") return { ...prev, window_zoom: change.value }
if (change?.key === "search_keyboard_layout")
return { ...prev, search_keyboard_layout: change.value }
if (change?.key === "auto_score_enabled") if (change?.key === "auto_score_enabled")
return { ...prev, auto_score_enabled: change.value } return { ...prev, auto_score_enabled: change.value }
return prev return prev
@@ -377,6 +381,34 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
<Divider /> <Divider />
<Form layout="horizontal" labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}> <Form layout="horizontal" labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>
<Form.Item label={t("settings.searchKeyboard.title")}>
<Select
value={settings.search_keyboard_layout || "qwerty26"}
onChange={async (v) => {
if (!(window as any).api) return
const next = String(v) as "t9" | "qwerty26"
const res = await (window as any).api.setSetting("search_keyboard_layout", next)
if (res.success) {
setSettings((prev) => ({ ...prev, search_keyboard_layout: next }))
messageApi.success(t("settings.general.saved"))
} else {
messageApi.error(res.message || t("settings.general.saveFailed"))
}
}}
style={{ width: "320px" }}
disabled={!canAdmin}
options={[
{ value: "qwerty26", label: t("settings.searchKeyboard.options.qwerty26") },
{ value: "t9", label: t("settings.searchKeyboard.options.t9") },
]}
/>
<div
style={{ marginTop: "4px", fontSize: "12px", color: "var(--ss-text-secondary)" }}
>
{t("settings.searchKeyboard.hint")}
</div>
</Form.Item>
<Form.Item label={t("settings.interfaceZoom")}> <Form.Item label={t("settings.interfaceZoom")}>
<Select <Select
value={settings.window_zoom || "1.0"} value={settings.window_zoom || "1.0"}
+8
View File
@@ -127,6 +127,14 @@
"darkMode": "Dark", "darkMode": "Dark",
"primaryColor": "Primary Color", "primaryColor": "Primary Color",
"backgroundGradient": "Background Gradient", "backgroundGradient": "Background Gradient",
"searchKeyboard": {
"title": "Search Keyboard",
"hint": "Choose the pop-up keyboard layout under the search box",
"options": {
"qwerty26": "26-key (QWERTY)",
"t9": "9-key (T9)"
}
},
"interfaceZoom": "Interface Zoom", "interfaceZoom": "Interface Zoom",
"zoomHint": "Adjust the overall size of the application interface", "zoomHint": "Adjust the overall size of the application interface",
"zoomUpdated": "Interface zoom updated", "zoomUpdated": "Interface zoom updated",
+8
View File
@@ -127,6 +127,14 @@
"darkMode": "深色", "darkMode": "深色",
"primaryColor": "主色", "primaryColor": "主色",
"backgroundGradient": "背景渐变", "backgroundGradient": "背景渐变",
"searchKeyboard": {
"title": "搜索小键盘",
"hint": "选择搜索框下方弹出的小键盘布局",
"options": {
"qwerty26": "26 键(QWERTY",
"t9": "9 键(T9"
}
},
"interfaceZoom": "界面缩放", "interfaceZoom": "界面缩放",
"zoomHint": "调节应用界面的整体大小", "zoomHint": "调节应用界面的整体大小",
"zoomUpdated": "界面缩放已更新", "zoomUpdated": "界面缩放已更新",
+2
View File
@@ -21,6 +21,7 @@ export type settingsKey =
| "is_wizard_completed" | "is_wizard_completed"
| "log_level" | "log_level"
| "window_zoom" | "window_zoom"
| "search_keyboard_layout"
| "themes_custom" | "themes_custom"
| "auto_score_enabled" | "auto_score_enabled"
| "auto_score_rules" | "auto_score_rules"
@@ -32,6 +33,7 @@ export interface settingsSpec {
is_wizard_completed: boolean is_wizard_completed: boolean
log_level: string log_level: string
window_zoom: number window_zoom: number
search_keyboard_layout: "t9" | "qwerty26"
themes_custom: themeConfig[] themes_custom: themeConfig[]
auto_score_enabled: boolean auto_score_enabled: boolean
auto_score_rules: any[] auto_score_rules: any[]