fix: 修复移动端底栏拖拽排序与更多分组异常

This commit is contained in:
JSR
2026-03-28 10:41:43 +08:00
parent 9a4e93bb9b
commit f3ee0c5202
5 changed files with 320 additions and 164 deletions
+303 -54
View File
@@ -1,4 +1,4 @@
import { Layout, Modal, Input, message, ConfigProvider, theme as antTheme } from "antd" import { Layout, Modal, Input, message, ConfigProvider, theme as antTheme, Drawer } from "antd"
import { import {
HomeOutlined, HomeOutlined,
SettingOutlined, SettingOutlined,
@@ -10,6 +10,9 @@ import {
UnorderedListOutlined, UnorderedListOutlined,
FileTextOutlined, FileTextOutlined,
MoreOutlined, MoreOutlined,
UpOutlined,
DownOutlined,
HolderOutlined,
} from "@ant-design/icons" } from "@ant-design/icons"
import { useEffect, useMemo, useRef, useState } from "react" import { useEffect, useMemo, useRef, useState } from "react"
import { HashRouter, useLocation, useNavigate, Routes, Route } from "react-router-dom" import { HashRouter, useLocation, useNavigate, Routes, Route } from "react-router-dom"
@@ -25,6 +28,7 @@ import {
} from "./shared/mobileNavigation" } from "./shared/mobileNavigation"
const DEFAULT_MOBILE_BOTTOM_NAV_ITEMS: MobileNavKey[] = MOBILE_NAV_ITEMS.map((item) => item.key) const DEFAULT_MOBILE_BOTTOM_NAV_ITEMS: MobileNavKey[] = MOBILE_NAV_ITEMS.map((item) => item.key)
const DEFAULT_MOBILE_BOTTOM_PRIMARY_KEYS: MobileNavKey[] = DEFAULT_MOBILE_BOTTOM_NAV_ITEMS.slice(0, 4)
function MainContent(): React.JSX.Element { function MainContent(): React.JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
@@ -35,6 +39,13 @@ function MainContent(): React.JSX.Element {
const { isIosDevice, isAndroidDevice, defaultPortraitMode } = useMemo(getMobileDeviceInfo, []) const { isIosDevice, isAndroidDevice, defaultPortraitMode } = useMemo(getMobileDeviceInfo, [])
const [immersiveMode, setImmersiveMode] = useState(false) const [immersiveMode, setImmersiveMode] = useState(false)
const normalizeStoredBottomKeys = (raw: unknown): MobileNavKey[] => {
if (Array.isArray(raw)) {
return sanitizeMobileNavKeys(raw, []).slice(0, 4)
}
return DEFAULT_MOBILE_BOTTOM_PRIMARY_KEYS
}
useEffect(() => { useEffect(() => {
const api = (window as any).api const api = (window as any).api
if (!api) return if (!api) return
@@ -81,6 +92,14 @@ function MainContent(): React.JSX.Element {
DEFAULT_MOBILE_BOTTOM_NAV_ITEMS DEFAULT_MOBILE_BOTTOM_NAV_ITEMS
) )
const [moreNavVisible, setMoreNavVisible] = useState(false) const [moreNavVisible, setMoreNavVisible] = useState(false)
const [editingNav, setEditingNav] = useState(false)
const [editingBottomNavKeys, setEditingBottomNavKeys] = useState<MobileNavKey[]>([])
const [editingMoreNavKeys, setEditingMoreNavKeys] = useState<MobileNavKey[]>([])
const [draggingNavKey, setDraggingNavKey] = useState<MobileNavKey | null>(null)
const [draggingFromList, setDraggingFromList] = useState<"bottom" | "more" | null>(null)
const [dragOverSlot, setDragOverSlot] = useState<{ list: "bottom" | "more"; index: number } | null>(
null
)
const [isPortraitMode] = useState(defaultPortraitMode) const [isPortraitMode] = useState(defaultPortraitMode)
const [sidebarCollapsed, setSidebarCollapsed] = useState(defaultPortraitMode) const [sidebarCollapsed, setSidebarCollapsed] = useState(defaultPortraitMode)
const [floatingSidebarExpanded, setFloatingSidebarExpanded] = useState(false) const [floatingSidebarExpanded, setFloatingSidebarExpanded] = useState(false)
@@ -131,12 +150,7 @@ function MainContent(): React.JSX.Element {
} }
const settingsRes = await (window as any).api.getAllSettings() const settingsRes = await (window as any).api.getAllSettings()
if (settingsRes?.success && settingsRes.data) { if (settingsRes?.success && settingsRes.data) {
setMobileBottomNavItems( setMobileBottomNavItems(normalizeStoredBottomKeys(settingsRes.data.mobile_bottom_nav_items))
sanitizeMobileNavKeys(
settingsRes.data.mobile_bottom_nav_items,
DEFAULT_MOBILE_BOTTOM_NAV_ITEMS
)
)
} }
} }
@@ -153,9 +167,7 @@ function MainContent(): React.JSX.Element {
api api
.onSettingChanged((change: { key?: string; value?: unknown }) => { .onSettingChanged((change: { key?: string; value?: unknown }) => {
if (change?.key !== "mobile_bottom_nav_items") return if (change?.key !== "mobile_bottom_nav_items") return
setMobileBottomNavItems( setMobileBottomNavItems(normalizeStoredBottomKeys(change.value))
sanitizeMobileNavKeys(change.value, DEFAULT_MOBILE_BOTTOM_NAV_ITEMS)
)
}) })
.then((fn: () => void) => { .then((fn: () => void) => {
if (disposed) { if (disposed) {
@@ -370,6 +382,7 @@ function MainContent(): React.JSX.Element {
const onMenuChange = (v: string) => { const onMenuChange = (v: string) => {
const key = String(v) const key = String(v)
setMoreNavVisible(false) setMoreNavVisible(false)
setEditingNav(false)
if (immersiveMode && key !== "home") return if (immersiveMode && key !== "home") return
if (key === "home") navigate("/") if (key === "home") navigate("/")
if (key === "students") navigate("/students") if (key === "students") navigate("/students")
@@ -429,33 +442,96 @@ function MainContent(): React.JSX.Element {
settings: <SettingOutlined style={{ fontSize: "18px" }} />, settings: <SettingOutlined style={{ fontSize: "18px" }} />,
} }
const mobileBottomVisibleItems = useMemo(() => { const mobileNavAvailableItems = useMemo(
const preferredKeys = sanitizeMobileNavKeys( () => MOBILE_NAV_ITEMS.filter((item) => !(item.adminOnly && permission !== "admin")),
mobileBottomNavItems, [permission]
DEFAULT_MOBILE_BOTTOM_NAV_ITEMS )
).slice() const mobileBottomSelectedKeys = useMemo(() => {
for (const baseKey of ["home", "settings"] as const) { const availableKeySet = new Set(mobileNavAvailableItems.map((item) => item.key))
if (!preferredKeys.includes(baseKey)) { return sanitizeMobileNavKeys(mobileBottomNavItems, [])
preferredKeys.push(baseKey) .filter((key) => availableKeySet.has(key))
} .slice(0, 4)
} }, [mobileBottomNavItems, mobileNavAvailableItems])
return preferredKeys
.map((key) => MOBILE_NAV_ITEMS.find((item) => item.key === key))
.filter((item): item is (typeof MOBILE_NAV_ITEMS)[number] => Boolean(item))
.filter((item) => !(item.adminOnly && permission !== "admin"))
}, [mobileBottomNavItems, permission])
const mobileBottomPrimaryItems = useMemo( const mobileBottomPrimaryItems = useMemo(
() => mobileBottomVisibleItems.slice(0, 4), () =>
[mobileBottomVisibleItems] mobileBottomSelectedKeys
.map((key) => mobileNavAvailableItems.find((item) => item.key === key))
.filter((item): item is (typeof MOBILE_NAV_ITEMS)[number] => Boolean(item)),
[mobileBottomSelectedKeys, mobileNavAvailableItems]
) )
const mobileBottomOverflowItems = useMemo( const mobileBottomOverflowItems = useMemo(
() => mobileBottomVisibleItems.slice(4), () => mobileNavAvailableItems.filter((item) => !mobileBottomSelectedKeys.includes(item.key)),
[mobileBottomVisibleItems] [mobileNavAvailableItems, mobileBottomSelectedKeys]
) )
const isMoreActive = mobileBottomOverflowItems.some((item) => item.key === activeMenu) const isMoreActive = mobileBottomOverflowItems.some((item) => item.key === activeMenu)
const openEditNav = () => {
const savedKeys = mobileBottomSelectedKeys
const missingKeys = mobileNavAvailableItems
.map((item) => item.key)
.filter((key) => !savedKeys.includes(key))
setEditingBottomNavKeys(savedKeys)
setEditingMoreNavKeys(missingKeys)
setEditingNav(true)
setDraggingNavKey(null)
setDraggingFromList(null)
setDragOverSlot(null)
}
const dropPlaceholder = (
<div
style={{
border: "1px dashed var(--ant-color-primary)",
borderRadius: "10px",
background: "color-mix(in srgb, var(--ant-color-primary) 10%, transparent)",
minHeight: "44px",
padding: "8px 10px",
}}
/>
)
const persistMobileBottomKeys = async (nextKeys: MobileNavKey[]) => {
const api = (window as any).api
if (!api) return
const next = sanitizeMobileNavKeys(nextKeys, [])
const res = await api.setSetting("mobile_bottom_nav_items", next)
if (res.success) {
setMobileBottomNavItems(next)
} else {
messageApi.error(res.message || t("settings.general.saveFailed"))
}
}
const handleDropToList = (targetList: "bottom" | "more", targetIndex: number) => {
if (!draggingNavKey || !draggingFromList) return
const bottom = editingBottomNavKeys.slice()
const more = editingMoreNavKeys.slice()
const source = draggingFromList === "bottom" ? bottom : more
const sourceIndex = source.indexOf(draggingNavKey)
if (sourceIndex < 0) return
source.splice(sourceIndex, 1)
if (targetList === "bottom" && draggingFromList === "more" && bottom.length >= 4) {
setDraggingNavKey(null)
setDraggingFromList(null)
setDragOverSlot(null)
messageApi.warning(t("settings.mobile.bottomMaxHint", "底栏最多 4 个"))
return
}
const target = targetList === "bottom" ? bottom : more
const clampedIndex = Math.max(0, Math.min(targetIndex, target.length))
target.splice(clampedIndex, 0, draggingNavKey)
setEditingBottomNavKeys(bottom)
setEditingMoreNavKeys(more)
void persistMobileBottomKeys(bottom)
setDraggingNavKey(null)
setDraggingFromList(null)
setDragOverSlot(null)
}
return ( return (
<ConfigProvider <ConfigProvider
theme={{ theme={{
@@ -575,38 +651,211 @@ function MainContent(): React.JSX.Element {
</div> </div>
)} )}
<Modal <Drawer
title={t("common.more", "更多")} title={editingNav ? t("common.edit") : t("common.more", "更多")}
placement="bottom"
open={moreNavVisible} open={moreNavVisible}
onCancel={() => setMoreNavVisible(false)} onClose={() => {
footer={null} setMoreNavVisible(false)
width={360} setEditingNav(false)
> setDraggingNavKey(null)
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}> setDraggingFromList(null)
{mobileBottomOverflowItems.map((item) => ( setDragOverSlot(null)
}}
height={editingNav ? "calc(100vh - env(safe-area-inset-top, 0px))" : "46vh"}
styles={{
content: {
borderTopLeftRadius: editingNav ? 0 : "14px",
borderTopRightRadius: editingNav ? 0 : "14px",
background: "var(--ss-card-bg)",
},
header: {
borderBottom: "1px solid var(--ss-border-color)",
padding: "12px 16px",
},
body: {
padding: "12px 16px 20px",
display: "flex",
flexDirection: "column",
minHeight: 0,
},
}}
extra={
permission === "admin" &&
(editingNav ? (
<button <button
key={item.key}
type="button" type="button"
onClick={() => onMenuChange(item.key)} onClick={() => setEditingNav(false)}
style={{ style={{
border: "1px solid var(--ss-border-color)", border: "1px solid var(--ss-border-color)",
borderRadius: "10px", borderRadius: "8px",
background: "var(--ss-card-bg)", background: "transparent",
color: "var(--ss-text-main)", color: "var(--ss-text-main)",
height: "44px", padding: "4px 10px",
padding: "0 12px",
display: "flex",
alignItems: "center",
gap: "8px",
fontSize: "14px",
}} }}
> >
{mobileBottomNavIconMap[item.key]} {t("common.finish", "完成")}
<span>{t(item.labelKey)}</span>
</button> </button>
))} ) : (
</div> <button
</Modal> type="button"
onClick={openEditNav}
style={{
border: "1px solid var(--ss-border-color)",
borderRadius: "8px",
background: "transparent",
color: "var(--ss-text-main)",
padding: "4px 10px",
}}
>
{t("common.edit")}
</button>
))
}
>
{editingNav ? (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "12px",
flex: 1,
minHeight: 0,
overflowY: "auto",
}}
>
<div style={{ fontSize: "12px", color: "var(--ss-text-secondary)" }}>
{t(
"settings.mobile.dragHint",
"拖动可调整顺序。前 4 项显示在底栏,其余显示在“更多”。"
)}
</div>
{[
{
list: "bottom" as const,
title: t("settings.mobile.bottomSection", "底栏(最多 4 个)"),
keys: editingBottomNavKeys,
},
{
list: "more" as const,
title: t("settings.mobile.moreSection", "更多"),
keys: editingMoreNavKeys,
},
].map((group) => (
<div key={group.list} style={{ marginTop: group.list === "more" ? 10 : 0 }}>
<div
style={{
marginBottom: "8px",
fontSize: "12px",
color: "var(--ss-text-secondary)",
}}
>
{group.title}
</div>
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
{Array.from({ length: group.keys.length + 1 }, (_, i) => {
const key = group.keys[i]
const item = key ? MOBILE_NAV_ITEMS.find((it) => it.key === key) : null
return (
<div key={`${group.list}-slot-${i}`}>
<div
onDragEnter={() => setDragOverSlot({ list: group.list, index: i })}
onDragOver={(e) => {
e.preventDefault()
setDragOverSlot({ list: group.list, index: i })
}}
onDrop={() => handleDropToList(group.list, i)}
>
{dragOverSlot?.list === group.list && dragOverSlot?.index === i ? (
dropPlaceholder
) : (
<div style={{ height: "8px" }} />
)}
</div>
{key && item && (
<button
type="button"
draggable
onDragStart={() => {
setDraggingNavKey(key)
setDraggingFromList(group.list)
}}
onDragEnd={() => {
setDraggingNavKey(null)
setDraggingFromList(null)
setDragOverSlot(null)
}}
style={{
border: "1px solid var(--ss-border-color)",
borderRadius: "10px",
background: "var(--ss-bg-color)",
color: "var(--ss-text-main)",
minHeight: "44px",
padding: "8px 10px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "8px",
fontSize: "14px",
opacity: draggingNavKey === key ? 0.35 : 1,
transform: draggingNavKey === key ? "scale(0.985)" : "scale(1)",
transition: "opacity 120ms ease, transform 120ms ease",
cursor: "grab",
}}
>
<span style={{ display: "flex", alignItems: "center", gap: "8px" }}>
{mobileBottomNavIconMap[key]}
<span>{t(item.labelKey)}</span>
</span>
<span
style={{
display: "inline-flex",
alignItems: "center",
gap: "4px",
color: "var(--ss-text-secondary)",
opacity: 0.9,
}}
>
<UpOutlined style={{ fontSize: "11px" }} />
<DownOutlined style={{ fontSize: "11px" }} />
<HolderOutlined style={{ fontSize: "12px" }} />
</span>
</button>
)}
</div>
)
})}
</div>
</div>
))}
</div>
) : (
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
{mobileBottomOverflowItems.map((item) => (
<button
key={item.key}
type="button"
onClick={() => onMenuChange(item.key)}
style={{
border: "1px solid var(--ss-border-color)",
borderRadius: "10px",
background: "var(--ss-bg-color)",
color: "var(--ss-text-main)",
height: "44px",
padding: "0 12px",
display: "flex",
alignItems: "center",
gap: "8px",
fontSize: "14px",
}}
>
{mobileBottomNavIconMap[item.key]}
<span>{t(item.labelKey)}</span>
</button>
))}
</div>
)}
</Drawer>
<OOBE visible={wizardVisible} onComplete={() => setWizardVisible(false)} /> <OOBE visible={wizardVisible} onComplete={() => setWizardVisible(false)} />
+1 -1
View File
@@ -380,7 +380,7 @@ export function ContentArea({
/> />
<Route <Route
path="/settings" path="/settings"
element={<Settings permission={permission} mobileNavigationEnabled={isPortraitMode} />} element={<Settings permission={permission} />}
/> />
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
</Routes> </Routes>
+2 -107
View File
@@ -1,5 +1,4 @@
import React, { useEffect, useMemo, useRef, useState } from "react" import React, { useEffect, useMemo, useRef, useState } from "react"
import { RightOutlined } from "@ant-design/icons"
import { import {
Tabs, Tabs,
Card, Card,
@@ -18,12 +17,6 @@ import { ThemeQuickSettings } from "./ThemeQuickSettings"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { changeLanguage, getCurrentLanguage, languageOptions, AppLanguage } from "../i18n" import { changeLanguage, getCurrentLanguage, languageOptions, AppLanguage } from "../i18n"
import { useResponsive } from "../hooks/useResponsive" import { useResponsive } from "../hooks/useResponsive"
import { useLocation, useNavigate } from "react-router-dom"
import {
MOBILE_NAV_ITEMS,
MobileNavKey,
sanitizeMobileNavKeys,
} from "../shared/mobileNavigation"
type permissionLevel = "admin" | "points" | "view" type permissionLevel = "admin" | "points" | "view"
type appSettings = { type appSettings = {
@@ -33,11 +26,7 @@ type appSettings = {
search_keyboard_layout?: "t9" | "qwerty26" search_keyboard_layout?: "t9" | "qwerty26"
disable_search_keyboard?: boolean disable_search_keyboard?: boolean
auto_score_enabled?: boolean auto_score_enabled?: boolean
mobile_bottom_nav_items?: MobileNavKey[]
} }
const DEFAULT_MOBILE_BOTTOM_NAV_ITEMS: MobileNavKey[] = MOBILE_NAV_ITEMS.map((item) => item.key)
const withTimeout = async ( const withTimeout = async (
promise: Promise<any>, promise: Promise<any>,
ms: number, ms: number,
@@ -58,11 +47,8 @@ const withTimeout = async (
export const Settings: React.FC<{ export const Settings: React.FC<{
permission: permissionLevel permission: permissionLevel
mobileNavigationEnabled?: boolean }> = ({ permission }) => {
}> = ({ permission, mobileNavigationEnabled = false }) => {
const { t } = useTranslation() const { t } = useTranslation()
const navigate = useNavigate()
const location = useLocation()
const breakpoint = useResponsive() const breakpoint = useResponsive()
const isMobile = breakpoint === "xs" || breakpoint === "sm" const isMobile = breakpoint === "xs" || breakpoint === "sm"
const [activeTab, setActiveTab] = useState("appearance") const [activeTab, setActiveTab] = useState("appearance")
@@ -73,7 +59,6 @@ export const Settings: React.FC<{
window_zoom: "1.0", window_zoom: "1.0",
search_keyboard_layout: "qwerty26", search_keyboard_layout: "qwerty26",
disable_search_keyboard: false, disable_search_keyboard: false,
mobile_bottom_nav_items: DEFAULT_MOBILE_BOTTOM_NAV_ITEMS,
}) })
const [securityStatus, setSecurityStatus] = useState<{ const [securityStatus, setSecurityStatus] = useState<{
@@ -148,19 +133,6 @@ export const Settings: React.FC<{
) )
}, [permission, t]) }, [permission, t])
const mobilePageItems = useMemo(
() =>
MOBILE_NAV_ITEMS.filter((item) => item.key !== "home" && item.key !== "settings").map(
(item) => ({
key: item.key,
path: item.path,
label: t(item.labelKey),
disabled: Boolean(item.adminOnly) && !canAdmin,
})
),
[canAdmin, t]
)
const emitDataUpdated = (category: "events" | "students" | "reasons" | "all") => { const emitDataUpdated = (category: "events" | "students" | "reasons" | "all") => {
window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category } })) window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category } }))
} }
@@ -184,13 +156,7 @@ export const Settings: React.FC<{
if (!(window as any).api) return if (!(window as any).api) return
const res = await (window as any).api.getAllSettings() const res = await (window as any).api.getAllSettings()
if (res.success && res.data) { if (res.success && res.data) {
setSettings({ setSettings(res.data)
...res.data,
mobile_bottom_nav_items: sanitizeMobileNavKeys(
res.data.mobile_bottom_nav_items,
DEFAULT_MOBILE_BOTTOM_NAV_ITEMS
),
})
setPgConnectionString(res.data.pg_connection_string || "") setPgConnectionString(res.data.pg_connection_string || "")
setPgConnectionStatus(res.data.pg_connection_status || { connected: true, type: "sqlite" }) setPgConnectionStatus(res.data.pg_connection_status || { connected: true, type: "sqlite" })
} }
@@ -220,15 +186,6 @@ export const Settings: React.FC<{
return { ...prev, disable_search_keyboard: Boolean(change.value) } return { ...prev, disable_search_keyboard: Boolean(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 }
if (change?.key === "mobile_bottom_nav_items") {
return {
...prev,
mobile_bottom_nav_items: sanitizeMobileNavKeys(
change.value,
DEFAULT_MOBILE_BOTTOM_NAV_ITEMS
),
}
}
return prev return prev
}) })
}) })
@@ -637,18 +594,6 @@ export const Settings: React.FC<{
}) })
} }
const handleMobileBottomNavChange = async (value: string[]) => {
if (!(window as any).api) return
const next = sanitizeMobileNavKeys(value, DEFAULT_MOBILE_BOTTOM_NAV_ITEMS)
const res = await (window as any).api.setSetting("mobile_bottom_nav_items", next)
if (res.success) {
setSettings((prev) => ({ ...prev, mobile_bottom_nav_items: next }))
messageApi.success(t("settings.general.saved"))
} else {
messageApi.error(res.message || t("settings.general.saveFailed"))
}
}
const tabItems = [ const tabItems = [
{ {
key: "appearance", key: "appearance",
@@ -776,27 +721,6 @@ export const Settings: React.FC<{
</div> </div>
</Form.Item> </Form.Item>
{mobileNavigationEnabled && (
<Form.Item label={t("settings.mobile.bottomNav.label")}>
<Select
mode="multiple"
value={settings.mobile_bottom_nav_items || DEFAULT_MOBILE_BOTTOM_NAV_ITEMS}
onChange={(v) => handleMobileBottomNavChange(v as string[])}
style={{ width: "100%", maxWidth: "520px" }}
disabled={!canAdmin}
options={MOBILE_NAV_ITEMS.map((item) => ({
value: item.key,
label: t(item.labelKey),
disabled: Boolean(item.adminOnly) && !canAdmin,
}))}
/>
<div
style={{ marginTop: "4px", fontSize: "12px", color: "var(--ss-text-secondary)" }}
>
{t("settings.mobile.bottomNav.hint")}
</div>
</Form.Item>
)}
</Form> </Form>
</Card> </Card>
), ),
@@ -1359,35 +1283,6 @@ export const Settings: React.FC<{
{permissionTag} {permissionTag}
</div> </div>
{mobileNavigationEnabled && isMobile && (
<Card
title={t("settings.mobile.navigationTitle", "页面入口")}
bodyStyle={{ padding: 0 }}
style={{
marginBottom: "16px",
backgroundColor: "var(--ss-card-bg)",
color: "var(--ss-text-main)",
}}
>
<div className="ss-settings-mobile-nav-list">
{mobilePageItems.map((item) => (
<Button
key={item.key}
disabled={item.disabled}
type="text"
className={`ss-settings-mobile-nav-item${
location.pathname.startsWith(item.path) ? " is-active" : ""
}`}
onClick={() => navigate(item.path)}
>
<span className="ss-settings-mobile-nav-item-label">{item.label}</span>
<RightOutlined className="ss-settings-mobile-nav-item-arrow" />
</Button>
))}
</div>
</Card>
)}
<Tabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} /> <Tabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} />
<Modal <Modal
+7 -1
View File
@@ -167,7 +167,13 @@
"bottomNav": { "bottomNav": {
"label": "Bottom Navigation Features", "label": "Bottom Navigation Features",
"hint": "Choose which features appear in the mobile bottom bar. The bar shows up to 4 items; extra items go under \"More\"." "hint": "Choose which features appear in the mobile bottom bar. The bar shows up to 4 items; extra items go under \"More\"."
} },
"dragHint": "Drag to reorder. The first 4 items appear in the bottom bar; the rest stay in \"More\".",
"dropHere": "Release to drop here",
"bottomSection": "Bottom Bar (up to 4)",
"moreSection": "More",
"moveToMore": "Move to More",
"moveToBottom": "Move to Bottom"
}, },
"zoomUpdated": "Interface zoom updated", "zoomUpdated": "Interface zoom updated",
"updateFailed": "Update failed", "updateFailed": "Update failed",
+7 -1
View File
@@ -167,7 +167,13 @@
"bottomNav": { "bottomNav": {
"label": "底部导航功能", "label": "底部导航功能",
"hint": "可选择要出现在手机底部导航的功能。底栏最多展示 4 项,其余会自动收纳到“更多”。" "hint": "可选择要出现在手机底部导航的功能。底栏最多展示 4 项,其余会自动收纳到“更多”。"
} },
"dragHint": "拖动可调整顺序。前 4 项显示在底栏,其余显示在“更多”。",
"dropHere": "松手后放到这里",
"bottomSection": "底栏(最多 4 个)",
"moreSection": "更多",
"moveToMore": "移到更多",
"moveToBottom": "移到底栏"
}, },
"zoomUpdated": "界面缩放已更新", "zoomUpdated": "界面缩放已更新",
"updateFailed": "更新失败", "updateFailed": "更新失败",