mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
Merge branch 'main' of https://github.com/SECTL/SecScore
This commit is contained in:
+9
-2
@@ -366,7 +366,14 @@ function MainContent(): React.JSX.Element {
|
|||||||
>
|
>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
<Layout style={{ height: "100%", flexDirection: "row", overflow: "hidden" }}>
|
<Layout style={{ height: "100%", flexDirection: "row", overflow: "hidden" }}>
|
||||||
{!immersiveMode && (
|
<div
|
||||||
|
className={`ss-immersive-sidebar ${immersiveMode ? "is-hidden" : "is-visible"}`}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--ss-sidebar-width": `${sidebarCollapsed ? 64 : 200}px`,
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
activeMenu={activeMenu}
|
activeMenu={activeMenu}
|
||||||
permission={permission}
|
permission={permission}
|
||||||
@@ -376,7 +383,7 @@ function MainContent(): React.JSX.Element {
|
|||||||
floatingExpanded={floatingSidebarExpanded}
|
floatingExpanded={floatingSidebarExpanded}
|
||||||
onFloatingExpandedChange={setFloatingSidebarExpanded}
|
onFloatingExpandedChange={setFloatingSidebarExpanded}
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
<ContentArea
|
<ContentArea
|
||||||
permission={permission}
|
permission={permission}
|
||||||
hasAnyPassword={hasAnyPassword}
|
hasAnyPassword={hasAnyPassword}
|
||||||
|
|||||||
@@ -225,3 +225,54 @@ html.platform-macos #root {
|
|||||||
.ss-group-board-modal .ant-modal-body {
|
.ss-group-board-modal .ant-modal-body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ss-immersive-sidebar {
|
||||||
|
--ss-sidebar-width: 200px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: var(--ss-sidebar-width);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--ss-sidebar-bg);
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
transition:
|
||||||
|
width 260ms cubic-bezier(0.22, 1, 0.36, 1),
|
||||||
|
opacity 220ms ease,
|
||||||
|
transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
will-change: width, opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ss-immersive-sidebar .ant-layout-sider {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ss-immersive-sidebar.is-hidden {
|
||||||
|
width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-10px);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ss-immersive-toolbar {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0) scale(1);
|
||||||
|
transition:
|
||||||
|
width 420ms cubic-bezier(0.2, 1.2, 0.26, 1),
|
||||||
|
opacity 200ms ease,
|
||||||
|
transform 320ms cubic-bezier(0.2, 1.2, 0.26, 1);
|
||||||
|
will-change: width, opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ss-immersive-toolbar.is-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(16px) scale(0.98);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.ss-immersive-sidebar,
|
||||||
|
.ss-immersive-toolbar {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+64
-24
@@ -117,6 +117,9 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||||||
const groupRefs = useRef<Record<string, HTMLDivElement | null>>({})
|
const groupRefs = useRef<Record<string, HTMLDivElement | null>>({})
|
||||||
const searchAreaRef = useRef<HTMLDivElement>(null)
|
const searchAreaRef = useRef<HTMLDivElement>(null)
|
||||||
|
const immersiveToolbarRef = useRef<HTMLDivElement>(null)
|
||||||
|
const immersiveToolbarContentRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [immersiveToolbarWidth, setImmersiveToolbarWidth] = useState<number | null>(null)
|
||||||
|
|
||||||
const [selectedStudent, setSelectedStudent] = useState<student | null>(null)
|
const [selectedStudent, setSelectedStudent] = useState<student | null>(null)
|
||||||
const [batchMode, setBatchMode] = useState(false)
|
const [batchMode, setBatchMode] = useState(false)
|
||||||
@@ -320,6 +323,33 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
return () => document.removeEventListener("mousedown", onDocumentClick)
|
return () => document.removeEventListener("mousedown", onDocumentClick)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!immersiveMode) return
|
||||||
|
const contentEl = immersiveToolbarContentRef.current
|
||||||
|
if (!contentEl) return
|
||||||
|
|
||||||
|
let frameId: number | null = null
|
||||||
|
const updateToolbarWidth = () => {
|
||||||
|
if (frameId !== null) cancelAnimationFrame(frameId)
|
||||||
|
frameId = requestAnimationFrame(() => {
|
||||||
|
const contentWidth = Math.ceil(contentEl.getBoundingClientRect().width)
|
||||||
|
const nextWidth = contentWidth + 20
|
||||||
|
setImmersiveToolbarWidth((prev) => (prev === nextWidth ? prev : nextWidth))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateToolbarWidth()
|
||||||
|
const observer = new ResizeObserver(updateToolbarWidth)
|
||||||
|
observer.observe(contentEl)
|
||||||
|
window.addEventListener("resize", updateToolbarWidth)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect()
|
||||||
|
window.removeEventListener("resize", updateToolbarWidth)
|
||||||
|
if (frameId !== null) cancelAnimationFrame(frameId)
|
||||||
|
}
|
||||||
|
}, [immersiveMode])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (longPressTimerRef.current !== null) {
|
if (longPressTimerRef.current !== null) {
|
||||||
@@ -376,6 +406,10 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
setSearchKeyword((prev) => `${prev}${keyValue}`)
|
setSearchKeyword((prev) => `${prev}${keyValue}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getImmersivePopupContainer = useCallback((triggerNode: HTMLElement) => {
|
||||||
|
return immersiveToolbarRef.current ?? triggerNode.parentElement ?? document.body
|
||||||
|
}, [])
|
||||||
|
|
||||||
const getDisplayText = (name: string) => {
|
const getDisplayText = (name: string) => {
|
||||||
if (!name) return ""
|
if (!name) return ""
|
||||||
return name.length > 2 ? name.substring(name.length - 2) : name
|
return name.length > 2 ? name.substring(name.length - 2) : name
|
||||||
@@ -2467,29 +2501,34 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{immersiveMode && (
|
<div
|
||||||
<div
|
ref={immersiveToolbarRef}
|
||||||
style={{
|
data-immersive-toolbar="true"
|
||||||
position: "fixed",
|
className={`ss-immersive-toolbar ${immersiveMode ? "is-visible" : "is-hidden"}`}
|
||||||
left: "50%",
|
aria-hidden={!immersiveMode}
|
||||||
bottom: isPortraitMode ? "12px" : "16px",
|
style={{
|
||||||
transform: "translateX(-50%)",
|
position: "fixed",
|
||||||
zIndex: 1100,
|
left: "50%",
|
||||||
width: "max-content",
|
bottom: isPortraitMode ? "12px" : "16px",
|
||||||
maxWidth: "calc(100vw - 20px)",
|
zIndex: 1100,
|
||||||
borderRadius: "999px",
|
width: immersiveToolbarWidth ? `${immersiveToolbarWidth}px` : "max-content",
|
||||||
border: "1px solid color-mix(in srgb, var(--ss-border-color) 80%, transparent)",
|
maxWidth: "calc(100vw - 20px)",
|
||||||
backgroundColor: "var(--ss-card-bg)",
|
borderRadius: "999px",
|
||||||
background: "color-mix(in srgb, var(--ss-card-bg) 62%, transparent)",
|
border: "1px solid color-mix(in srgb, var(--ss-border-color) 80%, transparent)",
|
||||||
backdropFilter: "blur(16px)",
|
backgroundColor: "var(--ss-card-bg)",
|
||||||
WebkitBackdropFilter: "blur(16px)",
|
background: "color-mix(in srgb, var(--ss-card-bg) 62%, transparent)",
|
||||||
boxShadow: "0 8px 30px rgba(0, 0, 0, 0.16)",
|
backdropFilter: "blur(16px)",
|
||||||
padding: "10px",
|
WebkitBackdropFilter: "blur(16px)",
|
||||||
overflow: "hidden",
|
boxShadow: "0 8px 30px rgba(0, 0, 0, 0.16)",
|
||||||
}}
|
padding: "10px",
|
||||||
>
|
overflow: "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
ref={searchAreaRef}
|
ref={(node) => {
|
||||||
|
searchAreaRef.current = node
|
||||||
|
immersiveToolbarContentRef.current = node
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -2519,6 +2558,7 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
<Select
|
<Select
|
||||||
value={sortType}
|
value={sortType}
|
||||||
onChange={(v) => setSortType(v as SortType)}
|
onChange={(v) => setSortType(v as SortType)}
|
||||||
|
getPopupContainer={getImmersivePopupContainer}
|
||||||
style={{ width: 126, flexShrink: 0 }}
|
style={{ width: 126, flexShrink: 0 }}
|
||||||
options={[
|
options={[
|
||||||
{ value: "alphabet", label: t("home.sortBy.alphabet") },
|
{ value: "alphabet", label: t("home.sortBy.alphabet") },
|
||||||
@@ -2530,6 +2570,7 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
<Select
|
<Select
|
||||||
value={layoutType}
|
value={layoutType}
|
||||||
onChange={(v) => setLayoutType(v as LayoutType)}
|
onChange={(v) => setLayoutType(v as LayoutType)}
|
||||||
|
getPopupContainer={getImmersivePopupContainer}
|
||||||
style={{ width: 126, flexShrink: 0 }}
|
style={{ width: 126, flexShrink: 0 }}
|
||||||
options={[
|
options={[
|
||||||
{ value: "grouped", label: t("home.layoutBy.grouped") },
|
{ value: "grouped", label: t("home.layoutBy.grouped") },
|
||||||
@@ -2625,8 +2666,7 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{isPortraitMode ? (
|
{isPortraitMode ? (
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|||||||
@@ -423,7 +423,7 @@
|
|||||||
"operationTitle": "积分操作:{{name}}",
|
"operationTitle": "积分操作:{{name}}",
|
||||||
"submitOperation": "提交操作",
|
"submitOperation": "提交操作",
|
||||||
"operationTitleBatch": "积分操作:已选 {{count}} 人",
|
"operationTitleBatch": "积分操作:已选 {{count}} 人",
|
||||||
"undoLastAction": "撤销上一步",
|
"undoLastAction": "撤销",
|
||||||
"undoLastHint": "撤销上一条:{{name}} {{delta}}",
|
"undoLastHint": "撤销上一条:{{name}} {{delta}}",
|
||||||
"undoUnavailable": "当前没有可撤销的积分操作",
|
"undoUnavailable": "当前没有可撤销的积分操作",
|
||||||
"undoLastSuccess": "已撤销上一步积分操作",
|
"undoLastSuccess": "已撤销上一步积分操作",
|
||||||
@@ -592,7 +592,7 @@
|
|||||||
},
|
},
|
||||||
"rewardExchange": {
|
"rewardExchange": {
|
||||||
"title": "奖励兑换",
|
"title": "奖励兑换",
|
||||||
"enterMode": "进入奖励兑换模式",
|
"enterMode": "奖励兑换",
|
||||||
"exitMode": "退出奖励兑换模式",
|
"exitMode": "退出奖励兑换模式",
|
||||||
"modeHint": "当前为奖励兑换模式,点击学生卡可选择可兑换奖励。",
|
"modeHint": "当前为奖励兑换模式,点击学生卡可选择可兑换奖励。",
|
||||||
"normalHint": "当前显示学生原始积分,点击按钮后将显示可兑换积分。",
|
"normalHint": "当前显示学生原始积分,点击按钮后将显示可兑换积分。",
|
||||||
|
|||||||
Reference in New Issue
Block a user