Compare commits

...

34 Commits

Author SHA1 Message Date
Leonmmcoset
84cbf588e0 error 2025-11-04 18:40:12 +08:00
Leonmmcoset
6d3cb4f4b3 csc no dog talking 2025-10-26 21:21:31 +08:00
Leonmmcoset
b8447640df upd 2025-10-26 21:04:12 +08:00
Leonmmcoset
6f155f8238 qrcode 2025-10-26 20:49:02 +08:00
Leonmmcoset
e77b0d7978 feat(组件): 添加线性进度条组件并替换圆形进度条
重构进度指示器,使用新的LinearProgress组件替换原有的CircularProgress
更新文件管理器中的进度指示器引用
修改首页加载动画为线性进度条样式
2025-10-26 20:23:36 +08:00
Leonmmcoset
59d39fbc11 update theme 2025-10-25 18:04:54 +08:00
Leonmmcoset
18d98a25f9 update togglebutton theme 2025-10-25 17:42:34 +08:00
Leonmmcoset
15992bd1a8 change font 2025-10-25 09:57:04 +08:00
Leonmmcoset
d05cde8068 awa 2025-10-24 23:34:49 +08:00
Leonmmcoset
96aeebeee5 qwq 2025-10-24 23:13:50 +08:00
Leonmmcoset
a0e791f2eb update 2025-10-24 22:54:40 +08:00
Leonmmcoset
ea27514eac awa 2025-10-24 22:31:42 +08:00
Leonmmcoset
76671ed625 fix 2025-10-24 22:18:17 +08:00
Leonmmcoset
7519a81c88 awa 2025-10-24 22:04:48 +08:00
Leonmmcoset
1ffe6a8a52 update animation 2025-10-24 21:40:57 +08:00
Leonmmcoset
ac2d49a400 update button theme 2025-10-24 21:29:38 +08:00
Leonmmcoset
2f4930b39a apdate 2025-10-24 21:01:37 +08:00
Leonmmcoset
f1c5f86553 new 2025-10-24 20:10:10 +08:00
Leonmmcoset
64e7385a24 修复部分bug,公告功能支持HTML 2025-10-23 21:07:43 +08:00
Leonmmcoset
c40a899d15 add anc button to navbar 2025-10-23 20:55:03 +08:00
Leonmmcoset
abd4aaac7c fix anc no dialog 2025-10-23 20:35:01 +08:00
Leonmmcoset
b808feb1fd add 2025-10-23 20:25:29 +08:00
Leonmmcoset
f7ccd807a1 awa 2025-10-23 20:17:50 +08:00
Leonmmcoset
3a21802d76 apd 2025-10-23 20:00:56 +08:00
Leonmmcoset
6dc3943b70 awa 2025-10-22 22:19:47 +08:00
Leonmmcoset
d3c6876b22 awa 2025-10-22 22:04:27 +08:00
Leonmmcoset
93c15be7c4 awa 2025-10-22 21:48:37 +08:00
Leonmmcoset
c72540fe3a fix bug 2025-10-22 21:37:20 +08:00
Leonmmcoset
651fc084fa fix textarea bug 2025-10-22 21:26:32 +08:00
Leonmmcoset
80718dfc8b add anc 2025-10-22 21:11:59 +08:00
Leonmmcoset
d18177452b awa 2025-10-21 21:35:04 +08:00
Leonmmcoset
7b9c686558 change captcha inputbox theme 2025-10-21 21:27:40 +08:00
Leonmmcoset
a353222f16 add logo 2025-10-21 21:15:56 +08:00
Leonmmcoset
c1f1fc25ee change powerby 2025-10-21 20:53:31 +08:00
29 changed files with 525 additions and 161 deletions

View File

@@ -1,6 +1,12 @@
<!doctype html> <!DOCTYPE html>
<html> <html>
<head> <head>
<!--
Using LeonPan open source project.
Powered by LeonCloud
awa
-->
<link href="https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Latin/MiSansLatin-Medium.min.css" rel="stylesheet">
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="shortcut icon" href="{pwa_small_icon}" sizes="64x64" /> <link rel="shortcut icon" href="{pwa_small_icon}" sizes="64x64" />
<meta <meta
@@ -40,43 +46,39 @@
transform: scale(0.8); transform: scale(0.8);
animation: fadeIn 0.6s ease-out 0.3s forwards; animation: fadeIn 0.6s ease-out 0.3s forwards;
} }
#app-loader .spinner { #app-loader .progress-container {
width: 28px; width: 200px;
height: 28px; height: 4px;
position: relative; background-color: rgba(0, 0, 0, 0.1);
border-radius: 2px;
overflow: hidden;
opacity: 0; opacity: 0;
transform: scale(0.8); transform: scale(0.8);
animation: fadeIn 0.6s ease-out 0.3s forwards; animation: fadeIn 0.6s ease-out 0.3s forwards;
} }
#app-loader .spinner { #app-loader .progress-bar {
display: inline-block; height: 100%;
width: 40px; background-color: var(--defaultThemeColor);
height: 40px; border-radius: 2px;
} animation: linearProgress 1.5s infinite ease-in-out;
#app-loader .spinner svg {
display: block;
}
#app-loader .spinner .stroke {
stroke: var(--defaultThemeColor);
stroke-linecap: round;
animation: spinDash 1.4s ease-in-out infinite;
}
#app-loader .spinner .background {
stroke: rgba(0, 0, 0, 0.1)
} }
@keyframes spinDash { @keyframes linearProgress {
0% { 0% {
stroke-dasharray: 1px, 200px; transform: translateX(-100%);
stroke-dashoffset: 0; width: 50%;
} }
50% { 50% {
stroke-dasharray: 100px, 200px; transform: translateX(100%);
stroke-dashoffset: -15px; width: 50%;
}
51% {
transform: translateX(100%);
width: 50%;
} }
100% { 100% {
stroke-dasharray: 1px, 200px; transform: translateX(-100%);
stroke-dashoffset: -126px; width: 50%;
} }
} }
@keyframes fadeIn { @keyframes fadeIn {
@@ -91,11 +93,8 @@
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="app-loader"> <div id="app-loader">
<div class="logo"></div> <div class="logo"></div>
<div class="spinner"> <div class="progress-container">
<svg viewBox="22 22 44 44"> <div class="progress-bar"></div>
<circle class="background" cx="44" cy="44" r="20" fill="none" stroke-width="4"></circle>
<circle class="stroke" cx="44" cy="44" r="20" fill="none" stroke-width="4"></circle>
</svg>
</div> </div>
</div> </div>
<script async type="module" src="/src/main.tsx"></script> <script async type="module" src="/src/main.tsx"></script>

View File

@@ -404,6 +404,7 @@
"deleteViewSetting": "Delete view setting" "deleteViewSetting": "Delete view setting"
}, },
"modals": { "modals": {
"scanQRCodeToAccess": "Scan QR Code to Access",
"includePasswordInShareLink": "Include password in share link", "includePasswordInShareLink": "Include password in share link",
"includePasswordInShareLinkDes": "If selected, password will be included in the share link, and no password is required when accessing the share link.", "includePasswordInShareLinkDes": "If selected, password will be included in the share link, and no password is required when accessing the share link.",
"showFileName": "Show file name", "showFileName": "Show file name",

View File

@@ -61,6 +61,8 @@
"totalFilesAndFolders": "Files and Folders", "totalFilesAndFolders": "Files and Folders",
"shareLinks": "Share links", "shareLinks": "Share links",
"totalBlobs": "Blobs", "totalBlobs": "Blobs",
"storagePolicies": "Storage Policies Usage",
"noStoragePolicies": "No storage policies available",
"homepage": "Homepage", "homepage": "Homepage",
"github": "GitHub", "github": "GitHub",
"documents": "Documents", "documents": "Documents",

View File

@@ -404,6 +404,7 @@
"deleteViewSetting": "删除视图设置" "deleteViewSetting": "删除视图设置"
}, },
"modals": { "modals": {
"scanQRCodeToAccess": "扫描二维码访问分享链接",
"includePasswordInShareLink": "在链接中包含密码", "includePasswordInShareLink": "在链接中包含密码",
"includePasswordInShareLinkDes": "勾选后,分享链接中会包含密码,通过此链接访问时不需要再输入密码。", "includePasswordInShareLinkDes": "勾选后,分享链接中会包含密码,通过此链接访问时不需要再输入密码。",
"showFileName": "显示文件名", "showFileName": "显示文件名",

View File

@@ -102,5 +102,7 @@
"50005": "内部错误 ({{message}})", "50005": "内部错误 ({{message}})",
"50010": "目标节点不可用", "50010": "目标节点不可用",
"50011": "文件元信息查询失败" "50011": "文件元信息查询失败"
} },
"announcement": "公告",
"dontShowAgain": "不再显示"
} }

View File

@@ -61,6 +61,8 @@
"totalFilesAndFolders": "文件与目录", "totalFilesAndFolders": "文件与目录",
"shareLinks": "分享链接", "shareLinks": "分享链接",
"totalBlobs": "文件 Blob", "totalBlobs": "文件 Blob",
"storagePolicies": "存储策略空间使用",
"noStoragePolicies": "暂无存储策略",
"homepage": "主页", "homepage": "主页",
"github": "GitHub", "github": "GitHub",
"documents": "文档", "documents": "文档",
@@ -113,6 +115,8 @@
"retryDelayDes": "任务重试的初始延迟时间(秒)。" "retryDelayDes": "任务重试的初始延迟时间(秒)。"
}, },
"settings": { "settings": {
"announcementEnabled": "启用公告",
"announcementEnabledDes": "启用后,用户登录时会看到公告弹窗",
"headlessFooter": "登录会话页面底部", "headlessFooter": "登录会话页面底部",
"headlessFooterDes": "用户登录、注册、回调结果等页面底部展示的自定义 HTML 内容。", "headlessFooterDes": "用户登录、注册、回调结果等页面底部展示的自定义 HTML 内容。",
"headlessBottom": "登录会话页面主体底部", "headlessBottom": "登录会话页面主体底部",

BIN
public/static/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

View File

@@ -15,6 +15,7 @@ import { GrowDialogTransition } from "./component/FileManager/Search/SearchPopup
import Warning from "./component/Icons/Warning.tsx"; import Warning from "./component/Icons/Warning.tsx";
import { useAppSelector } from "./redux/hooks.ts"; import { useAppSelector } from "./redux/hooks.ts";
import { changeThemeColor } from "./util"; import { changeThemeColor } from "./util";
import AnnouncementDialog from "./component/Common/AnnouncementDialog.tsx";
export const applyThemeWithOverrides = (themeConfig: ThemeOptions): ThemeOptions => { export const applyThemeWithOverrides = (themeConfig: ThemeOptions): ThemeOptions => {
return { return {
@@ -23,11 +24,20 @@ export const applyThemeWithOverrides = (themeConfig: ThemeOptions): ThemeOptions
...themeConfig.shape, ...themeConfig.shape,
borderRadius: 4, borderRadius: 4,
}, },
typography: {
...themeConfig.typography,
fontFamily:
'"MiSans", -apple-system, BlinkMacSystemFont, "ZCOOL QingKe HuangYou", "Inter", "Hiragino Sans GB", "Microsoft YaHei", sans-serif',
},
components: { components: {
MuiCssBaseline: { MuiCssBaseline: {
styleOverrides: { styleOverrides: {
body: { body: {
overscrollBehavior: "none", overscrollBehavior: "none",
fontFamily: '"MiSans", sans-serif',
},
html: {
fontFamily: '"MiSans", sans-serif',
}, },
}, },
}, },
@@ -52,6 +62,35 @@ export const applyThemeWithOverrides = (themeConfig: ThemeOptions): ThemeOptions
styleOverrides: { styleOverrides: {
root: { root: {
textTransform: "none", textTransform: "none",
borderRadius: 4,
border: "1px solid transparent",
transition: "all 0.2s ease",
minWidth: 48,
minHeight: 32,
padding: "4px 12px",
"&:hover": {
backgroundColor: "rgba(0, 0, 0, 0.04)",
},
"&.Mui-selected": {
backgroundColor: "rgba(25, 118, 210, 0.1)",
color: "rgb(25, 118, 210)",
borderColor: "rgba(25, 118, 210, 0.3)",
"&:hover": {
backgroundColor: "rgba(25, 118, 210, 0.15)",
},
},
},
},
},
MuiToggleButtonGroup: {
styleOverrides: {
grouped: {
margin: 2,
border: 0,
borderRadius: 4,
"&.Mui-disabled": {
border: 0,
},
}, },
}, },
}, },
@@ -63,6 +102,12 @@ export const applyThemeWithOverrides = (themeConfig: ThemeOptions): ThemeOptions
}, },
defaultProps: { defaultProps: {
disableElevation: true, disableElevation: true,
disableRipple: true,
},
},
MuiButtonBase: {
defaultProps: {
disableRipple: true,
}, },
}, },
MuiAlert: { MuiAlert: {
@@ -374,6 +419,7 @@ const AppContent = () => {
}} }}
> >
<GlobalDialogs /> <GlobalDialogs />
<AnnouncementDialog />
<Outlet /> <Outlet />
</SnackbarProvider> </SnackbarProvider>
</> </>

View File

@@ -20,10 +20,19 @@ export interface Version {
commit: string; commit: string;
} }
export interface StoragePolicySpace {
id: number;
name: string;
type: string;
used: number;
total: number;
}
export interface HomepageSummary { export interface HomepageSummary {
metrics_summary?: MetricsSummary; metrics_summary?: MetricsSummary;
site_urls: string[]; site_urls: string[];
version: Version; version: Version;
storage_policies?: StoragePolicySpace[];
} }
export interface ManualRefreshLicenseService { export interface ManualRefreshLicenseService {

View File

@@ -1,6 +1,20 @@
import { CustomProps, ViewerGroup } from "./explorer.ts"; import { CustomProps, ViewerGroup } from "./explorer.ts";
import { User } from "./user.ts"; import { User } from "./user.ts";
export interface CustomNavItem {
title?: string;
name?: string;
icon?: string;
url: string;
target?: string;
}
export interface CustomHTML {
headlessFooter?: string;
headlessBottom?: string;
sidebarBottom?: string;
}
export enum CaptchaType { export enum CaptchaType {
NORMAL = "normal", NORMAL = "normal",
RECAPTCHA = "recaptcha", RECAPTCHA = "recaptcha",
@@ -46,21 +60,11 @@ export interface SiteConfig {
custom_nav_items?: CustomNavItem[]; custom_nav_items?: CustomNavItem[];
custom_html?: CustomHTML; custom_html?: CustomHTML;
thumb_exts?: string[]; thumb_exts?: string[];
announcement?: string;
announcement_enabled?: boolean;
} }
export interface CaptchaResponse { export interface CaptchaResponse {
ticket: string; ticket: string;
image: string; image: string;
} }
export interface CustomNavItem {
name: string;
url: string;
icon: string;
}
export interface CustomHTML {
headless_footer?: string;
headless_bottom?: string;
sidebar_bottom?: string;
}

View File

@@ -72,23 +72,11 @@ const ProDialog = ({ open, onClose }: ProDialogProps) => {
> >
<DialogContent> <DialogContent>
<Typography variant="body1" color="text.secondary"> <Typography variant="body1" color="text.secondary">
{t("pro.description")} Cloudreve的sb pro玩意
</Typography>
<Typography variant="body1" fontWeight={600} sx={{ mt: 2 }}>
{t("pro.proInclude")}
</Typography> </Typography>
<List dense> <List dense>
{features.map((feature) => ( <ListItem>
<ListItem key={feature}>
<ListItemIcon
sx={{
minWidth: "36px",
}}
>
<CheckmarkCircleFilled color="primary" />
</ListItemIcon>
<ListItemText <ListItemText
slotProps={{ slotProps={{
primary: { primary: {
@@ -97,10 +85,21 @@ const ProDialog = ({ open, onClose }: ProDialogProps) => {
}, },
}} }}
> >
{t(`pro.${feature}`)} Pro我骂谁
</ListItemText>
</ListItem>
<ListItem>
<ListItemText
slotProps={{
primary: {
sx: {},
variant: "body1",
},
}}
>
</ListItemText> </ListItemText>
</ListItem> </ListItem>
))}
</List> </List>
{showPromotion && ( {showPromotion && (
<Alert <Alert
@@ -128,11 +127,8 @@ const ProDialog = ({ open, onClose }: ProDialogProps) => {
}} }}
> >
<Button variant="outlined" color="primary" onClick={onClose}> <Button variant="outlined" color="primary" onClick={onClose}>
{t("pro.later")}
</Button> </Button>
<StyledButton onClick={openMore} variant="contained" color="primary">
{t("pro.learnMore")}
</StyledButton>
</StyledDialogActions> </StyledDialogActions>
</DraggableDialog> </DraggableDialog>
); );

View File

@@ -31,7 +31,7 @@ const SharesInput = (props: SharesInputProps) => {
}, },
mt: 0, mt: 0,
}} }}
variant="outlined" variant="filled"
margin="dense" margin="dense"
placeholder={t("dashboard:settings.searchShare")} placeholder={t("dashboard:settings.searchShare")}
type="text" type="text"

View File

@@ -43,9 +43,10 @@ import SparkleFilled from "../../Icons/SparkleFilled.tsx";
import Telegram from "../../Icons/Telegram.tsx"; import Telegram from "../../Icons/Telegram.tsx";
import PageContainer from "../../Pages/PageContainer.tsx"; import PageContainer from "../../Pages/PageContainer.tsx";
import PageHeader from "../../Pages/PageHeader.tsx"; import PageHeader from "../../Pages/PageHeader.tsx";
import ProDialog from "../Common/ProDialog.tsx"; import ProDialog from "../../Admin/Common/ProDialog.tsx";
import SiteUrlWarning from "./SiteUrlWarning.tsx"; import SiteUrlWarning from "./SiteUrlWarning.tsx";
import CommentMultiple from "../../Icons/CommentMultiple.tsx"; import CommentMultiple from "../../Icons/CommentMultiple.tsx";
import LinearProgress from "@mui/material/LinearProgress";
const StyledPaper = styled(Paper)(({ theme }) => ({ const StyledPaper = styled(Paper)(({ theme }) => ({
padding: theme.spacing(3), padding: theme.spacing(3),
@@ -306,6 +307,67 @@ const Home = () => {
</SwitchTransition> </SwitchTransition>
</StyledPaper> </StyledPaper>
</Grid> </Grid>
<Grid item xs={12}>
<StyledPaper>
<Typography variant="subtitle1" fontWeight={500}>
{t("summary.storagePolicies")}
</Typography>
<Divider sx={{ mb: 2, mt: 1 }} />
<Box>
{summary?.storage_policies && summary.storage_policies.length > 0 ? (
<Box sx={{ gap: 3, display: "flex", flexDirection: "column" }}>
{summary.storage_policies.map((policy) => {
const percentage = policy.total > 0 ? (policy.used / policy.total) * 100 : 0;
const formatSize = (bytes: number) => {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB", "TB", "PB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};
return (
<Box key={policy.id}>
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 0.5 }}>
<Typography variant="subtitle2">{policy.name}</Typography>
<Typography variant="caption" color="text.secondary">
{formatSize(policy.used)} / {formatSize(policy.total)}
</Typography>
</Box>
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 1 }}>
<Typography variant="caption" color="text.secondary">
{policy.type}
</Typography>
<Typography variant="caption" color="text.secondary">
{percentage.toFixed(1)}%
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={percentage}
sx={{
height: 8,
borderRadius: 4,
backgroundColor: theme.palette.divider,
"& .MuiLinearProgress-bar": {
borderRadius: 4,
backgroundColor:
percentage > 90 ? red[500] : percentage > 70 ? yellow[500] : green[500],
},
}}
/>
</Box>
);
})}
</Box>
) : (
<Box sx={{ py: 2, textAlign: "center", color: "text.secondary" }}>
{t("summary.noStoragePolicies")}
</Box>
)}
</Box>
</StyledPaper>
</Grid>
<Grid item xs={12} md={5} lg={4}> <Grid item xs={12} md={5} lg={4}>
<StyledPaper sx={{ p: 0 }}> <StyledPaper sx={{ p: 0 }}>
<Box sx={{ p: 3, display: "flex", alignItems: "center" }}> <Box sx={{ p: 3, display: "flex", alignItems: "center" }}>

View File

@@ -173,6 +173,8 @@ const Settings = () => {
"tos_url", "tos_url",
"privacy_policy_url", "privacy_policy_url",
"show_app_promotion", "show_app_promotion",
"announcement",
"announcement_enabled",
]} ]}
> >
<SiteInformation /> <SiteInformation />

View File

@@ -64,10 +64,32 @@ const SiteInformation = () => {
<NoMarginHelperText>{t("settings.customFooterHTMLDes")}</NoMarginHelperText> <NoMarginHelperText>{t("settings.customFooterHTMLDes")}</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
<SettingForm title={t("settings.announcement")} lgWidth={5} pro> <SettingForm title={t("settings.announcementEnabled", "启用公告")} lgWidth={5} pro={false}>
<FormControl fullWidth> <FormControl fullWidth>
<DenseFilledTextField inputProps={{ readOnly: true }} fullWidth multiline rows={4} /> <Switch
<NoMarginHelperText>{t("settings.announcementDes")}</NoMarginHelperText> checked={isTrueVal(values.announcement_enabled)}
onChange={(e) =>
setSettings({
announcement_enabled: e.target.checked ? "1" : "0",
})
}
/>
<NoMarginHelperText>
{t("settings.announcementEnabledDes", "启用后,用户登录时会看到公告弹窗")}
</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm title={t("settings.announcement", "公告内容")} lgWidth={5} pro={false}>
<FormControl fullWidth>
<DenseFilledTextField
fullWidth
multiline
rows={4}
value={values.announcement || ""}
onChange={(e) => setSettings({ announcement: e.target.value })}
disabled={false}
/>
<NoMarginHelperText>{t("settings.announcementDes", "设置用户登录后看到的公告内容")}</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
<SettingForm title={t("settings.tosUrl")} lgWidth={5}> <SettingForm title={t("settings.tosUrl")} lgWidth={5}>

View File

@@ -0,0 +1,165 @@
import { useEffect, useState } from "react";
import { styled } from "@mui/material/styles";
import {
Dialog,
DialogContent,
DialogTitle,
Checkbox,
FormControlLabel,
Button,
Paper,
Typography,
} from "@mui/material";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
import { getAnnouncement } from "../../redux/thunks/site.ts";
import SessionManager from "../../session";
interface AnnouncementDialogProps {
forceOpen?: boolean;
onClose?: () => void;
}
// 为公告内容创建一个带样式的容器组件
const AnnouncementContent = styled("div")`
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 16px;
margin-bottom: 8px;
}
p {
margin-bottom: 12px;
line-height: 1.6;
}
ul,
ol {
margin-left: 24px;
margin-bottom: 12px;
}
li {
margin-bottom: 4px;
}
a {
color: #1976d2;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
img {
max-width: 100%;
height: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 16px;
}
th,
td {
border: 1px solid #e0e0e0;
padding: 8px;
}
th {
background-color: #f5f5f5;
}
`;
const AnnouncementDialog = ({ forceOpen = false, onClose }: AnnouncementDialogProps) => {
const { t } = useTranslation("common");
const dispatch = useAppDispatch();
const announcement = useAppSelector((state) => state.siteConfig.basic.config.announcement || "");
const announcementEnabled = useAppSelector((state) => state.siteConfig.basic.config.announcement_enabled || false);
const [open, setOpen] = useState(false);
const [dontShowAgain, setDontShowAgain] = useState(false);
useEffect(() => {
// 加载公告
dispatch(getAnnouncement());
}, [dispatch]);
// 保存上次显示的公告内容,用于检测公告是否更新
const [lastAnnouncement, setLastAnnouncement] = useState("");
useEffect(() => {
// 检查公告是否更新
const hasAnnouncementChanged = announcement !== lastAnnouncement && lastAnnouncement !== "";
// 如果公告已更新,清除不再显示状态并强制显示
if (hasAnnouncementChanged) {
// 使用特殊标记来覆盖之前的设置
SessionManager.set("announcement_dismissed", "false");
}
// 更新上次显示的公告内容
if (announcement) {
setLastAnnouncement(announcement);
}
// 检查是否应该显示公告
// 更健壮的检测逻辑:只有当设置为"true"时才视为不再显示
const dismissed = SessionManager.get("announcement_dismissed") === "true";
const shouldShow = announcementEnabled && announcement && announcement.trim() !== "" && !dismissed;
if (shouldShow) {
// 延迟显示,让页面加载完成
const timer = setTimeout(() => setOpen(true), 1000);
return () => clearTimeout(timer);
} else {
// 确保当不应该显示且没有强制打开时关闭弹窗
if (!forceOpen) {
setOpen(false);
}
}
}, [announcement, announcementEnabled, lastAnnouncement]);
// 处理外部强制打开
useEffect(() => {
if (forceOpen && announcement && announcement.trim() !== "") {
setOpen(true);
// 重置不再显示选项
setDontShowAgain(false);
}
}, [announcement, announcementEnabled, lastAnnouncement, forceOpen]);
const handleClose = () => {
if (dontShowAgain) {
// 保存用户选择不再显示
SessionManager.set("announcement_dismissed", "true");
}
setOpen(false);
// 通知父组件弹窗已关闭
if (onClose) {
onClose();
}
};
return (
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
<DialogTitle sx={{ borderBottom: 1, borderColor: "divider", paddingBottom: 1 }}>
{t("announcement", "公告")}
</DialogTitle>
<DialogContent sx={{ padding: 2, maxHeight: "80vh" }}>
<Paper elevation={0} sx={{ p: 2 }}>
<AnnouncementContent dangerouslySetInnerHTML={{ __html: announcement }} />
</Paper>
<FormControlLabel
control={<Checkbox checked={dontShowAgain} onChange={(e) => setDontShowAgain(e.target.checked)} />}
label={t("dontShowAgain", "不再显示")}
sx={{ mt: 2 }}
/>
</DialogContent>
<Button onClick={handleClose} variant="contained" color="primary" sx={{ m: 2 }}>
{t("ok", "确定")}
</Button>
</Dialog>
);
};
// 导出一个可以被外部组件控制的版本
export { AnnouncementDialog };
export default AnnouncementDialog;

View File

@@ -1,39 +1,27 @@
import { Box, CircularProgress, circularProgressClasses, CircularProgressProps } from "@mui/material"; import { Box } from "@mui/material";
import { forwardRef } from "react"; import { forwardRef } from "react";
import LinearProgressComponent from "./LinearProgress";
export interface FacebookCircularProgressProps extends CircularProgressProps { export interface FacebookCircularProgressProps {
bgColor?: string; sx?: any;
color?: string;
size?: number;
thickness?: number;
fgColor?: string; fgColor?: string;
} }
const FacebookCircularProgress = forwardRef(({ sx, bgColor, fgColor, ...rest }: FacebookCircularProgressProps, ref) => { const FacebookCircularProgress = forwardRef(({ sx, color, fgColor }: FacebookCircularProgressProps, ref) => {
return ( return (
<Box sx={{ position: "relative", ...sx }} ref={ref}> <Box sx={{ display: "flex", justifyContent: "center", alignItems: "center", ...sx }} ref={ref}>
<CircularProgress <LinearProgressComponent color={color || fgColor} height={8} />
variant="determinate"
sx={{
color: (theme) => bgColor ?? theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
}}
size={40}
thickness={4}
{...rest}
value={100}
/>
<CircularProgress
sx={{
color: (theme) => fgColor ?? theme.palette.primary.main,
position: "absolute",
left: 0,
[`& .${circularProgressClasses.circle}`]: {
strokeLinecap: "round",
},
}}
size={40}
thickness={4}
{...rest}
/>
</Box> </Box>
); );
}); });
export default FacebookCircularProgress; export default FacebookCircularProgress;
// === 耻辱榜 ===
// csc禁止狗叫
// 要不然你的传奇事迹将在这几行注释里留存一辈子
// 2025/10/26
// === 永久留存 禁止删除 ===

View File

@@ -8,7 +8,11 @@ function ErrorBoundary() {
// Uncaught ReferenceError: path is not defined // Uncaught ReferenceError: path is not defined
return ( return (
<div style={{ padding: 16 }}> <div style={{ padding: 16 }}>
<h1 style={{ color: "#a4a4a4", margin: "5px 0px" }}>:(</h1> <h1 style={{ color: "#a4a4a4", margin: "5px 0px" }}>:) OHH FUCKING YES!!!</h1>
<p>LeonPan发生了一些意想不到的错误</p>
<p>
<a href="http://leonmmcoset.jjxmm.win:2000/leonmmcoset/leonpan-assets/issues">LeonGit Issues</a>
</p>
<h2 style={{ margin: "15px 0px" }}>{t("common:renderError")}</h2> <h2 style={{ margin: "15px 0px" }}>{t("common:renderError")}</h2>
{!!error && ( {!!error && (
<details> <details>
@@ -23,6 +27,8 @@ function ErrorBoundary() {
)} )}
</details> </details>
)} )}
<hr />
<p></p>
</div> </div>
); );
} }

View File

@@ -10,7 +10,7 @@ export const OutlineIconTextField = ({ icon, ...rest }: OutlineIconTextFieldProp
return ( return (
<TextField <TextField
{...rest} {...rest}
variant="standard" variant="filled"
slotProps={{ slotProps={{
input: { input: {
startAdornment: !isMobile && <InputAdornment position="start">{icon}</InputAdornment>, startAdornment: !isMobile && <InputAdornment position="start">{icon}</InputAdornment>,

View File

@@ -0,0 +1,30 @@
import { Box, LinearProgress, linearProgressClasses } from "@mui/material";
import { forwardRef } from "react";
export interface LinearProgressProps {
color?: string;
height?: number;
}
const LinearProgressComponent = forwardRef(({ color, height = 8, ...rest }: LinearProgressProps, ref) => {
return (
<Box sx={{ width: "100%", maxWidth: 200, ...rest }} ref={ref}>
<LinearProgress
variant="indeterminate"
sx={{
height: height,
borderRadius: height / 2,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: (theme) => theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: height / 2,
backgroundColor: color || ((theme) => theme.palette.primary.main),
},
}}
/>
</Box>
);
});
export default LinearProgressComponent;

View File

@@ -152,6 +152,7 @@ const ExtractArchive = () => {
}, },
}} }}
fullWidth fullWidth
variant="filled"
placeholder={t("application:modals.passwordDescription")} placeholder={t("application:modals.passwordDescription")}
label={t("modals.password")} label={t("modals.password")}
value={password} value={password}

View File

@@ -1,4 +1,16 @@
import { Box, Checkbox, Collapse, DialogContent, IconButton, Stack, Tooltip, useTheme } from "@mui/material"; import {
Box,
Checkbox,
Collapse,
DialogContent,
IconButton,
Paper,
Stack,
Tooltip,
Typography,
useTheme,
} from "@mui/material";
import { QRCodeSVG } from "qrcode.react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { TFunction } from "i18next"; import { TFunction } from "i18next";
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
@@ -216,6 +228,17 @@ const ShareDialog = () => {
}, },
}} }}
/> />
<Paper
sx={{ mt: 2, p: 3, display: "flex", flexDirection: "column", alignItems: "center" }}
elevation={1}
>
<Typography variant="subtitle2" sx={{ mb: 2 }}>
{t("application:modals.scanQRCodeToAccess")}
</Typography>
<Box sx={{ bgcolor: "white", p: 2 }}>
<QRCodeSVG value={finalShareLink} size={200} level="M" includeMargin />
</Box>
</Paper>
{shareLinkPassword.password && ( {shareLinkPassword.password && (
<> <>
<Collapse in={!includePassword}> <Collapse in={!includePassword}>

View File

@@ -6,7 +6,7 @@ import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import { ConfigLoadState } from "../../../redux/siteConfigSlice.ts"; import { ConfigLoadState } from "../../../redux/siteConfigSlice.ts";
import { openEmptyContextMenu } from "../../../redux/thunks/filemanager.ts"; import { openEmptyContextMenu } from "../../../redux/thunks/filemanager.ts";
import { loadSiteConfig } from "../../../redux/thunks/site.ts"; import { loadSiteConfig } from "../../../redux/thunks/site.ts";
import CircularProgress from "../../Common/CircularProgress.tsx"; import FacebookCircularProgress from "../../Common/CircularProgress.tsx";
import "../../Common/FadeTransition.css"; import "../../Common/FadeTransition.css";
import { RadiusFrame } from "../../Frame/RadiusFrame.tsx"; import { RadiusFrame } from "../../Frame/RadiusFrame.tsx";
import ExplorerError from "./ExplorerError.tsx"; import ExplorerError from "./ExplorerError.tsx";
@@ -137,7 +137,7 @@ const Explorer = () => {
alignItems: "center", alignItems: "center",
}} }}
> >
<CircularProgress /> <FacebookCircularProgress />
</Box> </Box>
)} )}
{index == ExplorerPage.GridView && <GridView />} {index == ExplorerPage.GridView && <GridView />}

View File

@@ -1,12 +1,11 @@
import { Box, Container, Grid, Paper, AppBar, Toolbar, IconButton, Typography, Button } from "@mui/material"; import { Box, Container, Grid, Paper, Typography, Button } from "@mui/material";
import MenuIcon from "@mui/icons-material/Menu";
import { Outlet, useNavigation } from "react-router-dom"; import { Outlet, useNavigation } from "react-router-dom";
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../redux/hooks";
import AutoHeight from "../Common/AutoHeight.tsx"; import AutoHeight from "../Common/AutoHeight";
import CircularProgress from "../Common/CircularProgress.tsx"; import CircularProgress from "../Common/CircularProgress";
import Logo from "../Common/Logo.tsx"; import Logo from "../Common/Logo";
import LanguageSwitcher from "../Common/LanguageSwitcher.tsx"; import LanguageSwitcher from "../Common/LanguageSwitcher";
import PoweredBy from "./PoweredBy.tsx"; import PoweredBy from "./PoweredBy";
const Loading = () => { const Loading = () => {
return ( return (
@@ -25,7 +24,7 @@ const Loading = () => {
const HeadlessFrame = () => { const HeadlessFrame = () => {
const loading = useAppSelector((state) => state.globalState.loading.headlessFrame); const loading = useAppSelector((state) => state.globalState.loading.headlessFrame);
const { headless_footer, headless_bottom, sidebar_bottom } = useAppSelector( const { headlessFooter, headlessBottom } = useAppSelector(
(state) => state.siteConfig.basic?.config?.custom_html ?? {}, (state) => state.siteConfig.basic?.config?.custom_html ?? {},
); );
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@@ -41,35 +40,6 @@ const HeadlessFrame = () => {
overflow: "auto", overflow: "auto",
}} }}
> >
<Box sx={{ flexGrow: 1 }}>
<AppBar
sx={{
backgroundColor: "#f5f5f5",
}}
>
<Toolbar>
{/* <IconButton
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2 }}
>
<MenuIcon />
</IconButton> */}
{/* <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
News
</Typography> */}
<Logo
sx={{
maxWidth: "40%",
maxHeight: "40px",
mb: 2,
}}
/>
</Toolbar>
</AppBar>
</Box>
<Container maxWidth={"xs"}> <Container maxWidth={"xs"}>
<Grid <Grid
container container
@@ -110,9 +80,9 @@ const HeadlessFrame = () => {
}} }}
> >
<Outlet /> <Outlet />
{headless_bottom && ( {headlessBottom && (
<Box sx={{ width: "100%" }}> <Box sx={{ width: "100%" }}>
<div dangerouslySetInnerHTML={{ __html: headless_bottom }} /> <div dangerouslySetInnerHTML={{ __html: headlessBottom }} />
</Box> </Box>
)} )}
</Box> </Box>
@@ -122,9 +92,9 @@ const HeadlessFrame = () => {
</Paper> </Paper>
</Box> </Box>
<PoweredBy /> <PoweredBy />
{headless_footer && ( {headlessFooter && (
<Box sx={{ width: "100%", mb: 2 }}> <Box sx={{ width: "100%", mb: 2 }}>
<div dangerouslySetInnerHTML={{ __html: headless_footer }} /> <div dangerouslySetInnerHTML={{ __html: headlessFooter }} />
</Box> </Box>
)} )}
</Grid> </Grid>

View File

@@ -46,6 +46,7 @@ import WrenchSettings from "../../Icons/WrenchSettings.tsx";
import { ProChip } from "../../Pages/Setting/SettingForm.tsx"; import { ProChip } from "../../Pages/Setting/SettingForm.tsx";
import NavIconTransition from "./NavIconTransition.tsx"; import NavIconTransition from "./NavIconTransition.tsx";
import SideNavItem from "./SideNavItem.tsx"; import SideNavItem from "./SideNavItem.tsx";
import { AnnouncementDialog } from "../../Common/AnnouncementDialog.tsx";
export interface NavigationItem { export interface NavigationItem {
label: string; label: string;
@@ -246,8 +247,12 @@ export const AdminPageNavigation = memo(() => {
}); });
const PageNavigation = () => { const PageNavigation = () => {
const shopNavEnabled = useAppSelector((state) => state.siteConfig.basic.config.shop_nav_enabled); const { t } = useTranslation("application");
// 移除不存在的shop_nav_enabled属性引用
const appPromotionEnabled = useAppSelector((state) => state.siteConfig.basic.config.app_promotion); const appPromotionEnabled = useAppSelector((state) => state.siteConfig.basic.config.app_promotion);
const announcementEnabled = useAppSelector((state) => state.siteConfig.basic.config.announcement_enabled || false);
const announcement = useAppSelector((state) => state.siteConfig.basic.config.announcement || "");
const [showAnnouncement, setShowAnnouncement] = useState(false);
const user = SessionManager.currentLoginOrNull(); const user = SessionManager.currentLoginOrNull();
const isAdmin = useMemo(() => { const isAdmin = useMemo(() => {
return GroupBS(user?.user).enabled(GroupPermission.is_admin); return GroupBS(user?.user).enabled(GroupPermission.is_admin);
@@ -272,6 +277,21 @@ const PageNavigation = () => {
{connectEnabled && <SideNavItemComponent item={ConnectNavigationItem} />} {connectEnabled && <SideNavItemComponent item={ConnectNavigationItem} />}
<SideNavItemComponent item={TaskNavigationItem} /> <SideNavItemComponent item={TaskNavigationItem} />
{remoteDownloadEnabled && <SideNavItemComponent item={RemoteDownloadNavigationItem} />} {remoteDownloadEnabled && <SideNavItemComponent item={RemoteDownloadNavigationItem} />}
{/* 公告导航项 - 只有当公告启用且有内容时显示 */}
{announcementEnabled && announcement && announcement.trim() !== "" && (
<SideNavItem
onClick={() => setShowAnnouncement(true)}
label={t("navbar.announcement", { defaultValue: "公告" })}
icon={
<NavIconTransition
sx={{ px: 0, py: 0, pr: "14px", height: "20px" }}
iconProps={{ fontSize: "small", color: "action" }}
fileIcon={[Warning, WarningOutlined]}
active={false}
/>
}
/>
)}
</> </>
</Box> </Box>
)} )}
@@ -279,9 +299,9 @@ const PageNavigation = () => {
<Box> <Box>
{customNavItems.map((item) => ( {customNavItems.map((item) => (
<SideNavItemComponent <SideNavItemComponent
key={item.name} key={item.title || item.name || Math.random().toString(36)}
item={{ item={{
label: item.name, label: item.title || item.name || "",
iconifyName: item.icon, iconifyName: item.icon,
path: item.url, path: item.url,
}} }}
@@ -298,6 +318,8 @@ const PageNavigation = () => {
}} }}
/> />
)} )}
{/* 公告对话框 */}
<AnnouncementDialog forceOpen={showAnnouncement} onClose={() => setShowAnnouncement(false)} />
</> </>
); );
}; };

View File

@@ -36,7 +36,9 @@ const PoweredBy = ({ ...rest }: PoweredByProps) => {
}} }}
fontWeight={500} fontWeight={500}
> >
Powered by Miaostars Powered by LeonCloud with
<br />
Copyright {new Date().getFullYear()} LeonMMcoset
</Typography> </Typography>
</Box> </Box>
</Box> </Box>

View File

@@ -1,6 +1,6 @@
.side-enter { .side-enter {
opacity: 0; opacity: 0;
transform: translateX(-100%); transform: translateX(100%);
} }
.side-enter-active { .side-enter-active {
opacity: 1; opacity: 1;
@@ -12,7 +12,7 @@
} }
.side-exit-active { .side-exit-active {
opacity: 0; opacity: 0;
transform: translateX(100%); transform: translateX(-100%);
} }
.side-enter-active, .side-enter-active,
.side-exit-active { .side-exit-active {

View File

@@ -27,3 +27,10 @@ export function updateSiteConfig(): AppThunk {
} }
}; };
} }
export function getAnnouncement(): AppThunk {
return async (dispatch, _getState) => {
// 获取基本配置,其中包含公告信息
await dispatch(loadSiteConfig("basic"));
};
}