mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 12:34:22 +08:00
feat(响应式): 添加移动端适配支持
- 在Sidebar、Settings、OOBE组件中添加响应式布局处理 - 为移动端优化CSS样式,包括模态框、表格和选择器 - 在数据库切换命令中添加详细的日志记录 - 改进OOBE组件在移动设备上的显示效果
This commit is contained in:
@@ -11,6 +11,7 @@ use crate::db::entities::{
|
|||||||
reasons, reward_redemptions, reward_settings, score_events, student_tags, students, tags,
|
reasons, reward_redemptions, reward_settings, score_events, student_tags, students, tags,
|
||||||
};
|
};
|
||||||
use crate::db::migration::run_migration;
|
use crate::db::migration::run_migration;
|
||||||
|
use crate::services::logger::LogLevel;
|
||||||
use crate::services::permission::PermissionLevel;
|
use crate::services::permission::PermissionLevel;
|
||||||
use crate::services::settings::{SettingsKey, SettingsValue};
|
use crate::services::settings::{SettingsKey, SettingsValue};
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
@@ -1052,24 +1053,63 @@ pub async fn db_switch_connection(
|
|||||||
) -> Result<IpcResponse<SwitchConnectionResult>, String> {
|
) -> Result<IpcResponse<SwitchConnectionResult>, String> {
|
||||||
check_admin_permission(&state)?;
|
check_admin_permission(&state)?;
|
||||||
|
|
||||||
|
let state_guard = state.read();
|
||||||
|
let logger = state_guard.logger.read();
|
||||||
|
logger.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
&format!("Database switch requested, connection_string length: {}", connection_string.len()),
|
||||||
|
Some("database"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
drop(state_guard);
|
||||||
|
|
||||||
let (db_type, saved_connection_string, saved_status, conn) = if connection_string
|
let (db_type, saved_connection_string, saved_status, conn) = if connection_string
|
||||||
.starts_with("postgres://")
|
.starts_with("postgres://")
|
||||||
|| connection_string.starts_with("postgresql://")
|
|| connection_string.starts_with("postgresql://")
|
||||||
{
|
{
|
||||||
|
logger.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
"Switching to PostgreSQL database",
|
||||||
|
Some("database"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
let conn = timeout(
|
let conn = timeout(
|
||||||
Duration::from_secs(DB_CONNECT_TIMEOUT_SECS),
|
Duration::from_secs(DB_CONNECT_TIMEOUT_SECS),
|
||||||
create_postgres_connection(&connection_string),
|
create_postgres_connection(&connection_string),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| "PostgreSQL 连接超时,请检查网络或连接字符串".to_string())?
|
.map_err(|_| "PostgreSQL 连接超时,请检查网络或连接字符串".to_string())?
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| {
|
||||||
|
logger.log(
|
||||||
|
LogLevel::Error,
|
||||||
|
&format!("PostgreSQL connection failed: {}", e),
|
||||||
|
Some("database"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
e.to_string()
|
||||||
|
})?;
|
||||||
timeout(
|
timeout(
|
||||||
Duration::from_secs(DB_MIGRATION_TIMEOUT_SECS),
|
Duration::from_secs(DB_MIGRATION_TIMEOUT_SECS),
|
||||||
run_migration(&conn, DatabaseType::PostgreSQL),
|
run_migration(&conn, DatabaseType::PostgreSQL),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| "PostgreSQL 初始化超时,请稍后重试".to_string())?
|
.map_err(|_| "PostgreSQL 初始化超时,请稍后重试".to_string())?
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| {
|
||||||
|
logger.log(
|
||||||
|
LogLevel::Error,
|
||||||
|
&format!("PostgreSQL migration failed: {}", e),
|
||||||
|
Some("database"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
e.to_string()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
logger.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
"PostgreSQL connection and migration successful",
|
||||||
|
Some("database"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
(
|
(
|
||||||
"postgresql".to_string(),
|
"postgresql".to_string(),
|
||||||
@@ -1078,6 +1118,12 @@ pub async fn db_switch_connection(
|
|||||||
conn,
|
conn,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
logger.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
"Switching to SQLite database",
|
||||||
|
Some("database"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
let path = if connection_string.starts_with("sqlite://") {
|
let path = if connection_string.starts_with("sqlite://") {
|
||||||
connection_string
|
connection_string
|
||||||
.strip_prefix("sqlite://")
|
.strip_prefix("sqlite://")
|
||||||
@@ -1089,10 +1135,33 @@ pub async fn db_switch_connection(
|
|||||||
|
|
||||||
let conn = create_sqlite_connection(&path)
|
let conn = create_sqlite_connection(&path)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| {
|
||||||
|
logger.log(
|
||||||
|
LogLevel::Error,
|
||||||
|
&format!("SQLite connection failed: {}", e),
|
||||||
|
Some("database"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
e.to_string()
|
||||||
|
})?;
|
||||||
run_migration(&conn, DatabaseType::SQLite)
|
run_migration(&conn, DatabaseType::SQLite)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| {
|
||||||
|
logger.log(
|
||||||
|
LogLevel::Error,
|
||||||
|
&format!("SQLite migration failed: {}", e),
|
||||||
|
Some("database"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
e.to_string()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
logger.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
"SQLite connection and migration successful",
|
||||||
|
Some("database"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
(
|
(
|
||||||
"sqlite".to_string(),
|
"sqlite".to_string(),
|
||||||
@@ -1151,6 +1220,15 @@ pub async fn db_switch_connection(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let state_guard = state.read();
|
||||||
|
let logger = state_guard.logger.read();
|
||||||
|
logger.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
&format!("Database switched successfully to {}", db_type),
|
||||||
|
Some("database"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(IpcResponse::success(SwitchConnectionResult { db_type }))
|
Ok(IpcResponse::success(SwitchConnectionResult { db_type }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,6 +124,92 @@ select {
|
|||||||
padding-top: 14px;
|
padding-top: 14px;
|
||||||
padding-bottom: 14px;
|
padding-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-modal {
|
||||||
|
max-width: 100vw !important;
|
||||||
|
width: 100vw !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
top: 0 !important;
|
||||||
|
bottom: 0 !important;
|
||||||
|
left: 0 !important;
|
||||||
|
right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-content {
|
||||||
|
height: 100vh !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
flex: 1 !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-wrap {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-dropdown {
|
||||||
|
max-height: 50vh !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-wrapper {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table {
|
||||||
|
min-width: 600px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.ant-modal {
|
||||||
|
max-width: 100vw !important;
|
||||||
|
width: 100vw !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-content {
|
||||||
|
height: 100vh !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
flex: 1 !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-wrap {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-container {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table {
|
||||||
|
min-width: 600px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-dropdown {
|
||||||
|
max-height: 50vh !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ss-table-center .ant-table th,
|
.ss-table-center .ant-table th,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useTheme } from "../../contexts/ThemeContext"
|
|||||||
import { changeLanguage, AppLanguage, languageOptions } from "../../i18n"
|
import { changeLanguage, AppLanguage, languageOptions } from "../../i18n"
|
||||||
import type { themeConfig } from "../../preload/types"
|
import type { themeConfig } from "../../preload/types"
|
||||||
import logoSvg from "../../assets/logoHD.svg"
|
import logoSvg from "../../assets/logoHD.svg"
|
||||||
|
import { useResponsive, useScreenSize } from "../../hooks/useResponsive"
|
||||||
|
|
||||||
interface oobeProps {
|
interface oobeProps {
|
||||||
visible: boolean
|
visible: boolean
|
||||||
@@ -100,6 +101,9 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { currentTheme, setTheme, themes, applyTheme } = useTheme()
|
const { currentTheme, setTheme, themes, applyTheme } = useTheme()
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
|
const breakpoint = useResponsive()
|
||||||
|
const { width: screenWidth, height: screenHeight } = useScreenSize()
|
||||||
|
const isMobile = breakpoint === "xs" || breakpoint === "sm"
|
||||||
|
|
||||||
const [currentStep, setCurrentStep] = useState<oobeStep>("entry")
|
const [currentStep, setCurrentStep] = useState<oobeStep>("entry")
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@@ -902,6 +906,7 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OOBEBackground primaryColor={primaryColor} mode={workingTheme?.mode || "light"} />
|
<OOBEBackground primaryColor={primaryColor} mode={workingTheme?.mode || "light"} />
|
||||||
@@ -913,17 +918,21 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
background: isDark ? "rgba(15, 25, 45, 0.55)" : "rgba(255, 255, 255, 0.65)",
|
background: isDark ? "rgba(15, 25, 45, 0.55)" : "rgba(255, 255, 255, 0.65)",
|
||||||
backdropFilter: "blur(20px)",
|
backdropFilter: "blur(20px)",
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
padding: 32,
|
padding: isMobile ? 20 : 32,
|
||||||
width: 480,
|
width: 480,
|
||||||
maxWidth: "90vw",
|
maxWidth: isMobile ? "100vw" : "90vw",
|
||||||
|
maxHeight: isMobile ? "100vh" : "90vh",
|
||||||
boxShadow: isDark ? "0 8px 32px rgba(0, 0, 0, 0.3)" : "0 8px 32px rgba(0, 0, 0, 0.1)",
|
boxShadow: isDark ? "0 8px 32px rgba(0, 0, 0, 0.3)" : "0 8px 32px rgba(0, 0, 0, 0.1)",
|
||||||
border: isDark ? "1px solid rgba(255, 255, 255, 0.08)" : "1px solid rgba(0, 0, 0, 0.06)",
|
border: isDark ? "1px solid rgba(255, 255, 255, 0.08)" : "1px solid rgba(0, 0, 0, 0.06)",
|
||||||
fontFamily: "var(--ss-font-family)",
|
fontFamily: "var(--ss-font-family)",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
|
|
||||||
<div style={{ marginBottom: 24 }}>
|
<div style={{ marginBottom: isMobile ? 16 : 24, flexShrink: 0 }}>
|
||||||
<Typography.Title
|
<Typography.Title
|
||||||
level={3}
|
level={3}
|
||||||
style={{ margin: 0, color: isDark ? "#fff" : "rgba(0, 0, 0, 0.88)" }}
|
style={{ margin: 0, color: isDark ? "#fff" : "rgba(0, 0, 0, 0.88)" }}
|
||||||
@@ -935,7 +944,7 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: isMobile ? 12 : 16, flexShrink: 0 }}>
|
||||||
<Typography.Title
|
<Typography.Title
|
||||||
level={4}
|
level={4}
|
||||||
style={{ margin: 0, color: isDark ? "#fff" : "rgba(0, 0, 0, 0.88)" }}
|
style={{ margin: 0, color: isDark ? "#fff" : "rgba(0, 0, 0, 0.88)" }}
|
||||||
@@ -944,7 +953,18 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ minHeight: 200, marginBottom: 24 }}>{renderStepContent()}</div>
|
<div
|
||||||
|
style={{
|
||||||
|
minHeight: isMobile ? 150 : 200,
|
||||||
|
marginBottom: isMobile ? 16 : 24,
|
||||||
|
overflowY: isMobile ? "auto" : "visible",
|
||||||
|
overflowX: "hidden",
|
||||||
|
flex: 1,
|
||||||
|
WebkitOverflowScrolling: "touch",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderStepContent()}
|
||||||
|
</div>
|
||||||
|
|
||||||
{currentStep !== "entry" && currentStep !== "postgresql" && (
|
{currentStep !== "entry" && currentStep !== "postgresql" && (
|
||||||
<div
|
<div
|
||||||
@@ -952,9 +972,12 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
flexShrink: 0,
|
||||||
|
flexWrap: isMobile ? "wrap" : "nowrap",
|
||||||
|
gap: isMobile ? 8 : 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: "flex", gap: 8 }}>
|
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||||
{currentStep !== "language" && (
|
{currentStep !== "language" && (
|
||||||
<Button onClick={handlePrev}>{t("common.prev")}</Button>
|
<Button onClick={handlePrev}>{t("common.prev")}</Button>
|
||||||
)}
|
)}
|
||||||
@@ -968,6 +991,9 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
gap: 8,
|
gap: 8,
|
||||||
color: isDark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.45)",
|
color: isDark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.45)",
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
order: isMobile ? 3 : 2,
|
||||||
|
width: isMobile ? "100%" : "auto",
|
||||||
|
justifyContent: isMobile ? "center" : "flex-start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -991,7 +1017,7 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
<span>{t("oobe.step", { current: stepIndex, total: totalSteps })}</span>
|
<span>{t("oobe.step", { current: stepIndex, total: totalSteps })}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: "flex", gap: 8 }}>
|
<div style={{ display: "flex", gap: 8, order: isMobile ? 2 : 3 }}>
|
||||||
{currentStep !== "start" ? (
|
{currentStep !== "start" ? (
|
||||||
<Button type="primary" onClick={handleNext}>
|
<Button type="primary" onClick={handleNext}>
|
||||||
{t("common.next")}
|
{t("common.next")}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
import { ThemeQuickSettings } from "./ThemeQuickSettings"
|
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"
|
||||||
|
|
||||||
type permissionLevel = "admin" | "points" | "view"
|
type permissionLevel = "admin" | "points" | "view"
|
||||||
type appSettings = {
|
type appSettings = {
|
||||||
@@ -47,6 +48,8 @@ const withTimeout = async (
|
|||||||
|
|
||||||
export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission }) => {
|
export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const breakpoint = useResponsive()
|
||||||
|
const isMobile = breakpoint === "xs" || breakpoint === "sm"
|
||||||
const [activeTab, setActiveTab] = useState("appearance")
|
const [activeTab, setActiveTab] = useState("appearance")
|
||||||
const [currentLanguage, setCurrentLanguage] = useState<AppLanguage>(getCurrentLanguage())
|
const [currentLanguage, setCurrentLanguage] = useState<AppLanguage>(getCurrentLanguage())
|
||||||
const [settings, setSettings] = useState<appSettings>({
|
const [settings, setSettings] = useState<appSettings>({
|
||||||
@@ -890,12 +893,17 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
>
|
>
|
||||||
{t("settings.database.connectionExample")}
|
{t("settings.database.connectionExample")}
|
||||||
</div>
|
</div>
|
||||||
<Space>
|
<Space
|
||||||
|
direction={isMobile ? "vertical" : "horizontal"}
|
||||||
|
size={isMobile ? 8 : "small"}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={switchToPg}
|
onClick={switchToPg}
|
||||||
loading={pgSwitchLoading}
|
loading={pgSwitchLoading}
|
||||||
disabled={!canAdmin || !pgConnectionString}
|
disabled={!canAdmin || !pgConnectionString}
|
||||||
|
style={{ width: isMobile ? "100%" : "auto" }}
|
||||||
>
|
>
|
||||||
{t("settings.database.switchToPostgreSQL")}
|
{t("settings.database.switchToPostgreSQL")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -903,6 +911,7 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
onClick={switchToSQLite}
|
onClick={switchToSQLite}
|
||||||
loading={pgSwitchLoading}
|
loading={pgSwitchLoading}
|
||||||
disabled={!canAdmin || pgConnectionStatus.type === "sqlite"}
|
disabled={!canAdmin || pgConnectionStatus.type === "sqlite"}
|
||||||
|
style={{ width: isMobile ? "100%" : "auto" }}
|
||||||
>
|
>
|
||||||
{t("settings.database.switchToSQLite")}
|
{t("settings.database.switchToSQLite")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -910,6 +919,7 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
onClick={uploadLocalToRemote}
|
onClick={uploadLocalToRemote}
|
||||||
loading={pgUploadLoading}
|
loading={pgUploadLoading}
|
||||||
disabled={!canAdmin || pgConnectionStatus.type !== "postgresql"}
|
disabled={!canAdmin || pgConnectionStatus.type !== "postgresql"}
|
||||||
|
style={{ width: isMobile ? "100%" : "auto" }}
|
||||||
>
|
>
|
||||||
{t("settings.database.uploadButton")}
|
{t("settings.database.uploadButton")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from "@ant-design/icons"
|
} from "@ant-design/icons"
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useResponsive } from "../hooks/useResponsive"
|
||||||
import appLogo from "../assets/logoHD.svg"
|
import appLogo from "../assets/logoHD.svg"
|
||||||
|
|
||||||
const { Sider } = Layout
|
const { Sider } = Layout
|
||||||
@@ -44,6 +45,8 @@ export function Sidebar({
|
|||||||
onFloatingExpandedChange,
|
onFloatingExpandedChange,
|
||||||
}: SidebarProps): React.JSX.Element {
|
}: SidebarProps): React.JSX.Element {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const breakpoint = useResponsive()
|
||||||
|
const isMobile = breakpoint === "xs" || breakpoint === "sm"
|
||||||
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()
|
||||||
@@ -254,7 +257,16 @@ export function Sidebar({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!hideMenu && (
|
{!hideMenu && (
|
||||||
<div style={{ flex: 1, overflowY: "auto", display: "flex", flexDirection: "column" }}>
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
overflowY: "auto",
|
||||||
|
overflowX: "hidden",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
WebkitOverflowScrolling: "touch",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Menu
|
<Menu
|
||||||
mode="inline"
|
mode="inline"
|
||||||
inlineCollapsed={isCollapsedView}
|
inlineCollapsed={isCollapsedView}
|
||||||
|
|||||||
Reference in New Issue
Block a user