新增横竖模式切换并支持侧栏悬浮展开

This commit is contained in:
JSR
2026-03-18 21:05:07 +08:00
parent d4cbfc556a
commit 224c89f4dd
3 changed files with 146 additions and 33 deletions
+36 -1
View File
@@ -51,6 +51,8 @@ function MainContent(): React.JSX.Element {
const [authVisible, setAuthVisible] = useState(false) const [authVisible, setAuthVisible] = useState(false)
const [authPassword, setAuthPassword] = useState("") const [authPassword, setAuthPassword] = useState("")
const [authLoading, setAuthLoading] = useState(false) const [authLoading, setAuthLoading] = useState(false)
const [isPortraitMode, setIsPortraitMode] = useState(false)
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
const activeMenu = useMemo(() => { const activeMenu = useMemo(() => {
const p = location.pathname const p = location.pathname
@@ -127,6 +129,30 @@ function MainContent(): React.JSX.Element {
if (key === "settings") navigate("/settings") if (key === "settings") navigate("/settings")
} }
const toggleOrientationMode = async () => {
const api = (window as any).api
if (!api) return
const nextPortraitMode = !isPortraitMode
try {
const maximized = await api.windowIsMaximized()
if (maximized) {
await api.windowMaximize()
}
if (nextPortraitMode) {
await api.windowResize(800, 1000)
setSidebarCollapsed(true)
} else {
await api.windowResize(1180, 680)
setSidebarCollapsed(false)
}
setIsPortraitMode(nextPortraitMode)
} catch (error) {
messageApi.error(t("common.error"))
console.error("Failed to toggle orientation mode:", error)
}
}
const isDark = currentTheme?.mode === "dark" const isDark = currentTheme?.mode === "dark"
const brandColor = currentTheme?.config?.tdesign?.brandColor || "#0052D9" const brandColor = currentTheme?.config?.tdesign?.brandColor || "#0052D9"
@@ -141,12 +167,21 @@ function MainContent(): React.JSX.Element {
> >
{contextHolder} {contextHolder}
<Layout style={{ height: "100vh", flexDirection: "row", overflow: "hidden" }}> <Layout style={{ height: "100vh", flexDirection: "row", overflow: "hidden" }}>
<Sidebar activeMenu={activeMenu} permission={permission} onMenuChange={onMenuChange} /> <Sidebar
activeMenu={activeMenu}
permission={permission}
onMenuChange={onMenuChange}
collapsed={sidebarCollapsed}
floatingExpand={isPortraitMode}
onCollapsedChange={setSidebarCollapsed}
/>
<ContentArea <ContentArea
permission={permission} permission={permission}
hasAnyPassword={hasAnyPassword} hasAnyPassword={hasAnyPassword}
onAuthClick={() => setAuthVisible(true)} onAuthClick={() => setAuthVisible(true)}
onLogout={logout} onLogout={logout}
isPortraitMode={isPortraitMode}
onToggleOrientation={toggleOrientationMode}
/> />
<OOBE visible={wizardVisible} onComplete={() => setWizardVisible(false)} /> <OOBE visible={wizardVisible} onComplete={() => setWizardVisible(false)} />
+11
View File
@@ -45,6 +45,8 @@ interface ContentAreaProps {
hasAnyPassword: boolean hasAnyPassword: boolean
onAuthClick: () => void onAuthClick: () => void
onLogout: () => void onLogout: () => void
isPortraitMode: boolean
onToggleOrientation: () => void
} }
export function ContentArea({ export function ContentArea({
@@ -52,6 +54,8 @@ export function ContentArea({
hasAnyPassword, hasAnyPassword,
onAuthClick, onAuthClick,
onLogout, onLogout,
isPortraitMode,
onToggleOrientation,
}: ContentAreaProps): React.JSX.Element { }: ContentAreaProps): React.JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
@@ -132,6 +136,13 @@ export function ContentArea({
} }
> >
<Space size="small"> <Space size="small">
<Button
size="small"
onClick={onToggleOrientation}
title={isPortraitMode ? "当前:竖屏模式" : "当前:横屏模式"}
>
</Button>
{permissionTag} {permissionTag}
{hasAnyPassword && ( {hasAnyPassword && (
<> <>
+99 -32
View File
@@ -22,6 +22,9 @@ interface SidebarProps {
activeMenu: string activeMenu: string
permission: "admin" | "points" | "view" permission: "admin" | "points" | "view"
onMenuChange: (value: string) => void onMenuChange: (value: string) => void
collapsed: boolean
floatingExpand: boolean
onCollapsedChange: (collapsed: boolean) => void
} }
interface DbStatus { interface DbStatus {
@@ -30,9 +33,16 @@ interface DbStatus {
error?: string error?: string
} }
export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps): React.JSX.Element { export function Sidebar({
activeMenu,
permission,
onMenuChange,
collapsed,
floatingExpand,
onCollapsedChange,
}: SidebarProps): React.JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const [collapsed, setCollapsed] = useState(false) const [floatingExpanded, setFloatingExpanded] = useState(false)
const [dbStatus, setDbStatus] = useState<DbStatus>({ type: "sqlite", connected: true }) const [dbStatus, setDbStatus] = useState<DbStatus>({ type: "sqlite", connected: true })
const [syncLoading, setSyncLoading] = useState(false) const [syncLoading, setSyncLoading] = useState(false)
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
@@ -95,6 +105,12 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps):
const [forceSyncLoading, setForceSyncLoading] = useState(false) const [forceSyncLoading, setForceSyncLoading] = useState(false)
useEffect(() => {
if (!floatingExpand || !collapsed) {
setFloatingExpanded(false)
}
}, [floatingExpand, collapsed])
const handleForceSync = async () => { const handleForceSync = async () => {
if (!(window as any).api) return if (!(window as any).api) return
@@ -175,25 +191,15 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps):
}, },
] ]
return ( const showFloatingPanel = floatingExpand && collapsed && floatingExpanded
<Sider
className="ss-sidebar" const renderSidebarBody = (isCollapsedView: boolean, isFloatingPanel = false) => (
width={200} <>
collapsed={collapsed}
collapsedWidth={64}
style={{
background: "var(--ss-sidebar-bg)",
borderRight: "1px solid var(--ss-border-color)",
display: "flex",
flexDirection: "column",
}}
theme="light"
>
<div <div
data-tauri-drag-region data-tauri-drag-region
style={ style={
{ {
padding: collapsed ? "24px 8px 12px" : "32px 24px 16px", padding: isCollapsedView ? "24px 8px 12px" : "32px 24px 16px",
textAlign: "center", textAlign: "center",
WebkitAppRegion: "drag", WebkitAppRegion: "drag",
userSelect: "none", userSelect: "none",
@@ -205,25 +211,41 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps):
<Button <Button
type="text" type="text"
size="small" size="small"
onClick={() => setCollapsed((prev) => !prev)} onClick={() => {
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} if (floatingExpand && collapsed) {
style={{ setFloatingExpanded((prev) => !prev)
position: "absolute", return
top: "8px", }
right: "8px", onCollapsedChange(!collapsed)
WebkitAppRegion: "no-drag",
}} }}
icon={
floatingExpand && collapsed
? isFloatingPanel
? <MenuFoldOutlined />
: <MenuUnfoldOutlined />
: isCollapsedView
? <MenuUnfoldOutlined />
: <MenuFoldOutlined />
}
style={
{
position: "absolute",
top: "8px",
right: "8px",
WebkitAppRegion: "no-drag",
} as React.CSSProperties
}
/> />
<img <img
src={appLogo} src={appLogo}
style={{ style={{
width: collapsed ? "40px" : "48px", width: isCollapsedView ? "40px" : "48px",
height: collapsed ? "40px" : "48px", height: isCollapsedView ? "40px" : "48px",
marginBottom: collapsed ? "0" : "12px", marginBottom: isCollapsedView ? "0" : "12px",
}} }}
alt="logo" alt="logo"
/> />
{!collapsed && ( {!isCollapsedView && (
<> <>
<h2 <h2
style={{ style={{
@@ -251,9 +273,14 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps):
<div style={{ flex: 1, overflowY: "auto", display: "flex", flexDirection: "column" }}> <div style={{ flex: 1, overflowY: "auto", display: "flex", flexDirection: "column" }}>
<Menu <Menu
mode="inline" mode="inline"
inlineCollapsed={collapsed} inlineCollapsed={isCollapsedView}
selectedKeys={[activeMenu]} selectedKeys={[activeMenu]}
onClick={({ key }) => onMenuChange(key)} onClick={({ key }) => {
onMenuChange(key)
if (floatingExpand && collapsed) {
setFloatingExpanded(false)
}
}}
style={{ style={{
width: "100%", width: "100%",
border: "none", border: "none",
@@ -263,7 +290,7 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps):
/> />
</div> </div>
{!collapsed && dbStatus.type === "postgresql" && ( {!isCollapsedView && dbStatus.type === "postgresql" && (
<Card <Card
size="small" size="small"
style={{ style={{
@@ -273,7 +300,6 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps):
}} }}
styles={{ body: { padding: "12px" } }} styles={{ body: { padding: "12px" } }}
> >
{contextHolder}
<Space orientation="vertical" size={4} style={{ width: "100%" }}> <Space orientation="vertical" size={4} style={{ width: "100%" }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}> <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<Space size={4}> <Space size={4}>
@@ -324,6 +350,47 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps):
</Space> </Space>
</Card> </Card>
)} )}
</>
)
return (
<Sider
className="ss-sidebar"
width={200}
collapsed={collapsed}
collapsedWidth={64}
style={{
background: "var(--ss-sidebar-bg)",
borderRight: "1px solid var(--ss-border-color)",
display: "flex",
flexDirection: "column",
overflow: "visible",
position: "relative",
}}
theme="light"
>
{contextHolder}
{renderSidebarBody(collapsed)}
{showFloatingPanel && (
<div
style={{
position: "absolute",
left: "64px",
top: 0,
bottom: 0,
width: "200px",
background: "var(--ss-sidebar-bg)",
borderRight: "1px solid var(--ss-border-color)",
boxShadow: "6px 0 18px rgba(0, 0, 0, 0.12)",
zIndex: 1200,
display: "flex",
flexDirection: "column",
}}
>
{renderSidebarBody(false, true)}
</div>
)}
</Sider> </Sider>
) )
} }