mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-21 18:19:03 +08:00
设置页新增搜索小键盘布局并支持26键
This commit is contained in:
@@ -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
@@ -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}`}
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": "界面缩放已更新",
|
||||||
|
|||||||
@@ -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[]
|
||||||
|
|||||||
Reference in New Issue
Block a user