mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
设置页新增搜索小键盘布局并支持26键
This commit is contained in:
@@ -8,6 +8,7 @@ pub struct SettingsSpec {
|
||||
pub is_wizard_completed: bool,
|
||||
pub log_level: String,
|
||||
pub window_zoom: f64,
|
||||
pub search_keyboard_layout: String,
|
||||
pub themes_custom: JsonValue,
|
||||
pub auto_score_enabled: bool,
|
||||
pub auto_score_rules: JsonValue,
|
||||
@@ -22,6 +23,7 @@ impl Default for SettingsSpec {
|
||||
is_wizard_completed: false,
|
||||
log_level: "info".to_string(),
|
||||
window_zoom: 1.0,
|
||||
search_keyboard_layout: "qwerty26".to_string(),
|
||||
themes_custom: JsonValue::Array(vec![]),
|
||||
auto_score_enabled: false,
|
||||
auto_score_rules: JsonValue::Array(vec![]),
|
||||
@@ -37,6 +39,7 @@ pub enum SettingsKey {
|
||||
IsWizardCompleted,
|
||||
LogLevel,
|
||||
WindowZoom,
|
||||
SearchKeyboardLayout,
|
||||
ThemesCustom,
|
||||
AutoScoreEnabled,
|
||||
AutoScoreRules,
|
||||
@@ -51,6 +54,7 @@ impl SettingsKey {
|
||||
SettingsKey::IsWizardCompleted => "is_wizard_completed",
|
||||
SettingsKey::LogLevel => "log_level",
|
||||
SettingsKey::WindowZoom => "window_zoom",
|
||||
SettingsKey::SearchKeyboardLayout => "search_keyboard_layout",
|
||||
SettingsKey::ThemesCustom => "themes_custom",
|
||||
SettingsKey::AutoScoreEnabled => "auto_score_enabled",
|
||||
SettingsKey::AutoScoreRules => "auto_score_rules",
|
||||
@@ -65,6 +69,7 @@ impl SettingsKey {
|
||||
"is_wizard_completed" => Some(SettingsKey::IsWizardCompleted),
|
||||
"log_level" => Some(SettingsKey::LogLevel),
|
||||
"window_zoom" => Some(SettingsKey::WindowZoom),
|
||||
"search_keyboard_layout" => Some(SettingsKey::SearchKeyboardLayout),
|
||||
"themes_custom" => Some(SettingsKey::ThemesCustom),
|
||||
"auto_score_enabled" => Some(SettingsKey::AutoScoreEnabled),
|
||||
"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(
|
||||
SettingsKey::ThemesCustom,
|
||||
SettingDefinition {
|
||||
@@ -434,6 +455,10 @@ impl SettingsService {
|
||||
SettingsValue::Number(n) => n,
|
||||
_ => 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) {
|
||||
SettingsValue::Json(j) => j,
|
||||
_ => JsonValue::Array(vec![]),
|
||||
|
||||
+90
-20
@@ -24,6 +24,7 @@ interface reason {
|
||||
}
|
||||
|
||||
type SortType = "alphabet" | "surname" | "score"
|
||||
type SearchKeyboardLayout = "t9" | "qwerty26"
|
||||
|
||||
const T9_KEY_MAP: Record<string, string> = {
|
||||
a: "2",
|
||||
@@ -62,6 +63,7 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
||||
const [sortType, setSortType] = useState<SortType>("alphabet")
|
||||
const [searchKeyword, setSearchKeyword] = useState("")
|
||||
const [showPinyinKeyboard, setShowPinyinKeyboard] = useState(false)
|
||||
const [searchKeyboardLayout, setSearchKeyboardLayout] = useState<SearchKeyboardLayout>("qwerty26")
|
||||
|
||||
const scrollContainerRef = useRef<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)
|
||||
}, [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(() => {
|
||||
const onDocumentClick = (e: MouseEvent) => {
|
||||
const target = e.target as Node | null
|
||||
@@ -136,7 +178,7 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
||||
return () => document.removeEventListener("mousedown", onDocumentClick)
|
||||
}, [])
|
||||
|
||||
const pinyinKeyRows = [
|
||||
const t9KeyRows = [
|
||||
[
|
||||
{ digit: "2", letters: "ABC" },
|
||||
{ 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) =>
|
||||
text
|
||||
.toLowerCase()
|
||||
@@ -161,12 +209,12 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
||||
.map((ch) => T9_KEY_MAP[ch] || "")
|
||||
.join("")
|
||||
|
||||
const handlePinyinKeyPress = (digit: string) => {
|
||||
if (digit === "⌫") {
|
||||
const handleSearchKeyPress = (keyValue: string) => {
|
||||
if (keyValue === "⌫") {
|
||||
setSearchKeyword((prev) => prev.slice(0, -1))
|
||||
return
|
||||
}
|
||||
setSearchKeyword((prev) => `${prev}${digit}`)
|
||||
setSearchKeyword((prev) => `${prev}${keyValue}`)
|
||||
}
|
||||
|
||||
const getDisplayText = (name: string) => {
|
||||
@@ -861,7 +909,7 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
||||
position: "absolute",
|
||||
top: "calc(100% + 8px)",
|
||||
left: 0,
|
||||
width: "220px",
|
||||
width: searchKeyboardLayout === "qwerty26" ? "260px" : "220px",
|
||||
padding: "8px",
|
||||
borderRadius: "10px",
|
||||
border: "1px solid var(--ss-border-color)",
|
||||
@@ -871,23 +919,45 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||
{pinyinKeyRows.map((row, rowIndex) => (
|
||||
<div
|
||||
key={`pinyin-row-${rowIndex}`}
|
||||
style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: "6px" }}
|
||||
>
|
||||
{row.map((keyItem) => (
|
||||
<Button
|
||||
key={keyItem.digit}
|
||||
size="small"
|
||||
onClick={() => handlePinyinKeyPress(keyItem.digit)}
|
||||
style={{ height: "28px", fontSize: "11px", padding: 0 }}
|
||||
{searchKeyboardLayout === "qwerty26"
|
||||
? qwertyKeyRows.map((row, rowIndex) => (
|
||||
<div
|
||||
key={`qwerty-row-${rowIndex}`}
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: `repeat(${row.length}, 1fr)`,
|
||||
gap: "6px",
|
||||
}}
|
||||
>
|
||||
{keyItem.digit === "⌫" ? "⌫" : `${keyItem.digit} ${keyItem.letters}`}
|
||||
</Button>
|
||||
{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
|
||||
key={`pinyin-row-${rowIndex}`}
|
||||
style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: "6px" }}
|
||||
>
|
||||
{row.map((keyItem) => (
|
||||
<Button
|
||||
key={keyItem.digit}
|
||||
size="small"
|
||||
onClick={() => handleSearchKeyPress(keyItem.digit)}
|
||||
style={{ height: "28px", fontSize: "11px", padding: 0 }}
|
||||
>
|
||||
{keyItem.digit === "⌫" ? "⌫" : `${keyItem.digit} ${keyItem.letters}`}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -9,6 +9,7 @@ type appSettings = {
|
||||
is_wizard_completed: boolean
|
||||
log_level: "debug" | "info" | "warn" | "error"
|
||||
window_zoom?: string
|
||||
search_keyboard_layout?: "t9" | "qwerty26"
|
||||
auto_score_enabled?: boolean
|
||||
}
|
||||
|
||||
@@ -20,6 +21,7 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
||||
is_wizard_completed: false,
|
||||
log_level: "info",
|
||||
window_zoom: "1.0",
|
||||
search_keyboard_layout: "qwerty26",
|
||||
})
|
||||
|
||||
const [securityStatus, setSecurityStatus] = useState<{
|
||||
@@ -108,6 +110,8 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
||||
if (change?.key === "is_wizard_completed")
|
||||
return { ...prev, is_wizard_completed: 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")
|
||||
return { ...prev, auto_score_enabled: change.value }
|
||||
return prev
|
||||
@@ -377,6 +381,34 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
||||
<Divider />
|
||||
|
||||
<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")}>
|
||||
<Select
|
||||
value={settings.window_zoom || "1.0"}
|
||||
|
||||
@@ -127,6 +127,14 @@
|
||||
"darkMode": "Dark",
|
||||
"primaryColor": "Primary Color",
|
||||
"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",
|
||||
"zoomHint": "Adjust the overall size of the application interface",
|
||||
"zoomUpdated": "Interface zoom updated",
|
||||
|
||||
@@ -127,6 +127,14 @@
|
||||
"darkMode": "深色",
|
||||
"primaryColor": "主色",
|
||||
"backgroundGradient": "背景渐变",
|
||||
"searchKeyboard": {
|
||||
"title": "搜索小键盘",
|
||||
"hint": "选择搜索框下方弹出的小键盘布局",
|
||||
"options": {
|
||||
"qwerty26": "26 键(QWERTY)",
|
||||
"t9": "9 键(T9)"
|
||||
}
|
||||
},
|
||||
"interfaceZoom": "界面缩放",
|
||||
"zoomHint": "调节应用界面的整体大小",
|
||||
"zoomUpdated": "界面缩放已更新",
|
||||
|
||||
@@ -21,6 +21,7 @@ export type settingsKey =
|
||||
| "is_wizard_completed"
|
||||
| "log_level"
|
||||
| "window_zoom"
|
||||
| "search_keyboard_layout"
|
||||
| "themes_custom"
|
||||
| "auto_score_enabled"
|
||||
| "auto_score_rules"
|
||||
@@ -32,6 +33,7 @@ export interface settingsSpec {
|
||||
is_wizard_completed: boolean
|
||||
log_level: string
|
||||
window_zoom: number
|
||||
search_keyboard_layout: "t9" | "qwerty26"
|
||||
themes_custom: themeConfig[]
|
||||
auto_score_enabled: boolean
|
||||
auto_score_rules: any[]
|
||||
|
||||
Reference in New Issue
Block a user